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/cli": "^0.1.62",
"@swc/core": "^1.3.65", "@swc/core": "^1.3.65",
"@types/node": "^20.11.13", "@types/node": "^20.11.13",
"esbuild": "^0.20.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"typescript": ">5.2" "typescript": ">5.2"
} }
}, },
"../sdk/dist": { "../sdk/dist": {
"name": "@start9labs/start-sdk", "name": "@start9labs/start-sdk",
"version": "0.4.0-rev0.lib0.rc8.beta7", "version": "0.4.0-rev0.lib0.rc8.beta10",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
@@ -41,380 +40,13 @@
"devDependencies": { "devDependencies": {
"@types/jest": "^29.4.0", "@types/jest": "^29.4.0",
"jest": "^29.4.3", "jest": "^29.4.3",
"prettier": "^3.2.5",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsx": "^4.7.1", "tsx": "^4.7.1",
"typescript": "^5.0.4" "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": { "node_modules/@iarna/toml": {
"version": "2.2.5", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
@@ -1304,44 +936,6 @@
"node": ">= 0.4" "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": { "node_modules/esbuild-plugin-resolve": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-resolve/-/esbuild-plugin-resolve-2.0.0.tgz", "resolved": "https://registry.npmjs.org/esbuild-plugin-resolve/-/esbuild-plugin-resolve-2.0.0.tgz",
@@ -2965,167 +2559,6 @@
} }
}, },
"dependencies": { "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": { "@iarna/toml": {
"version": "2.2.5", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
@@ -3186,6 +2619,7 @@
"@types/jest": "^29.4.0", "@types/jest": "^29.4.0",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"jest": "^29.4.3", "jest": "^29.4.3",
"prettier": "^3.2.5",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"ts-matches": "^5.4.1", "ts-matches": "^5.4.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
@@ -3732,37 +3166,6 @@
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" "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": { "esbuild-plugin-resolve": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-resolve/-/esbuild-plugin-resolve-2.0.0.tgz", "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 }) await fs.mkdir(path, { recursive: true })
const volumeMount = volumes[mount] const volumeMount = volumes[mount]
if (volumeMount.type === "data") { 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") { } 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") { } else if (volumeMount.type === "certificate") {
volumeMount volumeMount
const certChain = await effects.getSslCertificate({ const certChain = await effects.getSslCertificate({
@@ -56,7 +62,7 @@ export class DockerProcedureContainer {
location: path, location: path,
target: { target: {
packageId: volumeMount["package-id"], packageId: volumeMount["package-id"],
path: volumeMount.path, subpath: volumeMount.path,
readonly: volumeMount.readonly, readonly: volumeMount.readonly,
volumeId: volumeMount["volume-id"], volumeId: volumeMount["volume-id"],
}, },

View File

@@ -2,8 +2,7 @@ import { PolyfillEffects } from "./polyfillEffects"
import { DockerProcedureContainer } from "./DockerProcedureContainer" import { DockerProcedureContainer } from "./DockerProcedureContainer"
import { SystemForEmbassy } from "." import { SystemForEmbassy } from "."
import { HostSystemStartOs } from "../../HostSystemStartOs" import { HostSystemStartOs } from "../../HostSystemStartOs"
import { util, Daemons, types as T } from "@start9labs/start-sdk" import { Daemons, T, daemons } from "@start9labs/start-sdk"
import { exec } from "child_process"
const EMBASSY_HEALTH_INTERVAL = 15 * 1000 const EMBASSY_HEALTH_INTERVAL = 15 * 1000
const EMBASSY_PROPERTIES_LOOP = 30 * 1000 const EMBASSY_PROPERTIES_LOOP = 30 * 1000
@@ -39,7 +38,6 @@ export class MainLoop {
private async constructMainEvent() { private async constructMainEvent() {
const { system, effects } = this const { system, effects } = this
const utils = util.createUtils(effects)
const currentCommand: [string, ...string[]] = [ const currentCommand: [string, ...string[]] = [
system.manifest.main.entrypoint, system.manifest.main.entrypoint,
...system.manifest.main.args, ...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, this.system.manifest.main.image,
currentCommand, 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 * as fs from "fs/promises"
import { PolyfillEffects } from "./polyfillEffects" import { PolyfillEffects } from "./polyfillEffects"
import { Duration, duration } from "../../../Models/Duration" import { Duration, duration } from "../../../Models/Duration"
import { System } from "../../../Interfaces/System" import { System } from "../../../Interfaces/System"
import { matchManifest, Manifest, Procedure } from "./matchManifest" import { matchManifest, Manifest } from "./matchManifest"
import * as childProcess from "node:child_process" import * as childProcess from "node:child_process"
import { DockerProcedureContainer } from "./DockerProcedureContainer" import { DockerProcedureContainer } from "./DockerProcedureContainer"
import { promisify } from "node:util" import { promisify } from "node:util"
@@ -22,6 +22,9 @@ import {
any, any,
tuple, tuple,
number, number,
anyOf,
deferred,
Parser,
} from "ts-matches" } from "ts-matches"
import { HostSystemStartOs } from "../../HostSystemStartOs" import { HostSystemStartOs } from "../../HostSystemStartOs"
import { JsonPath, unNestPath } from "../../../Models/JsonPath" 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_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig"
const matchPackagePropertyObject = object({ export type PackagePropertiesV2 = {
value: any, [name: string]: PackagePropertyObject | PackagePropertyString
type: literal("object"), }
description: string, export type PackagePropertyString = {
}) type: "string"
description?: string
const matchPackagePropertyString = object( value: string
{ /** Let's the ui make this copyable button */
type: literal("string"), 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, description: string,
value: string, })
copyable: boolean,
qr: boolean, const matchPackagePropertyString: Parser<unknown, PackagePropertyString> =
masked: boolean, object(
}, {
["copyable", "description", "qr", "masked"], 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({ const matchProperties = object({
version: literal(2), version: literal(2),
data: any, data: matchPackageProperties,
}) })
type ExportUi = { type ExportUi = {
value: string values: { [key: string]: any }
title: string expose: { [key: string]: T.ExposeUiPathsAll }
description?: string | undefined
masked?: boolean | undefined
copyable?: boolean | undefined
qr?: boolean | undefined
} }
function propertiesToExportUi(properties: unknown): ExportUi[] { function propertiesToExportUi(
if (!object.test(properties)) return [] properties: PackagePropertiesV2,
const paths: ExportUi[] = [] previousPath = "",
for (const key in properties) { ): ExportUi {
const value: unknown = (properties as any)[key] const exportUi: ExportUi = {
if (matchPackagePropertyObject.test(value)) { values: {},
paths.push(...propertiesToExportUi(value)) 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 continue
} }
if (!matchPackagePropertyString.test(value)) continue exportUi.values[key] = value.value
paths.push({ exportUi.expose[key] = {
value: value.value, type: "string",
title: key, path,
description: value.description, description: value.description ?? null,
masked: value.masked, masked: value.masked ?? false,
copyable: value.copyable, copyable: value.copyable ?? null,
qr: value.qr, qr: value.qr ?? null,
}) }
} }
return paths return exportUi
} }
export class SystemForEmbassy implements System { export class SystemForEmbassy implements System {
@@ -455,14 +494,9 @@ export class SystemForEmbassy implements System {
const exposeUis = propertiesToExportUi(properties.data) const exposeUis = propertiesToExportUi(properties.data)
await effects.store.set<any, any>({ await effects.store.set<any, any>({
path: "/properties", path: "/properties",
value: exposeUis.map((x) => x.value), value: exposeUis.values,
})
await effects.exposeUi({
paths: exposeUis.map((x, i) => ({
...x,
path: `/properties/${i}`,
})) as any[],
}) })
await effects.exposeUi(exposeUis.expose)
} else if (setConfigValue.type === "script") { } else if (setConfigValue.type === "script") {
const moduleCode = this.moduleCode const moduleCode = this.moduleCode
const method = moduleCode.properties const method = moduleCode.properties
@@ -479,14 +513,9 @@ export class SystemForEmbassy implements System {
const exposeUis = propertiesToExportUi(properties.data) const exposeUis = propertiesToExportUi(properties.data)
await effects.store.set<any, any>({ await effects.store.set<any, any>({
path: "/properties", path: "/properties",
value: exposeUis.map((x) => x.value), value: exposeUis.values,
})
await effects.exposeUi({
paths: exposeUis.map((x, i) => ({
...x,
path: `/properties/${i}`,
})) as any[],
}) })
await effects.exposeUi(exposeUis.expose)
} }
} }
private async health( private async health(
@@ -991,7 +1020,6 @@ async function updateConfig(
) { ) {
if (!dictionary([string, unknown]).test(spec)) return if (!dictionary([string, unknown]).test(spec)) return
if (!dictionary([string, unknown]).test(mutConfigValue)) return if (!dictionary([string, unknown]).test(mutConfigValue)) return
const utils = util.createUtils(effects)
for (const key in spec) { for (const key in spec) {
const specValue = spec[key] const specValue = spec[key]
@@ -1020,8 +1048,8 @@ async function updateConfig(
if (matchPointerPackage.test(specValue)) { if (matchPointerPackage.test(specValue)) {
if (specValue.target === "tor-key") if (specValue.target === "tor-key")
throw new Error("This service uses an unsupported target TorKey") throw new Error("This service uses an unsupported target TorKey")
const filled = await utils.serviceInterface const filled = await utils
.get({ .getServiceInterface(effects, {
packageId: specValue["package-id"], packageId: specValue["package-id"],
id: specValue.interface, id: specValue.interface,
}) })

View File

@@ -3,23 +3,18 @@ import * as oet from "./oldEmbassyTypes"
import { Volume } from "../../../Models/Volume" import { Volume } from "../../../Models/Volume"
import * as child_process from "child_process" import * as child_process from "child_process"
import { promisify } from "util" import { promisify } from "util"
import { util, Utils } from "@start9labs/start-sdk" import { startSdk } from "@start9labs/start-sdk"
import { Manifest } from "./matchManifest"
import { HostSystemStartOs } from "../../HostSystemStartOs" import { HostSystemStartOs } from "../../HostSystemStartOs"
import "isomorphic-fetch" import "isomorphic-fetch"
import { Manifest } from "./matchManifest"
const { createUtils } = util
const execFile = promisify(child_process.execFile) const execFile = promisify(child_process.execFile)
export class PolyfillEffects implements oet.Effects { export class PolyfillEffects implements oet.Effects {
private utils: Utils<any, any>
constructor( constructor(
readonly effects: HostSystemStartOs, readonly effects: HostSystemStartOs,
private manifest: Manifest, private manifest: Manifest,
) { ) {}
this.utils = createUtils(effects as any)
}
async writeFile(input: { async writeFile(input: {
path: string path: string
volumeId: string volumeId: string
@@ -99,9 +94,14 @@ export class PolyfillEffects implements oet.Effects {
args?: string[] | undefined args?: string[] | undefined
timeoutMillis?: number | undefined timeoutMillis?: number | undefined
}): Promise<oet.ResultType<string>> { }): Promise<oet.ResultType<string>> {
return this.utils return startSdk
.runCommand(this.manifest.main.image, [command, ...(args || [])], {}) .runCommand(
.then((x) => ({ this.effects,
this.manifest.main.image,
[command, ...(args || [])],
{},
)
.then((x: any) => ({
stderr: x.stderr.toString(), stderr: x.stderr.toString(),
stdout: x.stdout.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. // 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. // 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. // 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>, pub icon: DataUrl<'static>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[serde(tag = "kind")] #[serde(tag = "kind")]
pub enum CurrentDependencyInfo { 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 { impl Drop for NetService {

View File

@@ -1,5 +1,6 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::ffi::OsString; use std::ffi::OsString;
use std::net::Ipv4Addr;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
@@ -259,16 +260,24 @@ enum AllowedStatuses {
OnlyRunning, OnlyRunning,
OnlyStopped, OnlyStopped,
Any, Any,
Disabled,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ExportActionParams { 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, name: String,
description: String, description: String,
id: String, warning: Option<String>,
disabled: bool,
#[ts(type = "{[key: string]: any}")] #[ts(type = "{[key: string]: any}")]
input: Value, input: Value,
allowed_statuses: AllowedStatuses, allowed_statuses: AllowedStatuses,
@@ -335,8 +344,17 @@ async fn get_system_smtp(
) -> Result<Value, Error> { ) -> Result<Value, Error> {
todo!() todo!()
} }
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Value, Error> { async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr, Error> {
todo!() 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( async fn get_service_port_forward(
context: EffectContext, context: EffectContext,
@@ -764,8 +782,8 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result<Value, Error
let package = peeked let package = peeked
.as_public() .as_public()
.as_package_data() .as_package_data()
.as_idx(&package_id) .as_idx(package_id)
.or_not_found(&package_id)? .or_not_found(package_id)?
.as_status() .as_status()
.as_configured() .as_configured()
.de()?; .de()?;
@@ -1069,28 +1087,39 @@ enum DependencyKind {
} }
#[derive(Debug, Clone, Deserialize, Serialize, TS)] #[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase", tag = "kind")]
#[ts(export)] #[ts(export)]
struct DependencyRequirement { enum DependencyRequirement {
#[ts(type = "string")] Running {
id: PackageId, #[ts(type = "string")]
kind: DependencyKind, id: PackageId,
#[serde(default)] #[ts(type = "string[]")]
#[ts(type = "string[]")] #[serde(rename = "healthChecks")]
health_checks: BTreeSet<HealthCheckId>, 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 // filebrowser:exists,bitcoind:running:foo+bar+baz
impl FromStr for DependencyRequirement { impl FromStr for DependencyRequirement {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once(':') { match s.split_once(':') {
Some((id, "e")) | Some((id, "exists")) => Ok(Self { Some((id, "e")) | Some((id, "exists")) => Ok(Self::Exists {
id: id.parse()?, id: id.parse()?,
kind: DependencyKind::Exists, url: "".to_string(),
health_checks: BTreeSet::new(), version_spec: "*".to_string(),
}), }),
Some((id, rest)) => { Some((id, rest)) => {
let health_checks = match rest.split_once(":") { let health_checks = match rest.split_once(':') {
Some(("r", rest)) | Some(("running", rest)) => rest Some(("r", rest)) | Some(("running", rest)) => rest
.split('+') .split('+')
.map(|id| id.parse().map_err(Error::from)) .map(|id| id.parse().map_err(Error::from))
@@ -1107,16 +1136,18 @@ impl FromStr for DependencyRequirement {
)), )),
}, },
}?; }?;
Ok(Self { Ok(Self::Running {
id: id.parse()?, id: id.parse()?,
kind: DependencyKind::Running,
health_checks, health_checks,
url: "".to_string(),
version_spec: "*".to_string(),
}) })
} }
None => Ok(Self { None => Ok(Self::Running {
id: s.parse()?, id: s.parse()?,
kind: DependencyKind::Running,
health_checks: BTreeSet::new(), health_checks: BTreeSet::new(),
url: "".to_string(),
version_spec: "*".to_string(),
}), }),
} }
} }
@@ -1148,23 +1179,19 @@ async fn set_dependencies(
let dependencies = CurrentDependencies( let dependencies = CurrentDependencies(
dependencies dependencies
.into_iter() .into_iter()
.map( .map(|dependency| match dependency {
|DependencyRequirement { DependencyRequirement::Exists {
id, id,
kind, url,
health_checks, version_spec,
}| { } => (id, CurrentDependencyInfo::Exists),
( DependencyRequirement::Running {
id, id,
match kind { health_checks,
DependencyKind::Exists => CurrentDependencyInfo::Exists, url,
DependencyKind::Running => { version_spec,
CurrentDependencyInfo::Running { health_checks } } => (id, CurrentDependencyInfo::Running { health_checks }),
} })
},
)
},
)
.collect(), .collect(),
); );
for (dep, entry) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { 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, PackageId,
EnsureStorePath, EnsureStorePath,
ExtractStore, ExtractStore,
DaemonReturned,
ValidIfNoStupidEscape, ValidIfNoStupidEscape,
} from "./types" } from "./types"
import * as patterns from "./util/patterns" import * as patterns from "./util/patterns"
@@ -50,7 +49,11 @@ import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall"
import { setupMain } from "./mainFn" import { setupMain } from "./mainFn"
import { defaultTrigger } from "./trigger/defaultTrigger" import { defaultTrigger } from "./trigger/defaultTrigger"
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger" import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
import setupConfig, { Read, Save } from "./config/setupConfig" import setupConfig, {
DependenciesReceipt,
Read,
Save,
} from "./config/setupConfig"
import { import {
InterfacesReceipt, InterfacesReceipt,
SetInterfaces, SetInterfaces,
@@ -72,6 +75,9 @@ import { getStore } from "./store/getStore"
import { CommandOptions, MountOptions, Overlay } from "./util/Overlay" import { CommandOptions, MountOptions, Overlay } from "./util/Overlay"
import { splitCommand } from "./util/splitCommand" import { splitCommand } from "./util/splitCommand"
import { Mounts } from "./mainFn/Mounts" import { Mounts } from "./mainFn/Mounts"
import { Dependency } from "./Dependency"
import * as T from "./types"
import { Checker, EmVer } from "./emverLite/mod"
// prettier-ignore // prettier-ignore
type AnyNeverCond<T extends any[], Then, Else> = 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>) { 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 { return {
serviceInterface: { serviceInterface: {
getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) => getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) =>
@@ -184,7 +204,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
| Config<any, never>, | Config<any, never>,
Type extends Record<string, any> = ExtractConfigType<ConfigType>, Type extends Record<string, any> = ExtractConfigType<ConfigType>,
>( >(
metaData: Omit<ActionMetadata, "input"> & { id: string,
metadata: Omit<ActionMetadata, "input"> & {
input: Config<Type, Store> | Config<Type, never> input: Config<Type, Store> | Config<Type, never>
}, },
fn: (options: { fn: (options: {
@@ -192,8 +213,13 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
input: Type input: Type
}) => Promise<ActionResult>, }) => Promise<ActionResult>,
) => { ) => {
const { input, ...rest } = metaData const { input, ...rest } = metadata
return createAction<Manifest, Store, ConfigType, Type>(rest, fn, input) return createAction<Manifest, Store, ConfigType, Type>(
id,
rest,
fn,
input,
)
}, },
getSystemSmtp: <E extends Effects>(effects: E) => getSystemSmtp: <E extends Effects>(effects: E) =>
removeConstType<E>()(new GetSystemSmtp(effects)), removeConstType<E>()(new GetSystemSmtp(effects)),
@@ -205,16 +231,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
mounts?: { path: string; options: MountOptions }[] mounts?: { path: string; options: MountOptions }[]
}, },
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => { ): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
const commands = splitCommand(command) return runCommand<Manifest>(effects, imageId, command, options)
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()
}
}, },
createDynamicAction: < createDynamicAction: <
@@ -224,7 +241,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
| Config<any, never>, | Config<any, never>,
Type extends Record<string, any> = ExtractConfigType<ConfigType>, Type extends Record<string, any> = ExtractConfigType<ConfigType>,
>( >(
metaData: (options: { id: string,
metadata: (options: {
effects: Effects effects: Effects
}) => MaybePromise<Omit<ActionMetadata, "input">>, }) => MaybePromise<Omit<ActionMetadata, "input">>,
fn: (options: { fn: (options: {
@@ -234,7 +252,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
input: Config<Type, Store> | Config<Type, never>, input: Config<Type, Store> | Config<Type, never>,
) => { ) => {
return createAction<Manifest, Store, ConfigType, Type>( return createAction<Manifest, Store, ConfigType, Type>(
metaData, id,
metadata,
fn, fn,
input, input,
) )
@@ -242,6 +261,11 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
HealthCheck: { HealthCheck: {
of: healthCheck, of: healthCheck,
}, },
Dependency: {
of(data: Dependency["data"]) {
return new Dependency({ ...data })
},
},
healthCheck: { healthCheck: {
checkPortListening, checkPortListening,
checkWebUrl, checkWebUrl,
@@ -284,15 +308,51 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
Store, Store,
Input, Input,
any any
> > | null
}, },
) => setupDependencyConfig<Store, Input, Manifest>(config, autoConfigs), ) => 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, setupExports: (fn: SetupExports<Store>) => fn,
setupInit: ( setupInit: (
migrations: Migrations<Manifest, Store>, migrations: Migrations<Manifest, Store>,
install: Install<Manifest, Store>, install: Install<Manifest, Store>,
uninstall: Uninstall<Manifest, Store>, uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>, setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setDependencies: (options: {
effects: Effects
input: any
}) => Promise<DependenciesReceipt>,
setupExports: SetupExports<Store>, setupExports: SetupExports<Store>,
) => ) =>
setupInit<Manifest, Store>( setupInit<Manifest, Store>(
@@ -301,6 +361,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
uninstall, uninstall,
setInterfaces, setInterfaces,
setupExports, setupExports,
setDependencies,
), ),
setupInstall: (fn: InstallFn<Manifest, Store>) => Install.of(fn), setupInstall: (fn: InstallFn<Manifest, Store>) => Install.of(fn),
setupInterfaces: < setupInterfaces: <
@@ -355,6 +416,9 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
spec: Spec, spec: Spec,
) => Config.of<Spec, Store>(spec), ) => Config.of<Spec, Store>(spec),
}, },
Checker: {
parse: Checker.parse,
},
Daemons: { Daemons: {
of(config: { of(config: {
effects: Effects effects: Effects
@@ -369,13 +433,17 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
LocalConfig extends Record<string, any>, LocalConfig extends Record<string, any>,
RemoteConfig extends Record<string, any>, RemoteConfig extends Record<string, any>,
>({ >({
localConfig, localConfigSpec,
remoteConfig, remoteConfigSpec,
dependencyConfig, dependencyConfig,
update, update,
}: { }: {
localConfig: Config<LocalConfig, Store> | Config<LocalConfig, never> localConfigSpec:
remoteConfig: Config<RemoteConfig, any> | Config<RemoteConfig, never> | Config<LocalConfig, Store>
| Config<LocalConfig, never>
remoteConfigSpec:
| Config<RemoteConfig, any>
| Config<RemoteConfig, never>
dependencyConfig: (options: { dependencyConfig: (options: {
effects: Effects effects: Effects
localConfig: LocalConfig localConfig: LocalConfig
@@ -390,6 +458,10 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
>(dependencyConfig, update) >(dependencyConfig, update)
}, },
}, },
EmVer: {
from: EmVer.from,
parse: EmVer.parse,
},
List: { List: {
text: List.text, text: List.text,
number: List.number, 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>, Type extends Record<string, any> = ExtractConfigType<ConfigType>,
> { > {
private constructor( private constructor(
public readonly myMetaData: MaybeFn< public readonly id: string,
public readonly myMetadata: MaybeFn<
Manifest, Manifest,
Store, Store,
Omit<ActionMetadata, "input"> Omit<ActionMetadata, "input">
@@ -37,12 +38,14 @@ export class CreatedAction<
| Config<any, never>, | Config<any, never>,
Type extends Record<string, any> = ExtractConfigType<ConfigType>, 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>, fn: (options: { effects: Effects; input: Type }) => Promise<ActionResult>,
inputConfig: Config<Type, Store> | Config<Type, never>, inputConfig: Config<Type, Store> | Config<Type, never>,
) { ) {
return new CreatedAction<Manifest, Store, ConfigType, Type>( return new CreatedAction<Manifest, Store, ConfigType, Type>(
metaData, id,
metadata,
fn, fn,
inputConfig as Config<Type, Store>, inputConfig as Config<Type, Store>,
) )
@@ -62,15 +65,15 @@ export class CreatedAction<
}) })
} }
async metaData(options: { effects: Effects }) { async metadata(options: { effects: Effects }) {
if (this.myMetaData instanceof Function) if (this.myMetadata instanceof Function)
return await this.myMetaData(options) return await this.myMetadata(options)
return this.myMetaData return this.myMetadata
} }
async ActionMetadata(options: { effects: Effects }): Promise<ActionMetadata> { async ActionMetadata(options: { effects: Effects }): Promise<ActionMetadata> {
return { return {
...(await this.metaData(options)), ...(await this.metadata(options)),
input: await this.input.build(options), input: await this.input.build(options),
} }
} }

View File

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

View File

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

View File

@@ -163,7 +163,7 @@ export class EmVer {
} }
toString() { 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 * @returns
*/ */
static parse(range: string | Checker): Checker { static parse(range: string | Checker): Checker {
console.log(`Parser (${range})`)
if (range instanceof Checker) { if (range instanceof Checker) {
return range return range
} }
@@ -193,10 +194,12 @@ export class Checker {
return new Checker((version) => { return new Checker((version) => {
EmVer.from(version) EmVer.from(version)
return true return true
}) }, range)
} }
if (range.startsWith("!!")) return Checker.parse(range.substring(2))
if (range.startsWith("!")) { 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) const starSubMatches = starSub.exec(range)
if (starSubMatches != null) { if (starSubMatches != null) {
@@ -210,7 +213,7 @@ export class Checker {
!v.greaterThan(emVarUpper) && !v.greaterThan(emVarUpper) &&
!v.equals(emVarUpper) !v.equals(emVarUpper)
) )
}) }, range)
} }
switch (range.substring(0, 2)) { switch (range.substring(0, 2)) {
@@ -219,14 +222,14 @@ export class Checker {
return new Checker((version) => { return new Checker((version) => {
const v = EmVer.from(version) const v = EmVer.from(version)
return v.greaterThanOrEqual(emVar) return v.greaterThanOrEqual(emVar)
}) }, range)
} }
case "<=": { case "<=": {
const emVar = EmVer.parse(range.substring(2)) const emVar = EmVer.parse(range.substring(2))
return new Checker((version) => { return new Checker((version) => {
const v = EmVer.from(version) const v = EmVer.from(version)
return v.lessThanOrEqual(emVar) return v.lessThanOrEqual(emVar)
}) }, range)
} }
} }
@@ -236,21 +239,21 @@ export class Checker {
return new Checker((version) => { return new Checker((version) => {
const v = EmVer.from(version) const v = EmVer.from(version)
return v.greaterThan(emVar) return v.greaterThan(emVar)
}) }, range)
} }
case "<": { case "<": {
const emVar = EmVer.parse(range.substring(1)) const emVar = EmVer.parse(range.substring(1))
return new Checker((version) => { return new Checker((version) => {
const v = EmVer.from(version) const v = EmVer.from(version)
return v.lessThan(emVar) return v.lessThan(emVar)
}) }, range)
} }
case "=": { case "=": {
const emVar = EmVer.parse(range.substring(1)) const emVar = EmVer.parse(range.substring(1))
return new Checker((version) => { return new Checker((version) => {
const v = EmVer.from(version) const v = EmVer.from(version)
return v.equals(emVar) return v.equals(emVar)
}) }, `=${emVar.toString()}`)
} }
} }
throw new Error("Couldn't parse range: " + range) throw new Error("Couldn't parse range: " + range)
@@ -261,40 +264,53 @@ export class Checker {
* a pattern * a pattern
*/ */
public readonly check: (value: ValidEmVer | EmVer) => boolean, 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 * Used when we want the `and` condition with another checker
*/ */
public and(...others: (Checker | string)[]): Checker { public and(...others: (Checker | string)[]): Checker {
return new Checker((value) => { const othersCheck = others.map(Checker.parse)
if (!this.check(value)) { return new Checker(
return false (value) => {
} if (!this.check(value)) {
for (const other of others) {
if (!Checker.parse(other).check(value)) {
return false return false
} }
} for (const other of othersCheck) {
return true if (!other.check(value)) {
}) return false
}
}
return true
},
othersCheck.map((x) => x._range).join(" && "),
)
} }
/** /**
* Used when we want the `or` condition with another checker * Used when we want the `or` condition with another checker
*/ */
public or(...others: (Checker | string)[]): Checker { public or(...others: (Checker | string)[]): Checker {
return new Checker((value) => { const othersCheck = others.map(Checker.parse)
if (this.check(value)) { return new Checker(
return true (value) => {
} if (this.check(value)) {
for (const other of others) {
if (Checker.parse(other).check(value)) {
return true return true
} }
} for (const other of othersCheck) {
return false if (other.check(value)) {
}) return true
}
}
return false
},
othersCheck.map((x) => x._range).join(" || "),
)
} }
/** /**
@@ -302,6 +318,7 @@ export class Checker {
* @returns * @returns
*/ */
public not(): Checker { 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 configBuilder from "./config/builder"
export * as configTypes from "./config/configTypes" export * as configTypes from "./config/configTypes"
export * as dependencyConfig from "./dependencyConfig" export * as dependencyConfig from "./dependencyConfig"
export * as daemons from "./mainFn/Daemons"
export * as health from "./health" export * as health from "./health"
export * as healthFns from "./health/checkFns" export * as healthFns from "./health/checkFns"
export * as inits from "./inits" export * as inits from "./inits"
@@ -17,9 +18,10 @@ export * as mainFn from "./mainFn"
export * as manifest from "./manifest" export * as manifest from "./manifest"
export * as toml from "@iarna/toml" export * as toml from "@iarna/toml"
export * as types from "./types" export * as types from "./types"
export * as util from "./util" export * as T from "./types"
export * as yaml from "yaml" export * as yaml from "yaml"
export * as startSdk from "./StartSdk"
export * as utils from "./util"
export * as matches from "ts-matches" export * as matches from "ts-matches"
export * as YAML from "yaml" export * as YAML from "yaml"
export * as TOML from "@iarna/toml" 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 }) => export type SetupExports<Store> = (opts: { effects: Effects }) =>
| { | {
ui: { [k: string]: ExposeUiPaths<Store> } ui: { [k: string]: ExposeUiPaths<Store> }
services: ExposeServicePaths<Store> services: ExposeServicePaths<Store>["paths"]
} }
| Promise<{ | Promise<{
ui: { [k: string]: ExposeUiPaths<Store> } ui: { [k: string]: ExposeUiPaths<Store> }
services: ExposeServicePaths<Store> services: ExposeServicePaths<Store>["paths"]
}> }>
export const setupExports = <Store>(fn: (opts: SetupExports<Store>) => void) => 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 { SetInterfaces } from "../interfaces/setupInterfaces"
import { SDKManifest } from "../manifest/ManifestTypes" import { SDKManifest } from "../manifest/ManifestTypes"
import { ExpectedExports, ExposeUiPaths, ExposeUiPathsAll } from "../types" import {
Effects,
ExpectedExports,
ExposeUiPaths,
ExposeUiPathsAll,
} from "../types"
import { Migrations } from "./migrations/setupMigrations" import { Migrations } from "./migrations/setupMigrations"
import { SetupExports } from "./setupExports" import { SetupExports } from "./setupExports"
import { Install } from "./setupInstall" import { Install } from "./setupInstall"
@@ -12,6 +18,10 @@ export function setupInit<Manifest extends SDKManifest, Store>(
uninstall: Uninstall<Manifest, Store>, uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>, setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setupExports: SetupExports<Store>, setupExports: SetupExports<Store>,
setDependencies: (options: {
effects: Effects
input: any
}) => Promise<DependenciesReceipt>,
): { ): {
init: ExpectedExports.init init: ExpectedExports.init
uninit: ExpectedExports.uninit uninit: ExpectedExports.uninit
@@ -25,8 +35,9 @@ export function setupInit<Manifest extends SDKManifest, Store>(
input: null, input: null,
}) })
const { services, ui } = await setupExports(opts) const { services, ui } = await setupExports(opts)
await opts.effects.exposeForDependents(services) await opts.effects.exposeForDependents({ paths: services })
await opts.effects.exposeUi(forExpose(ui)) await opts.effects.exposeUi(forExpose(ui))
await setDependencies({ effects: opts.effects, input: null })
}, },
uninit: async (opts) => { uninit: async (opts) => {
await migrations.uninit(opts) await migrations.uninit(opts)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,8 @@ import { BindOptions, Scheme } from "./interfaces/Host"
import { Daemons } from "./mainFn/Daemons" import { Daemons } from "./mainFn/Daemons"
import { UrlString } from "./util/getServiceInterface" import { UrlString } from "./util/getServiceInterface"
export { SDKManifest } from "./manifest/ManifestTypes"
export type ExportedAction = (options: { export type ExportedAction = (options: {
effects: Effects effects: Effects
input?: Record<string, unknown> 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 /** Auto configure is used to make sure that other dependencies have the values t
* that this service could use. * that this service could use.
*/ */
export type dependencyConfig = Record<PackageId, DependencyConfig> export type dependencyConfig = Record<PackageId, DependencyConfig | null>
} }
export type TimeMs = number export type TimeMs = number
export type VersionString = string export type VersionString = string
@@ -161,9 +163,10 @@ export type DaemonReturned = {
export type ActionMetadata = { export type ActionMetadata = {
name: string name: string
description: string description: string
id: string warning: string | null
input: InputSpec 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 * 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 * @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. * Remove an action that was exported. Used problably during main or during setConfig.
*/ */
@@ -598,7 +601,8 @@ export type KnownError =
export type Dependency = { export type Dependency = {
id: PackageId id: PackageId
kind: DependencyKind versionSpec: string
url: string
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] }) } & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
export type Dependencies = Array<Dependency> export type Dependencies = Array<Dependency>

View File

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

View File

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

View File

@@ -2143,15 +2143,6 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "id-pool"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d0df4d8a768821ee4aa2e0353f67125c4586f0e13adbf95b8ebbf8d8fdb344"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" 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", "monaco-editor": "^0.33.0",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"ng-qrcode": "^17.0.0", "ng-qrcode": "^17.0.0",
"node-jose": "^2.1.1", "node-jose": "^2.2.0",
"patch-db-client": "file:../patch-db/client", "patch-db-client": "file:../patch-db/client",
"pbkdf2": "^3.1.2", "pbkdf2": "^3.1.2",
"rxjs": "^7.5.6", "rxjs": "^7.5.6",

View File

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

View File

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

View File

@@ -1,22 +1,19 @@
<div class="outer-container"> <div class="outer-container">
<div class="inner-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="wrapper-margin">
<div class="inner-container-title"> <div class="inner-container-title">
<span> <span>
{{ getTitle(dep.key) }} {{ pkg['dependency-metadata'][dep.key].title || dep.key }}
</span> </span>
<p> <p>
<ng-container [ngSwitch]="dep.value.requirement.type"> @if (dep.value.optional) {
<span *ngSwitchCase="'required'">(required)</span> <span>(optional)</span>
<span *ngSwitchCase="'opt-out'">(required by default)</span> } @else {
<span *ngSwitchCase="'opt-in'">(optional)</span> <span>(required)</span>
</ng-container> }
</p> </p>
</div> </div>
<span class="inner-container-version">
{{ dep.value.version | displayEmver }}
</span>
<span class="inner-container-description"> <span class="inner-container-description">
{{ dep.value.description }} {{ dep.value.description }}
</span> </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 { &-description {
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.25rem; line-height: 1.25rem;

View File

@@ -14,14 +14,4 @@ export class DependenciesComponent {
@Input({ required: true }) @Input({ required: true })
dep!: KeyValue<string, Dependency> 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 './pages/show/hero/hero.component'
export * from './pipes/filter-packages.pipe' 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'
export * from './components/store-icon/store-icon.component.module' export * from './components/store-icon/store-icon.component.module'

View File

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

View File

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

View File

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

View File

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

View File

@@ -866,7 +866,7 @@ export class MockApiService extends ApiService {
setTimeout(async () => { setTimeout(async () => {
for (let i = 0; i < ids.length; i++) { for (let i = 0; i < ids.length; i++) {
const id = ids[i] const id = ids[i]
const appPath = `/package-data/${id}/installed/status/main/status` const appPath = `/package-data/${id}/status/main/status`
const appPatch = [ const appPatch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
@@ -1032,7 +1032,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/configured`, path: `/package-data/${params.id}/status/configured`,
value: true, value: true,
}, },
] ]
@@ -1079,7 +1079,7 @@ export class MockApiService extends ApiService {
} }
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> { 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) await pauseFor(2000)
@@ -1128,7 +1128,7 @@ export class MockApiService extends ApiService {
): Promise<RR.RestartPackageRes> { ): Promise<RR.RestartPackageRes> {
// first enact stop // first enact stop
await pauseFor(2000) await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main` const path = `/package-data/${params.id}/status/main`
setTimeout(async () => { setTimeout(async () => {
const patch2: Operation<any>[] = [ 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' import { Mock } from './api.fixures'
export const mockPatchData: DataModel = { export const mockPatchData: DataModel = {
@@ -31,18 +36,49 @@ export const mockPatchData: DataModel = {
id: 'abcdefgh', id: 'abcdefgh',
version: '0.3.5.1', version: '0.3.5.1',
country: 'us', country: 'us',
ui: { ui: [
lanHostname: 'adjective-noun.local', {
torHostname: 'myveryownspecialtoraddress.onion', kind: 'ip',
ipInfo: { networkInterfaceId: 'elan0',
eth0: { public: false,
wireless: false, hostname: {
ipv4: '10.0.0.1', kind: 'local',
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD', 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: { network: {
domains: [], domains: [],
start9ToSubdomain: null, start9ToSubdomain: null,
@@ -109,11 +145,49 @@ export const mockPatchData: DataModel = {
}, },
'package-data': { 'package-data': {
bitcoind: { bitcoind: {
...Mock.bitcoind, 'state-info': {
manifest: { state: PackageState.Installed,
...Mock.bitcoind.manifest, manifest: {
version: '0.19.0', ...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': { 'service-interfaces': {
ui: { ui: {
id: 'ui', id: 'ui',
@@ -330,22 +404,20 @@ export const mockPatchData: DataModel = {
}, },
}, },
}, },
'current-dependents': {
lnd: {
pointers: [],
'health-checks': [],
},
},
'current-dependencies': {}, 'current-dependencies': {},
'dependency-info': {}, 'dependency-info': {},
'marketplace-url': 'https://registry.start9.com/', 'marketplace-url': 'https://registry.start9.com/',
'developer-key': 'developer-key', 'developer-key': 'developer-key',
'has-config': true,
outboundProxy: null,
}, },
lnd: { lnd: {
...Mock.lnd, 'state-info': {
manifest: { state: PackageState.Installed,
...Mock.lnd.manifest, manifest: {
version: '0.11.0', ...Mock.MockManifestLnd,
version: '0.11.0',
},
}, },
icon: '/assets/img/service-icons/lnd.png', icon: '/assets/img/service-icons/lnd.png',
'last-backup': null, 'last-backup': null,
@@ -358,6 +430,7 @@ export const mockPatchData: DataModel = {
'btc-rpc-proxy': 'This is a config unsatisfied error', 'btc-rpc-proxy': 'This is a config unsatisfied error',
}, },
}, },
actions: {},
'service-interfaces': { 'service-interfaces': {
grpc: { grpc: {
id: 'grpc', id: 'grpc',
@@ -568,12 +641,13 @@ export const mockPatchData: DataModel = {
}, },
}, },
}, },
'current-dependents': {},
'current-dependencies': { 'current-dependencies': {
bitcoind: { bitcoind: {
versionRange: '>=26.0.0',
'health-checks': [], 'health-checks': [],
}, },
'btc-rpc-proxy': { 'btc-rpc-proxy': {
versionRange: '>2.0.0',
'health-checks': [], 'health-checks': [],
}, },
}, },
@@ -589,6 +663,8 @@ export const mockPatchData: DataModel = {
}, },
'marketplace-url': 'https://registry.start9.com/', 'marketplace-url': 'https://registry.start9.com/',
'developer-key': 'developer-key', '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 const depManifest = dep['state-info'].manifest
// incorrect version // incorrect version
if ( if (!this.emver.satisfies(depManifest.version, versionRange)) {
!this.emver.satisfies(
depManifest.version,
pkgManifest.dependencies[depId].version,
)
) {
return { return {
type: DependencyErrorType.IncorrectVersion, type: DependencyErrorType.IncorrectVersion,
expected: pkgManifest.dependencies[depId].version, expected: versionRange,
received: depManifest.version, 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 { Url } from '@start9labs/shared'
import { Manifest } from '@start9labs/marketplace' 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 { 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 type ServiceInterfaceWithHostInfo = types.ServiceInterfaceWithHostInfo
export interface DataModel { export interface DataModel {
@@ -174,8 +178,8 @@ export type PackageDataEntry<T extends StateInfo = StateInfo> = {
'state-info': T 'state-info': T
icon: Url icon: Url
status: Status status: Status
actions: Record<string, ActionMetadata>
'last-backup': string | null 'last-backup': string | null
'current-dependents': { [id: string]: CurrentDependencyInfo }
'current-dependencies': { [id: string]: CurrentDependencyInfo } 'current-dependencies': { [id: string]: CurrentDependencyInfo }
'dependency-info': { 'dependency-info': {
[id: string]: { [id: string]: {
@@ -217,18 +221,10 @@ export enum PackageState {
} }
export interface CurrentDependencyInfo { export interface CurrentDependencyInfo {
versionRange: string
'health-checks': string[] // array of health check IDs '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 { export interface Status {
configured: boolean configured: boolean
main: MainStatus main: MainStatus
@@ -260,7 +256,7 @@ export interface MainStatusStarting {
export interface MainStatusRunning { export interface MainStatusRunning {
status: PackageMainStatus.Running status: PackageMainStatus.Running
started: string // UTC date string started: string // UTC date string
health: { [id: string]: HealthCheckResult } health: Record<string, HealthCheckResult>
} }
export interface MainStatusBackingUp { export interface MainStatusBackingUp {
@@ -307,7 +303,6 @@ export interface HealthCheckResultStarting {
export interface HealthCheckResultDisabled { export interface HealthCheckResultDisabled {
result: HealthResult.Disabled result: HealthResult.Disabled
reason: string
} }
export interface HealthCheckResultSuccess { export interface HealthCheckResultSuccess {
@@ -322,7 +317,7 @@ export interface HealthCheckResultLoading {
export interface HealthCheckResultFailure { export interface HealthCheckResultFailure {
result: HealthResult.Failure result: HealthResult.Failure
error: string message: string
} }
export type InstallingInfo = { export type InstallingInfo = {

View File

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

View File

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

View File

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

View File

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