sdk improvements (#2877)

This commit is contained in:
Aiden McClelland
2025-04-16 12:53:10 -06:00
committed by GitHub
parent 89f3fdc05f
commit 47b6509f70
11 changed files with 207 additions and 144 deletions

View File

@@ -7,15 +7,14 @@
"name": "@start9labs/start-sdk-base",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"@iarna/toml": "^3.0.0",
"@noble/curves": "^1.8.2",
"@noble/hashes": "^1.7.2",
"deep-equality-data-structures": "^1.5.0",
"isomorphic-fetch": "^3.0.0",
"lodash.merge": "^4.6.2",
"mime-types": "^2.1.35",
"mime-types": "^3.0.1",
"ts-matches": "^6.2.1",
"yaml": "^2.2.2"
"yaml": "^2.7.1"
},
"devDependencies": {
"@types/jest": "^29.4.0",
@@ -964,9 +963,9 @@
}
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz",
"integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==",
"license": "ISC"
},
"node_modules/@istanbuljs/load-nyc-config": {
@@ -1342,12 +1341,12 @@
}
},
"node_modules/@noble/curves": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz",
"integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz",
"integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.6.0"
"@noble/hashes": "1.7.2"
},
"engines": {
"node": "^14.21.3 || >=16"
@@ -1356,22 +1355,10 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz",
"integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz",
"integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz",
"integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
@@ -3696,12 +3683,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3790,21 +3771,21 @@
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
@@ -4970,9 +4951,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"

View File

@@ -21,14 +21,13 @@
},
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"@iarna/toml": "^3.0.0",
"@noble/curves": "^1.8.2",
"@noble/hashes": "^1.7.2",
"isomorphic-fetch": "^3.0.0",
"lodash.merge": "^4.6.2",
"mime-types": "^2.1.35",
"mime-types": "^3.0.1",
"ts-matches": "^6.2.1",
"yaml": "^2.2.2",
"yaml": "^2.7.1",
"deep-equality-data-structures": "^1.5.0"
},
"prettier": {

View File

@@ -1,6 +1,7 @@
import * as T from "../../../base/lib/types"
import * as child_process from "child_process"
import { asError } from "../util"
import * as fs from "fs/promises"
import { asError, StorePath } from "../util"
export const DEFAULT_OPTIONS: T.SyncOptions = {
delete: true,
@@ -91,7 +92,7 @@ export class Backups<M extends T.SDKManifest> {
return this
}
async createBackup() {
async createBackup(effects: T.Effects) {
for (const item of this.backupSet) {
const rsyncResults = await runRsync({
srcPath: item.dataPath,
@@ -105,10 +106,30 @@ export class Backups<M extends T.SDKManifest> {
})
await rsyncResults.wait()
}
await fs.writeFile(
"/media/startos/backup/store.json",
JSON.stringify(await effects.store.get({ path: "" as StorePath })),
{ encoding: "utf-8" },
)
const dataVersion = await effects.getDataVersion()
if (dataVersion)
await fs.writeFile("/media/startos/backup/dataVersion.txt", dataVersion, {
encoding: "utf-8",
})
return
}
async restoreBackup() {
async restoreBackup(effects: T.Effects) {
const store = await fs
.readFile("/media/startos/backup/store.json", {
encoding: "utf-8",
})
.catch((_) => null)
if (store)
await effects.store.set({
path: "" as StorePath,
value: JSON.parse(store),
})
for (const item of this.backupSet) {
const rsyncResults = await runRsync({
srcPath: item.backupPath,
@@ -121,6 +142,12 @@ export class Backups<M extends T.SDKManifest> {
},
})
await rsyncResults.wait()
const dataVersion = await fs
.readFile("/media/startos/backup/dataVersion.txt", {
encoding: "utf-8",
})
.catch((_) => null)
if (dataVersion) await effects.setDataVersion({ version: dataVersion })
}
return
}

View File

@@ -26,12 +26,12 @@ export function setupBackups<M extends T.SDKManifest>(
} = {
get createBackup() {
return (async (options) => {
return (await backupsFactory(options)).createBackup()
return (await backupsFactory(options)).createBackup(options.effects)
}) as T.ExpectedExports.createBackup
},
get restoreBackup() {
return (async (options) => {
return (await backupsFactory(options)).restoreBackup()
return (await backupsFactory(options)).restoreBackup(options.effects)
}) as T.ExpectedExports.restoreBackup
},
}

View File

@@ -1,6 +1,7 @@
import * as matches from "ts-matches"
import * as YAML from "yaml"
import * as TOML from "@iarna/toml"
import * as INI from "ini"
import * as T from "../../../base/lib/types"
import * as fs from "node:fs/promises"
import { asError, partialDiff } from "../../../base/lib/util"
@@ -285,6 +286,7 @@ export class FileHelper<A> {
) {
return new FileHelper<A>(path, toFile, fromFile, validate)
}
/**
* Create a File Helper for a .json file.
*/
@@ -296,6 +298,7 @@ export class FileHelper<A> {
(data) => shape.unsafeCast(data),
)
}
/**
* Create a File Helper for a .toml file
*/
@@ -310,6 +313,7 @@ export class FileHelper<A> {
(data) => shape.unsafeCast(data),
)
}
/**
* Create a File Helper for a .yaml file
*/
@@ -324,6 +328,41 @@ export class FileHelper<A> {
(data) => shape.unsafeCast(data),
)
}
static ini<A extends Record<string, unknown>>(
path: string,
shape: matches.Validator<unknown, A>,
options?: INI.EncodeOptions & INI.DecodeOptions,
) {
return new FileHelper<A>(
path,
(inData) => INI.stringify(inData, options),
(inString) => INI.parse(inString, options),
(data) => shape.unsafeCast(data),
)
}
static env<A extends Record<string, string>>(
path: string,
shape: matches.Validator<unknown, A>,
) {
return new FileHelper<A>(
path,
(inData) =>
Object.entries(inData)
.map(([k, v]) => `${k}=${v}`)
.join("\n"),
(inString) =>
Object.fromEntries(
inString
.split("\n")
.map((line) => line.trim())
.filter((line) => !line.startsWith("#") && line.includes("="))
.map((line) => line.split("=", 2)),
),
(data) => shape.unsafeCast(data),
)
}
}
export default FileHelper

View File

@@ -1,28 +1,28 @@
{
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.2",
"version": "0.4.0-beta.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.2",
"version": "0.4.0-beta.3",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"deep-equality-data-structures": "^1.5.1",
"@iarna/toml": "^3.0.0",
"@noble/curves": "^1.8.2",
"@noble/hashes": "^1.7.2",
"deep-equality-data-structures": "^2.0.0",
"ini": "^5.0.0",
"isomorphic-fetch": "^3.0.0",
"lodash.merge": "^4.6.2",
"mime-types": "^2.1.35",
"mime-types": "^3.0.1",
"ts-matches": "^6.2.1",
"yaml": "^2.2.2"
"yaml": "^2.7.1"
},
"devDependencies": {
"@iarna/toml": "^2.2.5",
"@types/ini": "^4.1.1",
"@types/jest": "^29.4.0",
"@types/lodash.merge": "^4.6.2",
"copyfiles": "^2.4.1",
"jest": "^29.4.3",
"peggy": "^3.0.2",
@@ -1366,12 +1366,12 @@
}
},
"node_modules/@noble/curves": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz",
"integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz",
"integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.6.0"
"@noble/hashes": "1.7.2"
},
"engines": {
"node": "^14.21.3 || >=16"
@@ -1380,22 +1380,10 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz",
"integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz",
"integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz",
"integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
@@ -1607,6 +1595,13 @@
"@types/node": "*"
}
},
"node_modules/@types/ini": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz",
"integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -1645,23 +1640,6 @@
"pretty-format": "^29.0.0"
}
},
"node_modules/@types/lodash": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash.merge": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz",
"integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "22.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz",
@@ -2289,9 +2267,9 @@
}
},
"node_modules/deep-equality-data-structures": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-1.5.1.tgz",
"integrity": "sha512-P7zsL2/AbZIGHDxbo/LLEhCp11AttRp8GvzXOXudqMT/qiGCLo/pyI4lAZvjUZyQnlIbPna3fv8DMsuRvLt4ww==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-2.0.0.tgz",
"integrity": "sha512-qgrUr7MKXq7VRN+WUpQ48QlXVGL0KdibAoTX8KRg18lgOgqbEKMAW1WZsVCtakY4+XX42pbAJzTz/DlXEFM2Fg==",
"license": "MIT",
"dependencies": {
"object-hash": "^3.0.0"
@@ -2847,6 +2825,15 @@
"dev": true,
"license": "ISC"
},
"node_modules/ini": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz",
"integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==",
"license": "ISC",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -3794,12 +3781,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3888,21 +3869,21 @@
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
@@ -5181,9 +5162,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"

View File

@@ -1,6 +1,6 @@
{
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.2",
"version": "0.4.0-beta.3",
"description": "Software development kit to facilitate packaging services for StartOS",
"main": "./package/lib/index.js",
"types": "./package/lib/index.d.ts",
@@ -32,14 +32,14 @@
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
"dependencies": {
"isomorphic-fetch": "^3.0.0",
"lodash.merge": "^4.6.2",
"mime-types": "^2.1.35",
"mime-types": "^3.0.1",
"ts-matches": "^6.2.1",
"yaml": "^2.2.2",
"deep-equality-data-structures": "^1.5.1",
"@iarna/toml": "^2.2.5",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0"
"yaml": "^2.7.1",
"deep-equality-data-structures": "^2.0.0",
"ini": "^5.0.0",
"@iarna/toml": "^3.0.0",
"@noble/curves": "^1.8.2",
"@noble/hashes": "^1.7.2"
},
"prettier": {
"trailingComma": "all",
@@ -50,7 +50,7 @@
"devDependencies": {
"@iarna/toml": "^2.2.5",
"@types/jest": "^29.4.0",
"@types/lodash.merge": "^4.6.2",
"@types/ini": "^4.1.1",
"copyfiles": "^2.4.1",
"jest": "^29.4.3",
"peggy": "^3.0.2",