Merge branch 'integration/new-container-runtime' of github.com:Start9Labs/start-os into rebase/feat/domains

This commit is contained in:
Matt Hill
2024-03-22 10:10:55 -06:00
52 changed files with 3977 additions and 4678 deletions

View File

@@ -23,14 +23,13 @@
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.65",
"@types/node": "^20.11.13",
"esbuild": "^0.20.0",
"prettier": "^3.2.5",
"typescript": ">5.2"
}
},
"../sdk/dist": {
"name": "@start9labs/start-sdk",
"version": "0.4.0-rev0.lib0.rc8.beta7",
"version": "0.4.0-rev0.lib0.rc8.beta10",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
@@ -41,380 +40,13 @@
"devDependencies": {
"@types/jest": "^29.4.0",
"jest": "^29.4.3",
"prettier": "^3.2.5",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"tsx": "^4.7.1",
"typescript": "^5.0.4"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz",
"integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz",
"integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz",
"integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz",
"integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz",
"integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz",
"integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz",
"integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz",
"integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz",
"integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz",
"integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz",
"integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz",
"integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz",
"integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz",
"integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz",
"integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz",
"integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz",
"integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz",
"integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz",
"integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz",
"integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz",
"integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz",
"integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz",
"integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
@@ -1304,44 +936,6 @@
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz",
"integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.0",
"@esbuild/android-arm": "0.20.0",
"@esbuild/android-arm64": "0.20.0",
"@esbuild/android-x64": "0.20.0",
"@esbuild/darwin-arm64": "0.20.0",
"@esbuild/darwin-x64": "0.20.0",
"@esbuild/freebsd-arm64": "0.20.0",
"@esbuild/freebsd-x64": "0.20.0",
"@esbuild/linux-arm": "0.20.0",
"@esbuild/linux-arm64": "0.20.0",
"@esbuild/linux-ia32": "0.20.0",
"@esbuild/linux-loong64": "0.20.0",
"@esbuild/linux-mips64el": "0.20.0",
"@esbuild/linux-ppc64": "0.20.0",
"@esbuild/linux-riscv64": "0.20.0",
"@esbuild/linux-s390x": "0.20.0",
"@esbuild/linux-x64": "0.20.0",
"@esbuild/netbsd-x64": "0.20.0",
"@esbuild/openbsd-x64": "0.20.0",
"@esbuild/sunos-x64": "0.20.0",
"@esbuild/win32-arm64": "0.20.0",
"@esbuild/win32-ia32": "0.20.0",
"@esbuild/win32-x64": "0.20.0"
}
},
"node_modules/esbuild-plugin-resolve": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-resolve/-/esbuild-plugin-resolve-2.0.0.tgz",
@@ -2965,167 +2559,6 @@
}
},
"dependencies": {
"@esbuild/aix-ppc64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz",
"integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==",
"dev": true,
"optional": true
},
"@esbuild/android-arm": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz",
"integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz",
"integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz",
"integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz",
"integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz",
"integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz",
"integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz",
"integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz",
"integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz",
"integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz",
"integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz",
"integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz",
"integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz",
"integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz",
"integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz",
"integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz",
"integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz",
"integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz",
"integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz",
"integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz",
"integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz",
"integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz",
"integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==",
"dev": true,
"optional": true
},
"@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
@@ -3186,6 +2619,7 @@
"@types/jest": "^29.4.0",
"isomorphic-fetch": "^3.0.0",
"jest": "^29.4.3",
"prettier": "^3.2.5",
"ts-jest": "^29.0.5",
"ts-matches": "^5.4.1",
"ts-node": "^10.9.1",
@@ -3732,37 +3166,6 @@
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
"esbuild": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz",
"integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==",
"dev": true,
"requires": {
"@esbuild/aix-ppc64": "0.20.0",
"@esbuild/android-arm": "0.20.0",
"@esbuild/android-arm64": "0.20.0",
"@esbuild/android-x64": "0.20.0",
"@esbuild/darwin-arm64": "0.20.0",
"@esbuild/darwin-x64": "0.20.0",
"@esbuild/freebsd-arm64": "0.20.0",
"@esbuild/freebsd-x64": "0.20.0",
"@esbuild/linux-arm": "0.20.0",
"@esbuild/linux-arm64": "0.20.0",
"@esbuild/linux-ia32": "0.20.0",
"@esbuild/linux-loong64": "0.20.0",
"@esbuild/linux-mips64el": "0.20.0",
"@esbuild/linux-ppc64": "0.20.0",
"@esbuild/linux-riscv64": "0.20.0",
"@esbuild/linux-s390x": "0.20.0",
"@esbuild/linux-x64": "0.20.0",
"@esbuild/netbsd-x64": "0.20.0",
"@esbuild/openbsd-x64": "0.20.0",
"@esbuild/sunos-x64": "0.20.0",
"@esbuild/win32-arm64": "0.20.0",
"@esbuild/win32-ia32": "0.20.0",
"@esbuild/win32-x64": "0.20.0"
}
},
"esbuild-plugin-resolve": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-resolve/-/esbuild-plugin-resolve-2.0.0.tgz",

View File

@@ -28,9 +28,15 @@ export class DockerProcedureContainer {
await fs.mkdir(path, { recursive: true })
const volumeMount = volumes[mount]
if (volumeMount.type === "data") {
await overlay.mount({ type: "volume", id: mount }, mounts[mount])
await overlay.mount(
{ type: "volume", id: mount, subpath: null, readonly: false },
mounts[mount],
)
} else if (volumeMount.type === "assets") {
await overlay.mount({ type: "assets", id: mount }, mounts[mount])
await overlay.mount(
{ type: "assets", id: mount, subpath: null },
mounts[mount],
)
} else if (volumeMount.type === "certificate") {
volumeMount
const certChain = await effects.getSslCertificate({
@@ -56,7 +62,7 @@ export class DockerProcedureContainer {
location: path,
target: {
packageId: volumeMount["package-id"],
path: volumeMount.path,
subpath: volumeMount.path,
readonly: volumeMount.readonly,
volumeId: volumeMount["volume-id"],
},

View File

@@ -2,8 +2,7 @@ import { PolyfillEffects } from "./polyfillEffects"
import { DockerProcedureContainer } from "./DockerProcedureContainer"
import { SystemForEmbassy } from "."
import { HostSystemStartOs } from "../../HostSystemStartOs"
import { util, Daemons, types as T } from "@start9labs/start-sdk"
import { exec } from "child_process"
import { Daemons, T, daemons } from "@start9labs/start-sdk"
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
@@ -39,7 +38,6 @@ export class MainLoop {
private async constructMainEvent() {
const { system, effects } = this
const utils = util.createUtils(effects)
const currentCommand: [string, ...string[]] = [
system.manifest.main.entrypoint,
...system.manifest.main.args,
@@ -67,7 +65,8 @@ export class MainLoop {
// }),
// }
}
const daemon = await utils.runDaemon(
const daemon = await daemons.runDaemon()(
this.effects,
this.system.manifest.main.image,
currentCommand,
{

View File

@@ -1,10 +1,10 @@
import { types as T, util, EmVer, Utils } from "@start9labs/start-sdk"
import { types as T, utils, EmVer } from "@start9labs/start-sdk"
import * as fs from "fs/promises"
import { PolyfillEffects } from "./polyfillEffects"
import { Duration, duration } from "../../../Models/Duration"
import { System } from "../../../Interfaces/System"
import { matchManifest, Manifest, Procedure } from "./matchManifest"
import { matchManifest, Manifest } from "./matchManifest"
import * as childProcess from "node:child_process"
import { DockerProcedureContainer } from "./DockerProcedureContainer"
import { promisify } from "node:util"
@@ -22,6 +22,9 @@ import {
any,
tuple,
number,
anyOf,
deferred,
Parser,
} from "ts-matches"
import { HostSystemStartOs } from "../../HostSystemStartOs"
import { JsonPath, unNestPath } from "../../../Models/JsonPath"
@@ -37,58 +40,94 @@ const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig"
const matchPackagePropertyObject = object({
value: any,
type: literal("object"),
description: string,
})
const matchPackagePropertyString = object(
{
type: literal("string"),
export type PackagePropertiesV2 = {
[name: string]: PackagePropertyObject | PackagePropertyString
}
export type PackagePropertyString = {
type: "string"
description?: string
value: string
/** Let's the ui make this copyable button */
copyable?: boolean
/** Let the ui create a qr for this field */
qr?: boolean
/** Hiding the value unless toggled off for field */
masked?: boolean
}
export type PackagePropertyObject = {
value: PackagePropertiesV2
type: "object"
description: string
}
const [matchPackageProperties, setMatchPackageProperties] =
deferred<PackagePropertiesV2>()
const matchPackagePropertyObject: Parser<unknown, PackagePropertyObject> =
object({
value: matchPackageProperties,
type: literal("object"),
description: string,
value: string,
copyable: boolean,
qr: boolean,
masked: boolean,
},
["copyable", "description", "qr", "masked"],
})
const matchPackagePropertyString: Parser<unknown, PackagePropertyString> =
object(
{
type: literal("string"),
description: string,
value: string,
copyable: boolean,
qr: boolean,
masked: boolean,
},
["copyable", "description", "qr", "masked"],
)
setMatchPackageProperties(
dictionary([
string,
anyOf(matchPackagePropertyObject, matchPackagePropertyString),
]),
)
const matchProperties = object({
version: literal(2),
data: any,
data: matchPackageProperties,
})
type ExportUi = {
value: string
title: string
description?: string | undefined
masked?: boolean | undefined
copyable?: boolean | undefined
qr?: boolean | undefined
values: { [key: string]: any }
expose: { [key: string]: T.ExposeUiPathsAll }
}
function propertiesToExportUi(properties: unknown): ExportUi[] {
if (!object.test(properties)) return []
const paths: ExportUi[] = []
for (const key in properties) {
const value: unknown = (properties as any)[key]
if (matchPackagePropertyObject.test(value)) {
paths.push(...propertiesToExportUi(value))
function propertiesToExportUi(
properties: PackagePropertiesV2,
previousPath = "",
): ExportUi {
const exportUi: ExportUi = {
values: {},
expose: {},
}
for (const [key, value] of Object.entries(properties)) {
const path = `${previousPath}/${key}`
if (value.type === "object") {
const { values, expose } = propertiesToExportUi(value.value, path)
exportUi.values[key] = values
exportUi.expose[key] = {
type: "object",
value: expose,
description: value.description,
}
continue
}
if (!matchPackagePropertyString.test(value)) continue
paths.push({
value: value.value,
title: key,
description: value.description,
masked: value.masked,
copyable: value.copyable,
qr: value.qr,
})
exportUi.values[key] = value.value
exportUi.expose[key] = {
type: "string",
path,
description: value.description ?? null,
masked: value.masked ?? false,
copyable: value.copyable ?? null,
qr: value.qr ?? null,
}
}
return paths
return exportUi
}
export class SystemForEmbassy implements System {
@@ -455,14 +494,9 @@ export class SystemForEmbassy implements System {
const exposeUis = propertiesToExportUi(properties.data)
await effects.store.set<any, any>({
path: "/properties",
value: exposeUis.map((x) => x.value),
})
await effects.exposeUi({
paths: exposeUis.map((x, i) => ({
...x,
path: `/properties/${i}`,
})) as any[],
value: exposeUis.values,
})
await effects.exposeUi(exposeUis.expose)
} else if (setConfigValue.type === "script") {
const moduleCode = this.moduleCode
const method = moduleCode.properties
@@ -479,14 +513,9 @@ export class SystemForEmbassy implements System {
const exposeUis = propertiesToExportUi(properties.data)
await effects.store.set<any, any>({
path: "/properties",
value: exposeUis.map((x) => x.value),
})
await effects.exposeUi({
paths: exposeUis.map((x, i) => ({
...x,
path: `/properties/${i}`,
})) as any[],
value: exposeUis.values,
})
await effects.exposeUi(exposeUis.expose)
}
}
private async health(
@@ -991,7 +1020,6 @@ async function updateConfig(
) {
if (!dictionary([string, unknown]).test(spec)) return
if (!dictionary([string, unknown]).test(mutConfigValue)) return
const utils = util.createUtils(effects)
for (const key in spec) {
const specValue = spec[key]
@@ -1020,8 +1048,8 @@ async function updateConfig(
if (matchPointerPackage.test(specValue)) {
if (specValue.target === "tor-key")
throw new Error("This service uses an unsupported target TorKey")
const filled = await utils.serviceInterface
.get({
const filled = await utils
.getServiceInterface(effects, {
packageId: specValue["package-id"],
id: specValue.interface,
})

View File

@@ -3,23 +3,18 @@ import * as oet from "./oldEmbassyTypes"
import { Volume } from "../../../Models/Volume"
import * as child_process from "child_process"
import { promisify } from "util"
import { util, Utils } from "@start9labs/start-sdk"
import { Manifest } from "./matchManifest"
import { startSdk } from "@start9labs/start-sdk"
import { HostSystemStartOs } from "../../HostSystemStartOs"
import "isomorphic-fetch"
const { createUtils } = util
import { Manifest } from "./matchManifest"
const execFile = promisify(child_process.execFile)
export class PolyfillEffects implements oet.Effects {
private utils: Utils<any, any>
constructor(
readonly effects: HostSystemStartOs,
private manifest: Manifest,
) {
this.utils = createUtils(effects as any)
}
) {}
async writeFile(input: {
path: string
volumeId: string
@@ -99,9 +94,14 @@ export class PolyfillEffects implements oet.Effects {
args?: string[] | undefined
timeoutMillis?: number | undefined
}): Promise<oet.ResultType<string>> {
return this.utils
.runCommand(this.manifest.main.image, [command, ...(args || [])], {})
.then((x) => ({
return startSdk
.runCommand(
this.effects,
this.manifest.main.image,
[command, ...(args || [])],
{},
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))

View File

@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AllowedStatuses } from "./AllowedStatuses";
export interface ActionMetadata { name: string, description: string, warning: string | null, disabled: boolean, input: {[key: string]: any}, allowedStatuses: AllowedStatuses, group: string | null, }

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type AllowedStatuses = "only-running" | "only-stopped" | "any" | "disabled";
export type AllowedStatuses = "only-running" | "only-stopped" | "any";

View File

@@ -1,4 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DependencyKind } from "./DependencyKind";
export interface DependencyRequirement { id: string, kind: DependencyKind, healthChecks: string[], }
export type DependencyRequirement = { "kind": "running", id: string, healthChecks: string[], versionSpec: string, url: string, } | { "kind": "exists", id: string, versionSpec: string, url: string, };

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AllowedStatuses } from "./AllowedStatuses";
import type { ActionMetadata } from "./ActionMetadata";
export interface ExportActionParams { name: string, description: string, id: string, input: {[key: string]: any}, allowedStatuses: AllowedStatuses, group: string | null, }
export interface ExportActionParams { id: string, metadata: ActionMetadata, }

View File

@@ -412,7 +412,7 @@ pub struct StaticDependencyInfo {
pub icon: DataUrl<'static>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "kind")]
pub enum CurrentDependencyInfo {

View File

@@ -385,6 +385,10 @@ impl NetService {
))
}
}
pub fn get_ip(&self) -> Ipv4Addr {
self.ip.to_owned()
}
}
impl Drop for NetService {

View File

@@ -1,5 +1,6 @@
use std::collections::BTreeSet;
use std::ffi::OsString;
use std::net::Ipv4Addr;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
@@ -259,16 +260,24 @@ enum AllowedStatuses {
OnlyRunning,
OnlyStopped,
Any,
Disabled,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ExportActionParams {
#[ts(type = "string")]
id: ActionId,
metadata: ActionMetadata,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
struct ActionMetadata {
name: String,
description: String,
id: String,
warning: Option<String>,
disabled: bool,
#[ts(type = "{[key: string]: any}")]
input: Value,
allowed_statuses: AllowedStatuses,
@@ -335,8 +344,17 @@ async fn get_system_smtp(
) -> Result<Value, Error> {
todo!()
}
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Value, Error> {
todo!()
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr, Error> {
match context.0.upgrade() {
Some(c) => {
let net_service = c.persistent_container.net_service.lock().await;
Ok(net_service.get_ip())
}
None => Err(Error::new(
eyre!("Upgrade on Weak<ServiceActorSeed> resulted in a None variant"),
crate::ErrorKind::NotFound,
)),
}
}
async fn get_service_port_forward(
context: EffectContext,
@@ -764,8 +782,8 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result<Value, Error
let package = peeked
.as_public()
.as_package_data()
.as_idx(&package_id)
.or_not_found(&package_id)?
.as_idx(package_id)
.or_not_found(package_id)?
.as_status()
.as_configured()
.de()?;
@@ -1069,28 +1087,39 @@ enum DependencyKind {
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", tag = "kind")]
#[ts(export)]
struct DependencyRequirement {
#[ts(type = "string")]
id: PackageId,
kind: DependencyKind,
#[serde(default)]
#[ts(type = "string[]")]
health_checks: BTreeSet<HealthCheckId>,
enum DependencyRequirement {
Running {
#[ts(type = "string")]
id: PackageId,
#[ts(type = "string[]")]
#[serde(rename = "healthChecks")]
health_checks: BTreeSet<HealthCheckId>,
#[serde(rename = "versionSpec")]
version_spec: String,
url: String,
},
Exists {
#[ts(type = "string")]
id: PackageId,
#[serde(rename = "versionSpec")]
version_spec: String,
url: String,
},
}
// filebrowser:exists,bitcoind:running:foo+bar+baz
impl FromStr for DependencyRequirement {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once(':') {
Some((id, "e")) | Some((id, "exists")) => Ok(Self {
Some((id, "e")) | Some((id, "exists")) => Ok(Self::Exists {
id: id.parse()?,
kind: DependencyKind::Exists,
health_checks: BTreeSet::new(),
url: "".to_string(),
version_spec: "*".to_string(),
}),
Some((id, rest)) => {
let health_checks = match rest.split_once(":") {
let health_checks = match rest.split_once(':') {
Some(("r", rest)) | Some(("running", rest)) => rest
.split('+')
.map(|id| id.parse().map_err(Error::from))
@@ -1107,16 +1136,18 @@ impl FromStr for DependencyRequirement {
)),
},
}?;
Ok(Self {
Ok(Self::Running {
id: id.parse()?,
kind: DependencyKind::Running,
health_checks,
url: "".to_string(),
version_spec: "*".to_string(),
})
}
None => Ok(Self {
None => Ok(Self::Running {
id: s.parse()?,
kind: DependencyKind::Running,
health_checks: BTreeSet::new(),
url: "".to_string(),
version_spec: "*".to_string(),
}),
}
}
@@ -1148,23 +1179,19 @@ async fn set_dependencies(
let dependencies = CurrentDependencies(
dependencies
.into_iter()
.map(
|DependencyRequirement {
id,
kind,
health_checks,
}| {
(
id,
match kind {
DependencyKind::Exists => CurrentDependencyInfo::Exists,
DependencyKind::Running => {
CurrentDependencyInfo::Running { health_checks }
}
},
)
},
)
.map(|dependency| match dependency {
DependencyRequirement::Exists {
id,
url,
version_spec,
} => (id, CurrentDependencyInfo::Exists),
DependencyRequirement::Running {
id,
health_checks,
url,
version_spec,
} => (id, CurrentDependencyInfo::Running { health_checks }),
})
.collect(),
);
for (dep, entry) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {

18
sdk/lib/Dependency.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Checker } from "./emverLite/mod"
export class Dependency {
constructor(
readonly data:
| {
type: "running"
versionSpec: Checker
url: string
healthChecks: string[]
}
| {
type: "exists"
versionSpec: Checker
url: string
},
) {}
}

View File

@@ -23,7 +23,6 @@ import {
PackageId,
EnsureStorePath,
ExtractStore,
DaemonReturned,
ValidIfNoStupidEscape,
} from "./types"
import * as patterns from "./util/patterns"
@@ -50,7 +49,11 @@ import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall"
import { setupMain } from "./mainFn"
import { defaultTrigger } from "./trigger/defaultTrigger"
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
import setupConfig, { Read, Save } from "./config/setupConfig"
import setupConfig, {
DependenciesReceipt,
Read,
Save,
} from "./config/setupConfig"
import {
InterfacesReceipt,
SetInterfaces,
@@ -72,6 +75,9 @@ import { getStore } from "./store/getStore"
import { CommandOptions, MountOptions, Overlay } from "./util/Overlay"
import { splitCommand } from "./util/splitCommand"
import { Mounts } from "./mainFn/Mounts"
import { Dependency } from "./Dependency"
import * as T from "./types"
import { Checker, EmVer } from "./emverLite/mod"
// prettier-ignore
type AnyNeverCond<T extends any[], Then, Else> =
@@ -104,6 +110,20 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
}
build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) {
type DependencyType = {
[K in keyof {
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false
? K
: never
}]: Dependency
} & {
[K in keyof {
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true
? K
: never
}]?: Dependency
}
return {
serviceInterface: {
getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) =>
@@ -184,7 +204,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
| Config<any, never>,
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
>(
metaData: Omit<ActionMetadata, "input"> & {
id: string,
metadata: Omit<ActionMetadata, "input"> & {
input: Config<Type, Store> | Config<Type, never>
},
fn: (options: {
@@ -192,8 +213,13 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
input: Type
}) => Promise<ActionResult>,
) => {
const { input, ...rest } = metaData
return createAction<Manifest, Store, ConfigType, Type>(rest, fn, input)
const { input, ...rest } = metadata
return createAction<Manifest, Store, ConfigType, Type>(
id,
rest,
fn,
input,
)
},
getSystemSmtp: <E extends Effects>(effects: E) =>
removeConstType<E>()(new GetSystemSmtp(effects)),
@@ -205,16 +231,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
mounts?: { path: string; options: MountOptions }[]
},
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
const commands = splitCommand(command)
const overlay = await Overlay.of(effects, imageId)
try {
for (let mount of options.mounts || []) {
await overlay.mount(mount.options, mount.path)
}
return await overlay.exec(commands)
} finally {
await overlay.destroy()
}
return runCommand<Manifest>(effects, imageId, command, options)
},
createDynamicAction: <
@@ -224,7 +241,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
| Config<any, never>,
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
>(
metaData: (options: {
id: string,
metadata: (options: {
effects: Effects
}) => MaybePromise<Omit<ActionMetadata, "input">>,
fn: (options: {
@@ -234,7 +252,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
input: Config<Type, Store> | Config<Type, never>,
) => {
return createAction<Manifest, Store, ConfigType, Type>(
metaData,
id,
metadata,
fn,
input,
)
@@ -242,6 +261,11 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
HealthCheck: {
of: healthCheck,
},
Dependency: {
of(data: Dependency["data"]) {
return new Dependency({ ...data })
},
},
healthCheck: {
checkPortListening,
checkWebUrl,
@@ -284,15 +308,51 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
Store,
Input,
any
>
> | null
},
) => setupDependencyConfig<Store, Input, Manifest>(config, autoConfigs),
setupDependencies: <Input extends Record<string, any>>(
fn: (options: {
effects: Effects
input: Input | null
}) => Promise<DependencyType>,
) => {
return async (options: { effects: Effects; input: Input }) => {
const dependencyType = await fn(options)
return await options.effects.setDependencies({
dependencies: Object.entries(dependencyType).map(
([
id,
{
data: { versionSpec, ...x },
},
]) => ({
id,
...x,
...(x.type === "running"
? {
kind: "running",
healthChecks: x.healthChecks,
}
: {
kind: "exists",
}),
versionSpec: versionSpec.range,
}),
),
})
}
},
setupExports: (fn: SetupExports<Store>) => fn,
setupInit: (
migrations: Migrations<Manifest, Store>,
install: Install<Manifest, Store>,
uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setDependencies: (options: {
effects: Effects
input: any
}) => Promise<DependenciesReceipt>,
setupExports: SetupExports<Store>,
) =>
setupInit<Manifest, Store>(
@@ -301,6 +361,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
uninstall,
setInterfaces,
setupExports,
setDependencies,
),
setupInstall: (fn: InstallFn<Manifest, Store>) => Install.of(fn),
setupInterfaces: <
@@ -355,6 +416,9 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
spec: Spec,
) => Config.of<Spec, Store>(spec),
},
Checker: {
parse: Checker.parse,
},
Daemons: {
of(config: {
effects: Effects
@@ -369,13 +433,17 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
LocalConfig extends Record<string, any>,
RemoteConfig extends Record<string, any>,
>({
localConfig,
remoteConfig,
localConfigSpec,
remoteConfigSpec,
dependencyConfig,
update,
}: {
localConfig: Config<LocalConfig, Store> | Config<LocalConfig, never>
remoteConfig: Config<RemoteConfig, any> | Config<RemoteConfig, never>
localConfigSpec:
| Config<LocalConfig, Store>
| Config<LocalConfig, never>
remoteConfigSpec:
| Config<RemoteConfig, any>
| Config<RemoteConfig, never>
dependencyConfig: (options: {
effects: Effects
localConfig: LocalConfig
@@ -390,6 +458,10 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
>(dependencyConfig, update)
},
},
EmVer: {
from: EmVer.from,
parse: EmVer.parse,
},
List: {
text: List.text,
number: List.number,
@@ -654,3 +726,23 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
}
}
}
export async function runCommand<Manifest extends SDKManifest>(
effects: Effects,
imageId: Manifest["images"][number],
command: string | [string, ...string[]],
options: CommandOptions & {
mounts?: { path: string; options: MountOptions }[]
},
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> {
const commands = splitCommand(command)
const overlay = await Overlay.of(effects, imageId)
try {
for (let mount of options.mounts || []) {
await overlay.mount(mount.options, mount.path)
}
return await overlay.exec(commands)
} finally {
await overlay.destroy()
}
}

View File

@@ -15,7 +15,8 @@ export class CreatedAction<
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
> {
private constructor(
public readonly myMetaData: MaybeFn<
public readonly id: string,
public readonly myMetadata: MaybeFn<
Manifest,
Store,
Omit<ActionMetadata, "input">
@@ -37,12 +38,14 @@ export class CreatedAction<
| Config<any, never>,
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
>(
metaData: MaybeFn<Manifest, Store, Omit<ActionMetadata, "input">>,
id: string,
metadata: MaybeFn<Manifest, Store, Omit<ActionMetadata, "input">>,
fn: (options: { effects: Effects; input: Type }) => Promise<ActionResult>,
inputConfig: Config<Type, Store> | Config<Type, never>,
) {
return new CreatedAction<Manifest, Store, ConfigType, Type>(
metaData,
id,
metadata,
fn,
inputConfig as Config<Type, Store>,
)
@@ -62,15 +65,15 @@ export class CreatedAction<
})
}
async metaData(options: { effects: Effects }) {
if (this.myMetaData instanceof Function)
return await this.myMetaData(options)
return this.myMetaData
async metadata(options: { effects: Effects }) {
if (this.myMetadata instanceof Function)
return await this.myMetadata(options)
return this.myMetadata
}
async ActionMetadata(options: { effects: Effects }): Promise<ActionMetadata> {
return {
...(await this.metaData(options)),
...(await this.metadata(options)),
input: await this.input.build(options),
}
}

View File

@@ -1,6 +1,5 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects, ExpectedExports } from "../types"
import { once } from "../util/once"
import { CreatedAction } from "./createAction"
export function setupActions<Manifest extends SDKManifest, Store>(
@@ -9,8 +8,7 @@ export function setupActions<Manifest extends SDKManifest, Store>(
const myActions = async (options: { effects: Effects }) => {
const actions: Record<string, CreatedAction<Manifest, Store, any>> = {}
for (const action of createdActions) {
const actionMetadata = await action.metaData(options)
actions[actionMetadata.id] = action
actions[action.id] = action
}
return actions
}

View File

@@ -15,7 +15,7 @@ export function setupDependencyConfig<
Store,
Input,
any
>
> | null
},
): ExpectedExports.dependencyConfig {
return autoConfigs

View File

@@ -163,7 +163,7 @@ export class EmVer {
}
toString() {
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}`
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}` as ValidEmVer
}
}
@@ -179,6 +179,7 @@ export class Checker {
* @returns
*/
static parse(range: string | Checker): Checker {
console.log(`Parser (${range})`)
if (range instanceof Checker) {
return range
}
@@ -193,10 +194,12 @@ export class Checker {
return new Checker((version) => {
EmVer.from(version)
return true
})
}, range)
}
if (range.startsWith("!!")) return Checker.parse(range.substring(2))
if (range.startsWith("!")) {
return Checker.parse(range.substring(1)).not()
const tempValue = Checker.parse(range.substring(1))
return new Checker((x) => !tempValue.check(x), range)
}
const starSubMatches = starSub.exec(range)
if (starSubMatches != null) {
@@ -210,7 +213,7 @@ export class Checker {
!v.greaterThan(emVarUpper) &&
!v.equals(emVarUpper)
)
})
}, range)
}
switch (range.substring(0, 2)) {
@@ -219,14 +222,14 @@ export class Checker {
return new Checker((version) => {
const v = EmVer.from(version)
return v.greaterThanOrEqual(emVar)
})
}, range)
}
case "<=": {
const emVar = EmVer.parse(range.substring(2))
return new Checker((version) => {
const v = EmVer.from(version)
return v.lessThanOrEqual(emVar)
})
}, range)
}
}
@@ -236,21 +239,21 @@ export class Checker {
return new Checker((version) => {
const v = EmVer.from(version)
return v.greaterThan(emVar)
})
}, range)
}
case "<": {
const emVar = EmVer.parse(range.substring(1))
return new Checker((version) => {
const v = EmVer.from(version)
return v.lessThan(emVar)
})
}, range)
}
case "=": {
const emVar = EmVer.parse(range.substring(1))
return new Checker((version) => {
const v = EmVer.from(version)
return v.equals(emVar)
})
}, `=${emVar.toString()}`)
}
}
throw new Error("Couldn't parse range: " + range)
@@ -261,40 +264,53 @@ export class Checker {
* a pattern
*/
public readonly check: (value: ValidEmVer | EmVer) => boolean,
private readonly _range: string,
) {}
get range() {
return this._range as ValidEmVerRange
}
/**
* Used when we want the `and` condition with another checker
*/
public and(...others: (Checker | string)[]): Checker {
return new Checker((value) => {
if (!this.check(value)) {
return false
}
for (const other of others) {
if (!Checker.parse(other).check(value)) {
const othersCheck = others.map(Checker.parse)
return new Checker(
(value) => {
if (!this.check(value)) {
return false
}
}
return true
})
for (const other of othersCheck) {
if (!other.check(value)) {
return false
}
}
return true
},
othersCheck.map((x) => x._range).join(" && "),
)
}
/**
* Used when we want the `or` condition with another checker
*/
public or(...others: (Checker | string)[]): Checker {
return new Checker((value) => {
if (this.check(value)) {
return true
}
for (const other of others) {
if (Checker.parse(other).check(value)) {
const othersCheck = others.map(Checker.parse)
return new Checker(
(value) => {
if (this.check(value)) {
return true
}
}
return false
})
for (const other of othersCheck) {
if (other.check(value)) {
return true
}
}
return false
},
othersCheck.map((x) => x._range).join(" || "),
)
}
/**
@@ -302,6 +318,7 @@ export class Checker {
* @returns
*/
public not(): Checker {
return new Checker((value) => !this.check(value))
let newRange = `!${this._range}`
return Checker.parse(newRange)
}
}

View File

@@ -10,6 +10,7 @@ export * as config from "./config"
export * as configBuilder from "./config/builder"
export * as configTypes from "./config/configTypes"
export * as dependencyConfig from "./dependencyConfig"
export * as daemons from "./mainFn/Daemons"
export * as health from "./health"
export * as healthFns from "./health/checkFns"
export * as inits from "./inits"
@@ -17,9 +18,10 @@ export * as mainFn from "./mainFn"
export * as manifest from "./manifest"
export * as toml from "@iarna/toml"
export * as types from "./types"
export * as util from "./util"
export * as T from "./types"
export * as yaml from "yaml"
export * as startSdk from "./StartSdk"
export * as utils from "./util"
export * as matches from "ts-matches"
export * as YAML from "yaml"
export * as TOML from "@iarna/toml"

View File

@@ -3,11 +3,11 @@ import { Effects, ExposeServicePaths, ExposeUiPaths } from "../types"
export type SetupExports<Store> = (opts: { effects: Effects }) =>
| {
ui: { [k: string]: ExposeUiPaths<Store> }
services: ExposeServicePaths<Store>
services: ExposeServicePaths<Store>["paths"]
}
| Promise<{
ui: { [k: string]: ExposeUiPaths<Store> }
services: ExposeServicePaths<Store>
services: ExposeServicePaths<Store>["paths"]
}>
export const setupExports = <Store>(fn: (opts: SetupExports<Store>) => void) =>

View File

@@ -1,6 +1,12 @@
import { DependenciesReceipt } from "../config/setupConfig"
import { SetInterfaces } from "../interfaces/setupInterfaces"
import { SDKManifest } from "../manifest/ManifestTypes"
import { ExpectedExports, ExposeUiPaths, ExposeUiPathsAll } from "../types"
import {
Effects,
ExpectedExports,
ExposeUiPaths,
ExposeUiPathsAll,
} from "../types"
import { Migrations } from "./migrations/setupMigrations"
import { SetupExports } from "./setupExports"
import { Install } from "./setupInstall"
@@ -12,6 +18,10 @@ export function setupInit<Manifest extends SDKManifest, Store>(
uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setupExports: SetupExports<Store>,
setDependencies: (options: {
effects: Effects
input: any
}) => Promise<DependenciesReceipt>,
): {
init: ExpectedExports.init
uninit: ExpectedExports.uninit
@@ -25,8 +35,9 @@ export function setupInit<Manifest extends SDKManifest, Store>(
input: null,
})
const { services, ui } = await setupExports(opts)
await opts.effects.exposeForDependents(services)
await opts.effects.exposeForDependents({ paths: services })
await opts.effects.exposeUi(forExpose(ui))
await setDependencies({ effects: opts.effects, input: null })
},
uninit: async (opts) => {
await migrations.uninit(opts)

View File

@@ -43,7 +43,7 @@ type Daemon<
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
const runDaemon =
export const runDaemon =
<Manifest extends SDKManifest>() =>
async <A extends string>(
effects: Effects,

View File

@@ -1,5 +1,4 @@
import { ValidEmVer } from "../emverLite/mod"
import { ActionMetadata } from "../types"
export interface Container {
/** This should be pointing to a docker container name */
@@ -75,31 +74,13 @@ export type SDKManifest = {
}
export interface ManifestDependency {
/** The range of versions that would satisfy the dependency
*
* ie: >=3.4.5 <4.0.0
*/
version: string
/**
* A human readable explanation on what the dependency is used for
*/
description: string | null
requirement:
| {
type: "opt-in"
/**
* The human readable explanation on how to opt-in to the dependency
*/
how: string
}
| {
type: "opt-out"
/**
* The human readable explanation on how to opt-out to the dependency
*/
how: string
}
| {
type: "required"
}
/**
* Determines if the dependency is optional or not. Times that optional that are good include such situations
* such as being able to toggle other services or to use a different service for the same purpose.
*/
optional: boolean
}

View File

@@ -414,8 +414,7 @@ describe("values", () => {
dependencies: {
remoteTest: {
description: "",
requirement: { how: "", type: "opt-in" },
version: "1.0",
optional: true,
},
},
}),

View File

@@ -35,8 +35,7 @@ export const sdk = StartSdk.of()
dependencies: {
remoteTest: {
description: "",
requirement: { how: "", type: "opt-in" },
version: "1.0",
optional: false,
},
},
}),

View File

@@ -16,8 +16,8 @@ describe("setupDependencyConfig", () => {
}),
})
const remoteTest = sdk.DependencyConfig.of({
localConfig: testConfig,
remoteConfig: testConfig2,
localConfigSpec: testConfig,
remoteConfigSpec: testConfig2,
dependencyConfig: async ({}) => {},
})
sdk.setupDependencyConfig(testConfig, {

View File

@@ -7,6 +7,8 @@ import { BindOptions, Scheme } from "./interfaces/Host"
import { Daemons } from "./mainFn/Daemons"
import { UrlString } from "./util/getServiceInterface"
export { SDKManifest } from "./manifest/ManifestTypes"
export type ExportedAction = (options: {
effects: Effects
input?: Record<string, unknown>
@@ -90,7 +92,7 @@ export namespace ExpectedExports {
/** Auto configure is used to make sure that other dependencies have the values t
* that this service could use.
*/
export type dependencyConfig = Record<PackageId, DependencyConfig>
export type dependencyConfig = Record<PackageId, DependencyConfig | null>
}
export type TimeMs = number
export type VersionString = string
@@ -161,9 +163,10 @@ export type DaemonReturned = {
export type ActionMetadata = {
name: string
description: string
id: string
warning: string | null
input: InputSpec
allowedStatuses: "only-running" | "only-stopped" | "any" | "disabled"
disabled: boolean
allowedStatuses: "only-running" | "only-stopped" | "any"
/**
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
*/
@@ -441,7 +444,7 @@ export type Effects = {
*
* @param options
*/
exportAction(options: ActionMetadata): Promise<void>
exportAction(options: { id: string; metadata: ActionMetadata }): Promise<void>
/**
* Remove an action that was exported. Used problably during main or during setConfig.
*/
@@ -598,7 +601,8 @@ export type KnownError =
export type Dependency = {
id: PackageId
kind: DependencyKind
versionSpec: string
url: string
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
export type Dependencies = Array<Dependency>

View File

@@ -7,8 +7,9 @@ import "./deepEqual"
import "./deepMerge"
import "./Overlay"
import "./once"
import { SDKManifest } from "../manifest/ManifestTypes"
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
export { getServiceInterfaces } from "./getServiceInterfaces"
// prettier-ignore
export type FlattenIntersection<T> =
T extends ArrayLike<any> ? T :
@@ -28,3 +29,5 @@ export type NoAny<A> = NeverPossible extends A
? never
: A
: A
export { getDefaultString } from "./getDefaultString"

View File

@@ -1,8 +1,7 @@
import { arrayOf, string } from "ts-matches"
import { ValidIfNoStupidEscape } from "../types"
export const splitCommand = <A>(
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
export const splitCommand = (
command: string | [string, ...string[]],
): string[] => {
if (arrayOf(string).test(command)) return command
return String(command)

View File

@@ -2143,15 +2143,6 @@ dependencies = [
"cc",
]
[[package]]
name = "id-pool"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d0df4d8a768821ee4aa2e0353f67125c4586f0e13adbf95b8ebbf8d8fdb344"
dependencies = [
"serde",
]
[[package]]
name = "ident_case"
version = "1.0.1"

7001
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -73,7 +73,7 @@
"monaco-editor": "^0.33.0",
"mustache": "^4.2.0",
"ng-qrcode": "^17.0.0",
"node-jose": "^2.1.1",
"node-jose": "^2.2.0",
"patch-db-client": "file:../patch-db/client",
"pbkdf2": "^3.1.2",
"rxjs": "^7.5.6",

View File

@@ -2,7 +2,7 @@
<!-- color background -->
<div class="background">
<img
[src]="pkg | mimeType | trustUrl"
[src]="pkg.icon| trustUrl"
alt="{{ pkg.manifest.title }} Icon"
/>
</div>
@@ -10,7 +10,7 @@
<div class="overlay"></div>
<!-- icon -->
<img
[src]="pkg | mimeType | trustUrl"
[src]="pkg.icon | trustUrl"
class="icon box-shadow-lg"
alt="{{ pkg.manifest.title }} Icon"
/>

View File

@@ -3,11 +3,10 @@ import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { SharedPipesModule } from '@start9labs/shared'
import { ItemComponent } from './item.component'
import { MimeTypePipeModule } from '../../../pipes/mime-type.pipe'
@NgModule({
declarations: [ItemComponent],
exports: [ItemComponent],
imports: [CommonModule, RouterModule, SharedPipesModule, MimeTypePipeModule],
imports: [CommonModule, RouterModule, SharedPipesModule],
})
export class ItemModule {}

View File

@@ -1,22 +1,19 @@
<div class="outer-container">
<div class="inner-container">
<tui-avatar class="dep-img" [src]="getImage(dep.key)"></tui-avatar>
<tui-avatar class="dep-img" [src]="pkg['dependency-metadata'][dep.key].icon"></tui-avatar>
<div class="wrapper-margin">
<div class="inner-container-title">
<span>
{{ getTitle(dep.key) }}
{{ pkg['dependency-metadata'][dep.key].title || dep.key }}
</span>
<p>
<ng-container [ngSwitch]="dep.value.requirement.type">
<span *ngSwitchCase="'required'">(required)</span>
<span *ngSwitchCase="'opt-out'">(required by default)</span>
<span *ngSwitchCase="'opt-in'">(optional)</span>
</ng-container>
@if (dep.value.optional) {
<span>(optional)</span>
} @else {
<span>(required)</span>
}
</p>
</div>
<span class="inner-container-version">
{{ dep.value.version | displayEmver }}
</span>
<span class="inner-container-description">
{{ dep.value.description }}
</span>

View File

@@ -36,16 +36,6 @@
}
}
&-version {
font-size: 0.875rem;
line-height: 1.25rem;
color: rgb(250 250 250 / 0.7);
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
&-description {
font-size: 0.875rem;
line-height: 1.25rem;

View File

@@ -14,14 +14,4 @@ export class DependenciesComponent {
@Input({ required: true })
dep!: KeyValue<string, Dependency>
getImage(key: string): string {
const icon = this.pkg['dependency-metadata'][key]?.icon
// @TODO fix when registry api is updated to include mimetype in icon url
return icon ? `data:image/png;base64,${icon}` : key.substring(0, 2)
}
getTitle(key: string): string {
return this.pkg['dependency-metadata'][key]?.title || key
}
}

View File

@@ -1,34 +0,0 @@
import { NgModule, Pipe, PipeTransform } from '@angular/core'
import { MarketplacePkg } from '../types'
@Pipe({
name: 'mimeType',
})
export class MimeTypePipe implements PipeTransform {
transform(pkg: MarketplacePkg): string {
if (pkg.icon.startsWith('data:')) return pkg.icon
if (pkg.manifest.assets.icon) {
switch (pkg.manifest.assets.icon.split('.').pop()) {
case 'png':
return `data:image/png;base64,${pkg.icon}`
case 'jpeg':
case 'jpg':
return `data:image/jpeg;base64,${pkg.icon}`
case 'gif':
return `data:image/gif;base64,${pkg.icon}`
case 'svg':
return `data:image/svg+xml;base64,${pkg.icon}`
default:
return `data:image/png;base64,${pkg.icon}`
}
}
return `data:image/png;base64,${pkg.icon}`
}
}
@NgModule({
declarations: [MimeTypePipe],
exports: [MimeTypePipe],
})
export class MimeTypePipeModule {}

View File

@@ -20,7 +20,6 @@ export * from './pages/show/screenshots/screenshots.component'
export * from './pages/show/hero/hero.component'
export * from './pipes/filter-packages.pipe'
export * from './pipes/mime-type.pipe'
export * from './components/store-icon/store-icon.component'
export * from './components/store-icon/store-icon.component.module'

View File

@@ -38,6 +38,7 @@ export interface MarketplacePkg {
export interface DependencyMetadata {
title: string
icon: Url
optional: boolean
hidden: boolean
}
@@ -50,9 +51,6 @@ export interface Manifest {
short: string
long: string
}
assets: {
icon: Url // filename
}
replaces?: string[]
'release-notes': string
license: string // name of license
@@ -70,21 +68,10 @@ export interface Manifest {
}
dependencies: Record<string, Dependency>
'os-version': string
'has-config': boolean
}
export interface Dependency {
version: string
requirement:
| {
type: 'opt-in'
how: string
}
| {
type: 'opt-out'
how: string
}
| {
type: 'required'
}
description: string | null
optional: boolean
}

View File

@@ -30,7 +30,7 @@ export class ServiceInterfaceRoute {
readonly interfaceInfo$ = inject(PatchDB<DataModel>).watch$(
'package-data',
this.context.packageId,
'installed',
'state-info',
'interfaceInfo',
this.context.interfaceId,
)

View File

@@ -1,13 +1,16 @@
import { Pipe, PipeTransform } from '@angular/core'
import { InstallProgress } from 'src/app/services/patch-db/data-model'
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
import { Progress } from 'src/app/services/patch-db/data-model'
@Pipe({
name: 'installProgress',
standalone: true,
})
export class InstallProgressPipe implements PipeTransform {
transform(installProgress?: InstallProgress): number {
return packageLoadingProgress(installProgress)?.totalProgress || 0
export class InstallingProgressDisplayPipe implements PipeTransform {
transform(progress: Progress): string {
if (progress === true) return 'finalizing'
if (progress === false || !progress.total) return 'unknown %'
const percentage = Math.round((100 * progress.done) / progress.total)
return percentage < 99 ? String(percentage) + '%' : 'finalizing'
}
}

View File

@@ -18,12 +18,12 @@ import {
MarketplacePkg,
} from '@start9labs/marketplace'
import { Log } from '@start9labs/shared'
import { unionSelectKey } from '@start9labs/start-sdk/lib/config/configTypes'
import { List } from '@start9labs/start-sdk/lib/config/builder/list'
import { Value } from '@start9labs/start-sdk/lib/config/builder/value'
import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants'
import { Config } from '@start9labs/start-sdk/lib/config/builder/config'
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
import { Config } from '@start9labs/start-sdk/cjs/sdk/lib/config/builder/config'
import { Value } from '@start9labs/start-sdk/cjs/sdk/lib/config/builder/value'
import { Variants } from '@start9labs/start-sdk/cjs/sdk/lib/config/builder/variants'
import { List } from '@start9labs/start-sdk/cjs/sdk/lib/config/builder/list'
import { unionSelectKey } from '@start9labs/start-sdk/cjs/sdk/lib/config/configTypes'
export module Mock {
export const ServerUpdated: ServerStatusInfo = {
@@ -67,9 +67,6 @@ export module Mock {
short: 'A Bitcoin full node by Bitcoin Core.',
long: 'Bitcoin is a decentralized consensus protocol and settlement network.',
},
assets: {
icon: 'icon.png',
},
replaces: ['banks', 'governments'],
'release-notes': 'Taproot, Schnorr, and more.',
license: 'MIT',
@@ -86,8 +83,9 @@ export module Mock {
start: 'Starting Bitcoin is good for your health.',
stop: null,
},
'os-version': '0.2.12',
dependencies: {},
'os-version': '0.4.0',
'has-config': true,
}
export const MockManifestLnd: Manifest = {
@@ -98,11 +96,7 @@ export module Mock {
short: 'A bolt spec compliant client.',
long: 'More info about LND. More info about LND. More info about LND.',
},
assets: {
icon: 'icon.png',
},
'release-notes':
'* Dual funded channels! And lots more amazing new features. Also includes several bugfixes and performance enhancements.',
'release-notes': 'Dual funded channels!',
license: 'MIT',
'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper',
'upstream-repo': 'https://github.com/lightningnetwork/lnd',
@@ -117,26 +111,19 @@ export module Mock {
start: 'Starting LND is good for your health.',
stop: null,
},
'os-version': '0.2.12',
dependencies: {
bitcoind: {
version: '=0.21.0',
description: 'LND needs bitcoin to live.',
requirement: {
type: 'opt-out',
how: 'You can use an external node from your server if you prefer.',
},
optional: true,
},
'btc-rpc-proxy': {
version: '>=0.2.2',
description:
'As long as Bitcoin is pruned, LND needs Bitcoin Proxy to fetch block over the P2P network.',
requirement: {
type: 'opt-in',
how: `To use Proxy's user management system, go to LND config and select Bitcoin Proxy under Bitcoin config.`,
},
optional: true,
},
},
'os-version': '0.4.0',
'has-config': true,
}
export const MockManifestBitcoinProxy: Manifest = {
@@ -148,9 +135,6 @@ export module Mock {
short: 'A super charger for your Bitcoin node.',
long: 'More info about Bitcoin Proxy. More info about Bitcoin Proxy. More info about Bitcoin Proxy.',
},
assets: {
icon: 'icon.png',
},
'release-notes': 'Even better support for Bitcoin and wallets!',
license: 'MIT',
'wrapper-repo': 'https://github.com/start9labs/btc-rpc-proxy-wrapper',
@@ -165,27 +149,27 @@ export module Mock {
start: null,
stop: null,
},
'os-version': '0.2.12',
dependencies: {
bitcoind: {
version: '>=0.20.0',
description: 'Bitcoin Proxy requires a Bitcoin node.',
requirement: {
type: 'required',
},
optional: false,
},
},
'os-version': '0.4.0',
'has-config': false,
}
export const BitcoinDep: DependencyMetadata = {
title: 'Bitcoin Core',
icon: BTC_ICON,
optional: false,
hidden: true,
}
export const ProxyDep: DependencyMetadata = {
title: 'Bitcoin Proxy',
icon: PROXY_ICON,
optional: true,
hidden: false,
}
@@ -1292,6 +1276,7 @@ export module Mock {
},
'dependency-config-errors': {},
},
actions: {}, // @TODO need mocks
'service-interfaces': {
ui: {
id: 'ui',
@@ -1508,11 +1493,6 @@ export module Mock {
},
},
},
'current-dependents': {
lnd: {
'health-checks': [],
},
},
'current-dependencies': {},
'dependency-info': {},
'marketplace-url': 'https://registry.start9.com/',
@@ -1535,6 +1515,7 @@ export module Mock {
},
'dependency-config-errors': {},
},
actions: {},
'service-interfaces': {
ui: {
id: 'ui',
@@ -1643,13 +1624,9 @@ export module Mock {
},
},
},
'current-dependents': {
lnd: {
'health-checks': [],
},
},
'current-dependencies': {
bitcoind: {
versionRange: '>=26.0.0',
'health-checks': [],
},
},
@@ -1681,6 +1658,7 @@ export module Mock {
'btc-rpc-proxy': 'Username not found',
},
},
actions: {},
'service-interfaces': {
grpc: {
id: 'grpc',
@@ -1893,12 +1871,13 @@ export module Mock {
},
},
},
'current-dependents': {},
'current-dependencies': {
bitcoind: {
versionRange: '>=26.0.0',
'health-checks': [],
},
'btc-rpc-proxy': {
versionRange: '>2.0.0', // @TODO
'health-checks': [],
},
},

View File

@@ -1,6 +1,5 @@
import { Dump, Revision } from 'patch-db-client'
import { MarketplacePkg, StoreInfo, Manifest } from '@start9labs/marketplace'
import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes'
import {
DataModel,
DomainInfo,
@@ -16,7 +15,8 @@ import {
FollowLogsRes,
FollowLogsReq,
} from '@start9labs/shared'
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
import { customSmtp } from '@start9labs/start-sdk/cjs/sdk/lib/config/configConstants'
import { InputSpec } from '@start9labs/start-sdk/cjs/sdk/lib/config/configTypes'
export module RR {
// DB
@@ -596,8 +596,8 @@ export enum NotificationLevel {
export type NotificationData<T> = T extends 0
? null
: T extends 1
? BackupReport
: any
? BackupReport
: any
export interface BackupReport {
server: {

View File

@@ -866,7 +866,7 @@ export class MockApiService extends ApiService {
setTimeout(async () => {
for (let i = 0; i < ids.length; i++) {
const id = ids[i]
const appPath = `/package-data/${id}/installed/status/main/status`
const appPath = `/package-data/${id}/status/main/status`
const appPatch = [
{
op: PatchOp.REPLACE,
@@ -1032,7 +1032,7 @@ export class MockApiService extends ApiService {
const patch = [
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/configured`,
path: `/package-data/${params.id}/status/configured`,
value: true,
},
]
@@ -1079,7 +1079,7 @@ export class MockApiService extends ApiService {
}
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
const path = `/package-data/${params.id}/installed/status/main`
const path = `/package-data/${params.id}/status/main`
await pauseFor(2000)
@@ -1128,7 +1128,7 @@ export class MockApiService extends ApiService {
): Promise<RR.RestartPackageRes> {
// first enact stop
await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main`
const path = `/package-data/${params.id}/status/main`
setTimeout(async () => {
const patch2: Operation<any>[] = [

View File

@@ -1,4 +1,9 @@
import { DataModel } from 'src/app/services/patch-db/data-model'
import {
DataModel,
HealthResult,
PackageMainStatus,
PackageState,
} from 'src/app/services/patch-db/data-model'
import { Mock } from './api.fixures'
export const mockPatchData: DataModel = {
@@ -31,18 +36,49 @@ export const mockPatchData: DataModel = {
id: 'abcdefgh',
version: '0.3.5.1',
country: 'us',
ui: {
lanHostname: 'adjective-noun.local',
torHostname: 'myveryownspecialtoraddress.onion',
ipInfo: {
eth0: {
wireless: false,
ipv4: '10.0.0.1',
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD',
ui: [
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'local',
value: 'adjective-noun.local',
port: null,
sslPort: 1111,
},
},
domainInfo: null,
},
{
kind: 'onion',
hostname: {
value: 'myveryownspecialtoraddress.onion',
port: 80,
sslPort: 443,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv4',
value: '192.168.1.5',
port: null,
sslPort: 1111,
},
},
{
kind: 'ip',
networkInterfaceId: 'elan0',
public: false,
hostname: {
kind: 'ipv6',
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
port: null,
sslPort: 1111,
},
},
],
network: {
domains: [],
start9ToSubdomain: null,
@@ -109,11 +145,49 @@ export const mockPatchData: DataModel = {
},
'package-data': {
bitcoind: {
...Mock.bitcoind,
manifest: {
...Mock.bitcoind.manifest,
version: '0.19.0',
'state-info': {
state: PackageState.Installed,
manifest: {
...Mock.MockManifestBitcoind,
version: '0.20.0',
},
},
icon: '/assets/img/service-icons/bitcoind.svg',
'last-backup': null,
status: {
configured: true,
main: {
status: PackageMainStatus.Running,
started: '2021-06-14T20:49:17.774Z',
health: {
'ephemeral-health-check': {
name: 'Ephemeral Health Check',
result: HealthResult.Starting,
},
'chain-state': {
name: 'Chain State',
result: HealthResult.Loading,
message: 'Bitcoin is syncing from genesis',
},
'p2p-interface': {
name: 'P2P',
result: HealthResult.Success,
message: 'Health check successful',
},
'rpc-interface': {
name: 'RPC',
result: HealthResult.Failure,
message: 'RPC interface unreachable.',
},
'unnecessary-health-check': {
name: 'Unnecessary Health Check',
result: HealthResult.Disabled,
},
},
},
'dependency-config-errors': {},
},
actions: {}, // @TODO
'service-interfaces': {
ui: {
id: 'ui',
@@ -330,22 +404,20 @@ export const mockPatchData: DataModel = {
},
},
},
'current-dependents': {
lnd: {
pointers: [],
'health-checks': [],
},
},
'current-dependencies': {},
'dependency-info': {},
'marketplace-url': 'https://registry.start9.com/',
'developer-key': 'developer-key',
'has-config': true,
outboundProxy: null,
},
lnd: {
...Mock.lnd,
manifest: {
...Mock.lnd.manifest,
version: '0.11.0',
'state-info': {
state: PackageState.Installed,
manifest: {
...Mock.MockManifestLnd,
version: '0.11.0',
},
},
icon: '/assets/img/service-icons/lnd.png',
'last-backup': null,
@@ -358,6 +430,7 @@ export const mockPatchData: DataModel = {
'btc-rpc-proxy': 'This is a config unsatisfied error',
},
},
actions: {},
'service-interfaces': {
grpc: {
id: 'grpc',
@@ -568,12 +641,13 @@ export const mockPatchData: DataModel = {
},
},
},
'current-dependents': {},
'current-dependencies': {
bitcoind: {
versionRange: '>=26.0.0',
'health-checks': [],
},
'btc-rpc-proxy': {
versionRange: '>2.0.0',
'health-checks': [],
},
},
@@ -589,6 +663,8 @@ export const mockPatchData: DataModel = {
},
'marketplace-url': 'https://registry.start9.com/',
'developer-key': 'developer-key',
'has-config': true,
outboundProxy: null,
},
},
}

View File

@@ -88,19 +88,14 @@ export class DepErrorService {
}
}
const pkgManifest = pkg['state-info'].manifest
const versionRange = pkg['current-dependencies'][depId].versionRange
const depManifest = dep['state-info'].manifest
// incorrect version
if (
!this.emver.satisfies(
depManifest.version,
pkgManifest.dependencies[depId].version,
)
) {
if (!this.emver.satisfies(depManifest.version, versionRange)) {
return {
type: DependencyErrorType.IncorrectVersion,
expected: pkgManifest.dependencies[depId].version,
expected: versionRange,
received: depManifest.version,
}
}

View File

@@ -1,9 +1,13 @@
import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes'
import { BackupJob, ServerNotifications } from '../api/api.types'
import { Url } from '@start9labs/shared'
import { Manifest } from '@start9labs/marketplace'
import { BackupJob, ServerNotifications } from '../api/api.types'
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
import { types } from '@start9labs/start-sdk'
import { InputSpec } from '@start9labs/start-sdk/cjs/sdk/lib/config/configTypes'
import {
ActionMetadata,
HostnameInfo,
} from '@start9labs/start-sdk/cjs/sdk/lib/types'
import { customSmtp } from '@start9labs/start-sdk/cjs/sdk/lib/config/configConstants'
type ServiceInterfaceWithHostInfo = types.ServiceInterfaceWithHostInfo
export interface DataModel {
@@ -174,8 +178,8 @@ export type PackageDataEntry<T extends StateInfo = StateInfo> = {
'state-info': T
icon: Url
status: Status
actions: Record<string, ActionMetadata>
'last-backup': string | null
'current-dependents': { [id: string]: CurrentDependencyInfo }
'current-dependencies': { [id: string]: CurrentDependencyInfo }
'dependency-info': {
[id: string]: {
@@ -217,18 +221,10 @@ export enum PackageState {
}
export interface CurrentDependencyInfo {
versionRange: string
'health-checks': string[] // array of health check IDs
}
export interface Action {
name: string
description: string
warning: string | null
disabled: string | null
'input-spec': InputSpec | null
group: string | null
}
export interface Status {
configured: boolean
main: MainStatus
@@ -260,7 +256,7 @@ export interface MainStatusStarting {
export interface MainStatusRunning {
status: PackageMainStatus.Running
started: string // UTC date string
health: { [id: string]: HealthCheckResult }
health: Record<string, HealthCheckResult>
}
export interface MainStatusBackingUp {
@@ -307,7 +303,6 @@ export interface HealthCheckResultStarting {
export interface HealthCheckResultDisabled {
result: HealthResult.Disabled
reason: string
}
export interface HealthCheckResultSuccess {
@@ -322,7 +317,7 @@ export interface HealthCheckResultLoading {
export interface HealthCheckResultFailure {
result: HealthResult.Failure
error: string
message: string
}
export type InstallingInfo = {

View File

@@ -23,10 +23,7 @@ export function renderPkgStatus(
if (pkg['state-info'].state === PackageState.Installed) {
primary = getPrimaryStatus(pkg.status)
dependency = getDependencyStatus(depErrors)
health = getHealthStatus(
pkg.status,
!isEmptyObject(pkg['state-info'].manifest['health-checks']),
)
health = getHealthStatus(pkg.status)
} else {
primary = pkg['state-info'].state as string as PrimaryStatus
}
@@ -49,12 +46,16 @@ function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus {
}
function getHealthStatus(status: Status): HealthStatus | null {
if (status.main.status !== PackageMainStatus.Running) {
if (status.main.status !== PackageMainStatus.Running || !status.main.health) {
return null
}
const values = Object.values(status.main.health)
if (values.some(h => !h.result)) {
return HealthStatus.Waiting
}
if (values.some(h => h.result === 'failure')) {
return HealthStatus.Failure
}
@@ -63,7 +64,7 @@ function getHealthStatus(status: Status): HealthStatus | null {
return HealthStatus.Loading
}
if (values.some(h => !h.result || h.result === 'starting')) {
if (values.some(h => h.result === 'starting')) {
return HealthStatus.Starting
}

View File

@@ -13,7 +13,7 @@ export function dryUpdate(
Object.keys(pkg['current-dependencies'] || {}).some(
pkgId => pkgId === id,
) &&
!emver.satisfies(version, getManifest(pkg).dependencies[id].version),
!emver.satisfies(version, pkg['current-dependencies'][id].versionRange),
)
.map(pkg => getManifest(pkg).title)
}

View File

@@ -3,12 +3,12 @@ import {
DataModel,
InstalledState,
InstallingState,
Manifest,
PackageDataEntry,
PackageState,
UpdatingState,
} from 'src/app/services/patch-db/data-model'
import { firstValueFrom } from 'rxjs'
import { Manifest } from '@start9labs/marketplace'
export async function getPackage(
patch: PatchDB<DataModel>,

View File

@@ -1,8 +1,8 @@
import { PackageDataEntry } from '../services/patch-db/data-model'
import { getManifest } from './get-package-data'
export function hasCurrentDeps(pkg: PackageDataEntry): boolean {
return !!Object.keys(pkg['current-dependents']).filter(
depId => depId !== getManifest(pkg).id,
).length
export function hasCurrentDeps(
id: string,
pkgs: Record<string, PackageDataEntry>,
): boolean {
return !!Object.values(pkgs).some(pkg => !!pkg['current-dependencies'][id])
}