diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..935128e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +name: build + +on: + pull_request: + push: + branches: + - main + +jobs: + build: + name: build node v24 ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-15-intel + + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + - run: npm ci + - run: npm test + - run: npm audit --audit-level=moderate + - run: npm ls --omit=dev --all + - run: npm pack --dry-run + - run: node --check index.js + - run: node --check lib/buddy-allocator.js + - run: node --check lib/ds-store.js + - run: node --check lib/entry.js + - run: node --check lib/partition.js + - run: node --check test/ds-store.js + - run: node --check test/entry.js diff --git a/README.md b/README.md index 08e76d5..3262413 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# node-ds-store +# @appdmg/ds-store -.DS_Store manipulation and creation from node.js +`.DS_Store` creation for appdmg packages. + +This package is a CommonJS Node.js 24+ modernization of the original +`ds-store` package used by `node-appdmg`. It keeps the appdmg-facing API while +moving the maintained package under the `@appdmg` npm scope. ## Status @@ -11,15 +15,30 @@ limitations and also only allows creating new files from scratch. ## Installation ```sh -npm install ds-store +npm install @appdmg/ds-store ``` ## Usage -```javscript -var DSStore = require('ds-store'); +```javascript +const DSStore = require('@appdmg/ds-store') + +async function main () { + const file = new DSStore() + + file.setWindowPos(100, 100) + file.setWindowSize(640, 480) + file.setIconSize(128) + file.setIconPos('Example.app', 140, 120) + file.setIconPos('Applications', 420, 120) + + await file.write('/Volumes/Example/.DS_Store') +} -var file = new DSStore(); +main().catch((err) => { + console.error(err) + process.exitCode = 1 +}) ``` ## API @@ -28,6 +47,10 @@ var file = new DSStore(); Set the background image to file specified by `path`. +On non-Darwin platforms, pass `{ volumeName: 'Volume Name' }` as the second +argument when using background image aliases. The native macOS volume lookup is +not available there. + ### file.setBackgroundColor(red, green, blue) Set the background color to the color specified by three floats between 0 and 1. @@ -60,6 +83,12 @@ Write the `.DS_Store` information to file at `path`. `cb` will get called with `err` upon file creation. +### await file.write(path) + +Promise-based equivalent of `file.write(path, cb)`. + +The callback form remains supported for existing appdmg callers. + ## Future I have started work on a Buddy Allocator and B-Tree implementation, diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..7a21da6 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,18 @@ +declare class DSStore { + setBackgroundPath(path: string, options?: { volumeName?: string }): void; + setBackgroundColor(red: number, green: number, blue: number): void; + setIconSize(size: number): void; + setIconPos(name: string, x: number, y: number): void; + setWindowPos(x: number, y: number): void; + setWindowSize(width: number, height: number): void; + vSrn(value: 0 | 1): void; + write(path: string): Promise; + write(path: string, callback: (error: Error | null) => void): void; + + /** + * @deprecated Use setBackgroundPath instead. + */ + setBackground(path: string, options?: { volumeName?: string }): void; +} + +export = DSStore; diff --git a/index.js b/index.js index 2c6d3fb..9b045c3 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,11 @@ -var assert = require('assert') -var alias = require('macos-alias') -var util = require('util') +'use strict' -var Entry = require('./lib/entry') -var DSStore = require('./lib/ds-store') +const assert = require('node:assert') +const util = require('node:util') +const alias = require('@appdmg/macos-alias') + +const Entry = require('./lib/entry') +const DSStore = require('./lib/ds-store') function Helper () { this.file = new DSStore() @@ -12,8 +14,9 @@ function Helper () { } } -Helper.prototype.setBackgroundPath = function (path) { +Helper.prototype.setBackgroundPath = function (path, options) { this.opts.backgroundPath = path + this.opts.backgroundAliasOptions = options } Helper.prototype.setBackgroundColor = function (red, green, blue) { @@ -44,20 +47,34 @@ Helper.prototype.vSrn = function (value) { } Helper.prototype.write = function (path, cb) { - var rawAlias, colorComponents + const promise = writeHelper(this, path) + + if (typeof cb === 'function') { + promise.then(function () { + cb(null) + }, cb) + return + } + + return promise +} + +async function writeHelper (helper, path) { + let rawAlias + let colorComponents - if (this.opts.backgroundPath) { - rawAlias = alias.create(this.opts.backgroundPath) + if (helper.opts.backgroundPath) { + rawAlias = alias.create(helper.opts.backgroundPath, helper.opts.backgroundAliasOptions || {}) } - if (this.opts.backgroundColor) { - colorComponents = this.opts.backgroundColor + if (helper.opts.backgroundColor) { + colorComponents = helper.opts.backgroundColor } - this.file.push(Entry.construct('.', 'bwsp', this.opts.window)) - this.file.push(Entry.construct('.', 'icvp', { iconSize: this.opts.iconSize, rawAlias: rawAlias, colorComponents: colorComponents })) + helper.file.push(Entry.construct('.', 'bwsp', helper.opts.window)) + helper.file.push(Entry.construct('.', 'icvp', { iconSize: helper.opts.iconSize, rawAlias: rawAlias, colorComponents: colorComponents })) - this.file.write(path, cb) + await helper.file.write(path) } /* Backwards compatibility */ diff --git a/lib/buddy-allocator.js b/lib/buddy-allocator.js index 002390a..5a031d7 100644 --- a/lib/buddy-allocator.js +++ b/lib/buddy-allocator.js @@ -1,23 +1,25 @@ -var assert = require('assert') +'use strict' + +const assert = require('node:assert') function BuddyAllocator () { this.offsets = new Array(0) this.freelist = new Array(32) - for (var i = 0; i < 32; i++) { + for (let i = 0; i < 32; i++) { this.freelist[i] = [] } this.freelist[31].push(0) - var head = this._alloc(5) + const head = this._alloc(5) assert(head === 0) } BuddyAllocator.prototype._alloc = function (width) { assert(width < 32) - var list = this.freelist[width] + const list = this.freelist[width] if (list.length > 0) { // There is a block of the desired size; return it. @@ -27,8 +29,8 @@ BuddyAllocator.prototype._alloc = function (width) { // Allocate a block of the next larger size; split // it and put the other half on the free list. - var offset = this._alloc(width + 1) - var buddy = offset ^ Math.pow(2, width) + const offset = this._alloc(width + 1) + const buddy = offset ^ Math.pow(2, width) this._free(buddy, width) return offset @@ -36,10 +38,10 @@ BuddyAllocator.prototype._alloc = function (width) { } BuddyAllocator.prototype._free = function (offset, width) { - var list = this.freelist[width] - var buddy = offset ^ Math.pow(2, width) + const list = this.freelist[width] + const buddy = offset ^ Math.pow(2, width) - var idx = list.indexOf(buddy) + const idx = list.indexOf(buddy) if (~idx) { // Our buddy is free. Coalesce, and @@ -64,12 +66,14 @@ BuddyAllocator.prototype.allocate = function (bytes, blocknum) { } } - var wantwidth = 5 + let wantwidth = 5 while (bytes > (1 << wantwidth)) { wantwidth += 1 } - var blockaddr, blockwidth, blockoffset + let blockaddr + let blockwidth + let blockoffset if (this.offsets[blocknum]) { blockaddr = this.offsets[blocknum] blockwidth = blockaddr & 0x1F diff --git a/lib/ds-store.js b/lib/ds-store.js index 0795e20..f82edb9 100644 --- a/lib/ds-store.js +++ b/lib/ds-store.js @@ -1,11 +1,15 @@ -var fs = require('fs') -var path = require('path') +'use strict' + +const fs = require('node:fs/promises') +const path = require('node:path') // var assert = require('assert') -var Entry = require('./entry') +const Entry = require('./entry') // var partition = require('./partition') // var BuddyAllocator = require('./buddy-allocator') +const CLEAN_STORE_PATH = path.join(__dirname, '..', 'assets', 'DSStore-clean') + function DSStore () { this.entries = [] // this.store = new BuddyAllocator() @@ -260,37 +264,43 @@ DSStore.prototype.push = function (entry) { // } DSStore.prototype.write = function (filePath, cb) { - var entries = this.entries.sort(Entry.sort) - - fs.readFile(path.join(__dirname, '/../assets/DSStore-clean'), function (err, buf) { - if (err) return cb(err) + const promise = writeStore(this, filePath) - var modified = new Buffer(3840) + if (typeof cb === 'function') { + promise.then(function () { + cb(null) + }, cb) + return + } - modified.fill(0) + return promise +} - var currentPos = 0 +async function writeStore (store, filePath) { + const entries = store.entries.sort(Entry.sort) - var P = 0 - var count = entries.length + const buf = await fs.readFile(CLEAN_STORE_PATH) + const modified = Buffer.alloc(3840) - modified.writeUInt32BE(P, currentPos) - modified.writeUInt32BE(count, currentPos + 4) - currentPos += 8 + let currentPos = 0 - entries.forEach(function (e, i) { - var b = e.buffer - b.copy(modified, currentPos) - currentPos += b.length - }) + const P = 0 + const count = entries.length - buf.writeUInt32BE(entries.length, 76) - modified.copy(buf, 4100) + modified.writeUInt32BE(P, currentPos) + modified.writeUInt32BE(count, currentPos + 4) + currentPos += 8 - fs.writeFile(filePath, buf, function (err) { - cb(err) - }) + entries.forEach(function (entry) { + const entryBuffer = entry.buffer + entryBuffer.copy(modified, currentPos) + currentPos += entryBuffer.length }) + + buf.writeUInt32BE(entries.length, 76) + modified.copy(buf, 4100) + + await fs.writeFile(filePath, buf) } module.exports = exports = DSStore diff --git a/lib/entry.js b/lib/entry.js index 482025f..54351e8 100644 --- a/lib/entry.js +++ b/lib/entry.js @@ -1,11 +1,13 @@ -var tn1150 = require('tn1150') -var bplist = require('bplist-creator') +'use strict' + +const tn1150 = require('@appdmg/tn1150') +const bplist = require('@appdmg/bplist-creator') function utf16be (str) { - var b = new Buffer(str, 'ucs2') + const b = Buffer.from(str, 'ucs2') - for (var i = 0; i < b.length; i += 2) { - var a = b[i] + for (let i = 0; i < b.length; i += 2) { + const a = b[i] b[i] = b[i + 1] b[i + 1] = a } @@ -17,10 +19,10 @@ function Entry (filename, structureId, dataType, blob) { this.filename = tn1150.normalize(filename) this.structureId = structureId - var filenameLength = this.filename.length - var filenameBytes = filenameLength * 2 + const filenameLength = this.filename.length + const filenameBytes = filenameLength * 2 - this.buffer = new Buffer(4 + filenameBytes + 4 + 4 + blob.length) + this.buffer = Buffer.alloc(4 + filenameBytes + 4 + 4 + blob.length) this.buffer.writeUInt32BE(filenameLength, 0) utf16be(this.filename).copy(this.buffer, 4) @@ -31,19 +33,22 @@ function Entry (filename, structureId, dataType, blob) { } Entry.prototype.length = function () { - return this.buffer.length() + return this.buffer.length } Entry.sort = function (a, b) { - var s1 = tn1150.compare(a.filename, b.filename) - var s2 = a.structureId.localeCompare(b.structureId) + const s1 = tn1150.compare(a.filename, b.filename) + const s2 = a.structureId.localeCompare(b.structureId) return s1 || s2 } Entry.construct = function (filename, structureId, opts) { - var dataType, blob + opts = opts || {} + + let dataType + let blob - var opt = function (key, def) { + const opt = function (key, def) { if (key in opts) { return opts[key] } else if (def === undefined) { @@ -57,7 +62,7 @@ Entry.construct = function (filename, structureId, opts) { case 'BKGD': dataType = 'blob' - blob = new Buffer(12 + 4) + blob = Buffer.alloc(12 + 4) blob.writeUInt32BE(blob.length - 4, 0) if (opts.color) { @@ -74,7 +79,7 @@ Entry.construct = function (filename, structureId, opts) { case 'Iloc': dataType = 'blob' - blob = new Buffer(16 + 4) + blob = Buffer.alloc(16 + 4) blob.writeUInt32BE(blob.length - 4, 0) blob.writeUInt32BE(opts.x, 4) @@ -126,7 +131,7 @@ Entry.construct = function (filename, structureId, opts) { break case 'icvp': - var plistObj = { + const plistObj = { backgroundType: 1, backgroundColorRed: new bplist.Real(1), backgroundColorGreen: new bplist.Real(1), @@ -161,7 +166,7 @@ Entry.construct = function (filename, structureId, opts) { case 'vSrn': dataType = 'long' - blob = new Buffer(4) + blob = Buffer.alloc(4) blob.writeUInt32BE(opt('value'), 0) @@ -172,9 +177,9 @@ Entry.construct = function (filename, structureId, opts) { if (dataType === 'bplist') { dataType = 'blob' - var buf = blob + const buf = blob - blob = new Buffer(buf.length + 4) + blob = Buffer.alloc(buf.length + 4) blob.writeUInt32BE(buf.length, 0) buf.copy(blob, 4) } diff --git a/lib/partition.js b/lib/partition.js index 186ade5..9c871f8 100644 --- a/lib/partition.js +++ b/lib/partition.js @@ -1,21 +1,23 @@ -var assert = require('assert') +'use strict' + +const assert = require('node:assert') exports.sizes = function (max, sizes) { assert(Array.isArray(sizes)) - var sum = sizes.reduce(function (p, c) { + const sum = sizes.reduce(function (p, c) { return p + c }, 0) assert(sum > max) - var ejecta = [] - var bcount = Math.ceil(sum, max) - var target = sum / bcount + const ejecta = [] + const bcount = Math.ceil(sum / max) + const target = sum / bcount while (true) { - var n = 0 - var bsum = 0 + let n = 0 + let bsum = 0 while (n < sizes.length && bsum < target && (bsum + sizes[n]) < max) { bsum += sizes[n] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cf82bee --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1780 @@ +{ + "name": "@appdmg/ds-store", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@appdmg/ds-store", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@appdmg/bplist-creator": "~1.0.0", + "@appdmg/macos-alias": "~3.0.0", + "@appdmg/tn1150": "~1.0.0" + }, + "devDependencies": { + "ava": "~8.0.0" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@appdmg/bplist-creator": { + "version": "1.0.0", + "resolved": "https://github.com/appdmg/bplist-creator/archive/fd9cbe2651753cbed17726bfdf5c434936c2b1f6.tar.gz", + "integrity": "sha512-L1hC8OHFYhcAXangtA4czcLX841+MzM8Z+48SXkReQvhHa62SPLU5YeF6fG/dvdQYKZP4xsJ54h6GrL6guPDaA==", + "license": "MIT", + "engines": { + "node": ">=24" + } + }, + "node_modules/@appdmg/macos-alias": { + "version": "3.0.0", + "resolved": "https://github.com/appdmg/macos-alias/archive/b8da50fff1de66d63d1a4b1eb7afa740421bed34.tar.gz", + "integrity": "sha512-sZ3iuNsuf0ahFAaYsaEsMbmZj9mH5JfHqwOqVu7TQ1wcCwGOuhp6M6OvF7nZ5+NWHOBZcHWdfixx9IN8WOreJg==", + "license": "MIT", + "dependencies": { + "nan": "~2.26.2" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@appdmg/tn1150": { + "version": "1.0.0", + "resolved": "https://github.com/appdmg/tn1150/archive/be61d362b3568e571c426d57029da0a9c8fd8c4d.tar.gz", + "integrity": "sha512-W4b95Y7VsxWI8eSWo0I+Tf2bkwyrZE8mWlPpmHuRn9b8XddCp5soloE6gdWMNaWmlC7h2nWe+lL1gydzp2zPDQ==", + "license": "MIT", + "engines": { + "node": ">=24" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/nft": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^2.0.0", + "@rollup/pluginutils": "^5.1.3", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^13.0.0", + "graceful-fs": "^4.2.9", + "node-gyp-build": "^4.2.2", + "picomatch": "^4.0.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@vercel/nft/node_modules/acorn": { + "version": "8.16.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@vercel/nft/node_modules/acorn-import-attributes": { + "version": "1.9.5", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/@vercel/nft/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vercel/nft/node_modules/brace-expansion": { + "version": "5.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vercel/nft/node_modules/glob": { + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vercel/nft/node_modules/minimatch": { + "version": "10.2.5", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vercel/nft/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/abbrev": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk/node_modules/acorn": { + "version": "8.16.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrgv": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/arrify": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/async-sema": { + "version": "3.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/ava": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vercel/nft": "^1.5.0", + "acorn": "^8.16.0", + "acorn-walk": "^8.3.5", + "ansi-styles": "^6.2.3", + "arrgv": "^1.0.2", + "arrify": "^3.0.0", + "callsites": "^4.2.0", + "cbor": "^10.0.12", + "chalk": "^5.6.2", + "chunkd": "^2.0.1", + "ci-info": "^4.4.0", + "ci-parallel-vars": "^1.0.1", + "cli-truncate": "^6.0.0", + "code-excerpt": "^4.0.0", + "common-path-prefix": "^3.0.0", + "concordance": "^5.0.4", + "currently-unhandled": "^0.4.1", + "debug": "^4.4.3", + "emittery": "^2.0.0", + "figures": "^6.1.0", + "globby": "^16.2.0", + "ignore-by-default": "^2.1.0", + "indent-string": "^5.0.0", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "matcher": "^6.0.0", + "memoize": "^11.0.0", + "ms": "^2.1.3", + "p-map": "^7.0.4", + "package-config": "^5.0.0", + "picomatch": "^4.0.4", + "plur": "^6.0.0", + "pretty-ms": "^9.3.0", + "resolve-cwd": "^3.0.0", + "slash": "^5.1.0", + "stack-utils": "^2.0.6", + "supertap": "^3.0.1", + "temp-dir": "^3.0.0", + "write-file-atomic": "^7.0.1", + "yargs": "^18.0.0" + }, + "bin": { + "ava": "entrypoints/cli.js" + }, + "engines": { + "node": "^22.20 || ^24.12 || >=25" + }, + "peerDependencies": { + "@ava/typescript": "*" + }, + "peerDependenciesMeta": { + "@ava/typescript": { + "optional": true + } + } + }, + "node_modules/ava/node_modules/acorn": { + "version": "8.16.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ava/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ava/node_modules/callsites": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ava/node_modules/chalk": { + "version": "5.6.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/blueimp-md5": { + "version": "2.19.0", + "dev": true, + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cbor": { + "version": "10.0.12", + "dev": true, + "license": "MIT", + "dependencies": { + "nofilter": "^3.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chunkd": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ci-parallel-vars": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^9.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/concordance": { + "version": "5.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "date-time": "^3.1.0", + "esutils": "^2.0.3", + "fast-diff": "^1.2.0", + "js-string-escape": "^1.0.1", + "lodash": "^4.17.15", + "md5-hex": "^3.0.1", + "semver": "^7.3.2", + "well-known-symbols": "^2.0.0" + }, + "engines": { + "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/date-time": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "time-zone": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emittery": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "16.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore-by-default": { + "version": "2.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10 <11 || >=12 <13 || >=14" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/irregular-plurals": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-string-escape": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.3.5", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/matcher": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/md5-hex": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "blueimp-md5": "^2.10.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/memoize": { + "version": "11.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.1" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.26.2", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nofilter": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.19" + } + }, + "node_modules/nopt": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-config": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "load-json-file": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-config/node_modules/load-json-file": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plur": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "irregular-plurals": "^4.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supertap": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^5.0.0", + "js-yaml": "^3.14.1", + "serialize-error": "^7.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/supertap/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/supertap/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/time-zone": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/well-known-symbols": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/write-file-atomic": { + "version": "7.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "18.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + } + } +} diff --git a/package.json b/package.json index 2c212e3..854128b 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,26 @@ { - "name": "ds-store", - "version": "0.1.6", + "name": "@appdmg/ds-store", + "version": "1.0.0", + "description": ".DS_Store writer for appdmg packages.", "license": "MIT", "main": "index.js", + "types": "index.d.ts", "author": "Linus Unnebäck ", "scripts": { - "test": "standard" + "test": "ava" }, + "files": [ + "assets", + "index.js", + "index.d.ts", + "lib", + "README.md", + "LICENSE" + ], "dependencies": { - "bplist-creator": "~0.0.3", - "macos-alias": "~0.2.5", - "tn1150": "^0.1.0" + "@appdmg/bplist-creator": "~1.0.0", + "@appdmg/macos-alias": "~3.0.0", + "@appdmg/tn1150": "~1.0.0" }, "contributors": [ "Linus Unnebäck ", @@ -18,9 +28,26 @@ ], "repository": { "type": "git", - "url": "http://github.com/LinusU/node-ds-store.git" + "url": "git+https://github.com/appdmg/ds-store.git" + }, + "bugs": { + "url": "https://github.com/appdmg/ds-store/issues" + }, + "homepage": "https://github.com/appdmg/ds-store#readme", + "keywords": [ + "ds-store", + "finder", + "dmg", + "appdmg" + ], + "engines": { + "node": ">=24" }, "devDependencies": { - "standard": "^7.0.1" + "ava": "~8.0.0" + }, + "overrides": { + "js-yaml": "~3.14.2", + "lodash": "~4.18.1" } } diff --git a/test/ds-store.js b/test/ds-store.js new file mode 100644 index 0000000..a202a67 --- /dev/null +++ b/test/ds-store.js @@ -0,0 +1,179 @@ +'use strict' + +const crypto = require('node:crypto') +const fs = require('node:fs/promises') +const os = require('node:os') +const path = require('node:path') +const test = require('ava').default + +const DSStore = require('../') + +const GENERATED_COLOR_SHA256 = '8df681c341464ee89409056b62c892e2fd9f45f03ac9e35a6e681a3b743ff963' + +test('writes appdmg Finder metadata as stable DS_Store bytes', async (t) => { + const filePath = await createTempPath(t) + const store = createSampleStore() + + await store.write(filePath) + + const buf = await fs.readFile(filePath) + const entries = readEntries(buf) + + t.is(buf.length, 15364) + t.is(buf.readUInt32BE(76), 5) + t.is(sha256(buf), GENERATED_COLOR_SHA256) + t.deepEqual(entries.map((entry) => [entry.filename, entry.structureId, entry.dataType]), [ + ['.', 'bwsp', 'blob'], + ['.', 'icvp', 'blob'], + ['.', 'vSrn', 'long'], + ['Applications', 'Iloc', 'blob'], + ['My App.app', 'Iloc', 'blob'] + ]) + + const appIcon = entries.find((entry) => entry.filename === 'My App.app') + t.is(appIcon.blob.readUInt32BE(0), 16) + t.is(appIcon.blob.readUInt32BE(4), 140) + t.is(appIcon.blob.readUInt32BE(8), 120) + t.deepEqual(appIcon.blob.subarray(12, 16), Buffer.from('ffffff00', 'hex')) +}) + +test('write keeps the callback API for existing callers', async (t) => { + const filePath = await createTempPath(t) + const store = createSampleStore() + + const callbackFinished = new Promise((resolve, reject) => { + const result = store.write(filePath, (err) => { + if (err) { + reject(err) + return + } + + resolve(result) + }) + + t.is(result, undefined) + }) + + await callbackFinished + await eventually(async () => { + t.is((await fs.stat(filePath)).isFile(), true) + }) +}) + +test('background image aliases can be written with explicit non-Darwin volume metadata', async (t) => { + const tmpDir = await createTempDir(t) + const backgroundPath = path.join(tmpDir, 'background.png') + const storePath = path.join(tmpDir, '.DS_Store') + + await fs.writeFile(backgroundPath, Buffer.from('not a real png')) + + const store = new DSStore() + store.setIconSize(96) + store.setWindowPos(20, 30) + store.setWindowSize(400, 300) + store.setBackgroundPath(backgroundPath, { volumeName: 'Test Volume' }) + + await store.write(storePath) + + const entries = readEntries(await fs.readFile(storePath)) + const viewOptions = entries.find((entry) => entry.filename === '.' && entry.structureId === 'icvp') + + t.truthy(viewOptions) + t.is(viewOptions.blob.subarray(4, 12).toString('ascii'), 'bplist00') +}) + +function createSampleStore () { + const store = new DSStore() + + store.setIconSize(128) + store.setWindowPos(100, 120) + store.setWindowSize(640, 480) + store.setIconPos('My App.app', 140, 120) + store.setIconPos('Applications', 420, 120) + store.setBackgroundColor(1, 0.5, 0) + store.vSrn(1) + + return store +} + +async function createTempDir (t) { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ds-store-')) + + t.teardown(async () => { + await fs.rm(tmpDir, { recursive: true, force: true }) + }) + + return tmpDir +} + +async function createTempPath (t) { + return path.join(await createTempDir(t), '.DS_Store') +} + +function readEntries (buf) { + const count = buf.readUInt32BE(4104) + const entries = [] + let offset = 4108 + + for (let i = 0; i < count; i += 1) { + const filenameLength = buf.readUInt32BE(offset) + offset += 4 + + const filename = readUtf16BE(buf.subarray(offset, offset + (filenameLength * 2))) + offset += filenameLength * 2 + + const structureId = buf.subarray(offset, offset + 4).toString('ascii') + offset += 4 + + const dataType = buf.subarray(offset, offset + 4).toString('ascii') + offset += 4 + + let blob + if (dataType === 'blob') { + const blobLength = buf.readUInt32BE(offset) + blob = buf.subarray(offset, offset + 4 + blobLength) + offset += 4 + blobLength + } else if (dataType === 'long') { + blob = buf.subarray(offset, offset + 4) + offset += 4 + } else { + throw new Error('Unsupported test data type: ' + dataType) + } + + entries.push({ filename, structureId, dataType, blob }) + } + + return entries +} + +function readUtf16BE (buf) { + const littleEndian = Buffer.from(buf) + + for (let i = 0; i < littleEndian.length; i += 2) { + const value = littleEndian[i] + littleEndian[i] = littleEndian[i + 1] + littleEndian[i + 1] = value + } + + return littleEndian.toString('ucs2') +} + +function sha256 (buf) { + return crypto.createHash('sha256').update(buf).digest('hex') +} + +async function eventually (assertion) { + let lastError + + for (let i = 0; i < 20; i += 1) { + try { + await assertion() + return + } catch (err) { + lastError = err + await new Promise((resolve) => setTimeout(resolve, 10)) + } + } + + throw lastError +} diff --git a/test/entry.js b/test/entry.js new file mode 100644 index 0000000..3dcc425 --- /dev/null +++ b/test/entry.js @@ -0,0 +1,43 @@ +'use strict' + +const test = require('ava').default + +const Entry = require('../lib/entry') + +test('constructs icon location records', (t) => { + const entry = Entry.construct('My App.app', 'Iloc', { x: 140, y: 120 }) + + t.is(entry.filename, 'My App.app') + t.is(entry.structureId, 'Iloc') + t.is(entry.length(), entry.buffer.length) + + const filenameLength = entry.buffer.readUInt32BE(0) + const recordOffset = 4 + (filenameLength * 2) + + t.is(entry.buffer.subarray(recordOffset, recordOffset + 4).toString('ascii'), 'Iloc') + t.is(entry.buffer.subarray(recordOffset + 4, recordOffset + 8).toString('ascii'), 'blob') + t.is(entry.buffer.readUInt32BE(recordOffset + 8), 16) + t.is(entry.buffer.readUInt32BE(recordOffset + 12), 140) + t.is(entry.buffer.readUInt32BE(recordOffset + 16), 120) +}) + +test('wraps binary plist records as Finder blobs', (t) => { + const entry = Entry.construct('.', 'bwsp', { + x: 100, + y: 120, + width: 640, + height: 502 + }) + + const blobOffset = 4 + (entry.filename.length * 2) + 8 + const blobLength = entry.buffer.readUInt32BE(blobOffset) + + t.is(entry.buffer.subarray(blobOffset + 4, blobOffset + 12).toString('ascii'), 'bplist00') + t.is(blobLength, entry.buffer.length - blobOffset - 4) +}) + +test('fails early for missing required record options', (t) => { + const err = t.throws(() => Entry.construct('.', 'vSrn', {}), { instanceOf: TypeError }) + + t.is(err.message, 'Missing option: value') +})