diff --git a/.gitignore b/.gitignore index 47d44a1..97dceb1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ build/ *.nzip *.log .env -package-lock.json dist/ .dist-test/ *.tsbuildinfo +.tangled-ci/ +.release-urls +.release.json diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e7..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/.tangled/workflows/ci.yml b/.tangled/workflows/ci.yml new file mode 100644 index 0000000..635e483 --- /dev/null +++ b/.tangled/workflows/ci.yml @@ -0,0 +1,29 @@ +# Every push and pull request to main: type check and test the app, test the +# workspace packages, then pack the bundle and start it: `--version`, and the +# setup window in a headless browser, which only reports that it loaded once +# every module was found inside the archive. +when: + - event: ["push", "pull_request"] + branch: ["main"] + +engine: microvm +image: nixos + +dependencies: + # bundle and node:vfs need node >= 26.11.1, which nixos-unstable has. + - github:nixos/nixpkgs/nixos-unstable#nodejs_26 + - git + # The smoke test opens the setup window, headless. + - chromium + +steps: + - name: install + command: npm ci --no-audit --no-fund + - name: test + command: npm test + - name: test packages + command: npm run test:packages + - name: pack + command: npm run pack + - name: smoke + command: npm run smoke diff --git a/.tangled/workflows/release.yml b/.tangled/workflows/release.yml new file mode 100644 index 0000000..03986a1 --- /dev/null +++ b/.tangled/workflows/release.yml @@ -0,0 +1,22 @@ +# Release on every pushed version tag (an annotated one: set "version" in +# package.json, commit, git tag -a v, git push --follow-tags). Run by +# hand (tangled run trigger --workflow release.yml), it is a dry run: it builds +# and checks everything, and uploads, attests and publishes nothing. +# See tools/release.sh for what it does and the secrets it needs. +when: + - event: ["push"] + tag: ["v*"] + - event: ["manual"] + +engine: microvm +image: nixos + +dependencies: + - github:nixos/nixpkgs/nixos-unstable#nodejs_26 + - git + - curl + - chromium + +steps: + - name: release + command: ./tools/release.sh diff --git a/ci/policy.json b/ci/policy.json new file mode 100644 index 0000000..50163fe --- /dev/null +++ b/ci/policy.json @@ -0,0 +1,4 @@ +{ + "require": { "attesters": ["published@did:plc:rwypz6edxmzyhob2k3rsl2fw"] }, + "trust": { "attesters": ["did:plc:rwypz6edxmzyhob2k3rsl2fw"] } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..85b3832 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1172 @@ +{ + "name": "davsync", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "davsync", + "version": "0.0.1", + "license": "EUPL-1.2", + "workspaces": [ + "packages/xml", + "packages/observe", + "packages/dav-core", + "packages/dav-client" + ], + "dependencies": { + "@ordner.fyi/dav-client": "*", + "@ordner.fyi/dav-core": "*", + "@ordner.fyi/xml": "*", + "chrome-launcher": "^1.2.2" + }, + "devDependencies": { + "@pipobscure/bundle": "^0.0.20", + "@types/node": "^26.6.4", + "typescript": "^7.0.2" + }, + "engines": { + "node": ">=26.11.1" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "5.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^9.0.0", + "http-proxy-agent": "^9.0.0", + "https-proxy-agent": "^9.0.0", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^10.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@ordner.fyi/dav-client": { + "resolved": "packages/dav-client", + "link": true + }, + "node_modules/@ordner.fyi/dav-core": { + "resolved": "packages/dav-core", + "link": true + }, + "node_modules/@ordner.fyi/observe": { + "resolved": "packages/observe", + "link": true + }, + "node_modules/@ordner.fyi/xml": { + "resolved": "packages/xml", + "link": true + }, + "node_modules/@pipobscure/bundle": { + "version": "0.0.20", + "dev": true, + "license": "EUPL-1.2", + "dependencies": { + "@sigstore/bundle": "^5.0.0", + "@sigstore/protobuf-specs": "^0.5.2", + "@sigstore/sign": "^5.0.0", + "@sigstore/tuf": "^5.0.0", + "@sigstore/verify": "^4.1.2" + }, + "bin": { + "bundle": "bundle.nzip" + }, + "engines": { + "node": ">=26.11.1" + } + }, + "node_modules/@rgrove/parse-xml": { + "version": "4.2.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "4.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^5.0.0", + "@sigstore/core": "^4.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^16.0.0", + "proc-log": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "4.1.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^5.0.0", + "@sigstore/core": "^4.0.1", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.2.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@types/node": { + "version": "26.6.5", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/agent-base": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/cacache": { + "version": "21.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^6.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^14.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/chrome-launcher": { + "version": "1.2.2", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.cjs" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/https-proxy-agent": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ip-address": { + "version": "10.7.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lighthouse-logger": { + "version": "2.0.2", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "marky": "^1.2.2" + } + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "16.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^5.0.0", + "@npmcli/redact": "^5.0.0", + "cacache": "^21.0.0", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^6.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^7.0.0", + "ssri": "^14.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "license": "Apache-2.0" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/p-map": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/proc-log": { + "version": "7.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/ssri": { + "version": "14.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/tuf-js": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@gar/promise-retry": "^1.0.3", + "@tufjs/models": "5.0.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "license": "MIT" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "packages/dav-client": { + "name": "@ordner.fyi/dav-client", + "version": "0.1.0", + "license": "EUPL-1.2", + "dependencies": { + "@ordner.fyi/dav-core": "*", + "@ordner.fyi/xml": "*" + }, + "devDependencies": { + "@types/node": "^26.1.0", + "typescript": "^7.0.2" + }, + "engines": { + "node": ">=26.11.1" + } + }, + "packages/dav-core": { + "name": "@ordner.fyi/dav-core", + "version": "0.1.0", + "license": "EUPL-1.2", + "dependencies": { + "@ordner.fyi/observe": "*", + "@ordner.fyi/xml": "*" + }, + "devDependencies": { + "@types/node": "^26.1.0", + "typescript": "^7.0.2" + }, + "engines": { + "node": ">=26.11.1" + } + }, + "packages/observe": { + "name": "@ordner.fyi/observe", + "version": "0.1.0", + "license": "EUPL-1.2", + "devDependencies": { + "@types/node": "^26.1.0" + }, + "engines": { + "node": ">=26.11.1" + } + }, + "packages/xml": { + "name": "@ordner.fyi/xml", + "version": "2.0.0", + "license": "EUPL-1.2", + "devDependencies": { + "@rgrove/parse-xml": "^4.2.1", + "@types/node": "^26.1.0", + "saxes": "^6.0.0", + "typescript": "^7.0.2" + }, + "engines": { + "node": ">=26.11.1" + } + } + } +} diff --git a/package.json b/package.json index 1b42f16..f9c1bae 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,9 @@ "setup": "node --no-warnings src/main.ts setup", "typecheck": "tsc && tsc -p web", "test": "npm run typecheck && node --no-warnings --test --test-force-exit test/*.test.ts", - "manifest": "node --no-warnings tools/manifest.ts", - "package": "npm run manifest && bundle create --base . --files build/davsync.manifest --launcher --output build/davsync.unsigned.nzip", - "smoke": "node --no-warnings tools/smoke.ts build/davsync.unsigned.nzip", - "sign": "bundle sign build/davsync.unsigned.nzip", - "verify": "bundle verify build/davsync.nzip", - "test:packages": "npm test --workspaces --if-present" + "smoke": "node --no-warnings tools/smoke.ts", + "test:packages": "npm test --workspaces --if-present", + "pack": "node --no-warnings tools/pack.ts" }, "dependencies": { "@ordner.fyi/dav-client": "*", diff --git a/src/main.ts b/src/main.ts index 52885f3..bf73d51 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { ConfigStore, paths } from "./config.ts"; import { runService, servicePid } from "./service.ts"; import { syncFolder } from "./sync/engine.ts"; @@ -13,11 +14,14 @@ usage: davsync [folder...] sync [folder...] sync once (all enabled folders, or the ones named) and exit plan [folder...] show what a sync would do, without doing it status list folders and how their last sync went + version print the version Folders are named by name or id. Configuration: ${paths.config} State: ${paths.database} `; +const VERSION = (JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string }).version; + const [command = "help", ...args] = process.argv.slice(2); try { @@ -48,6 +52,10 @@ async function main(command: string, args: string[]): Promise { return syncOnce(args, command === "plan"); case "status": return status(); + case "version": + case "--version": + console.log(`davsync ${VERSION}`); + return 0; case "help": case "--help": case "-h": diff --git a/src/ui/window.ts b/src/ui/window.ts index b9817cc..80972ad 100644 --- a/src/ui/window.ts +++ b/src/ui/window.ts @@ -45,6 +45,10 @@ export class AppWindow extends EventEmitter { "--remote-debugging-pipe", `--app=${url}`, "--window-size=1200,800", + // Chrome refuses to run as root with its sandbox (CI VMs and containers + // run as root). The window only ever shows our own loopback page, under + // a CSP that allows nothing but our own modules. + ...(process.getuid?.() === 0 ? ["--no-sandbox"] : []), ], }); if (!chrome.remoteDebuggingPipes) throw new Error("Chrome started without debugging pipes"); diff --git a/tools/manifest.ts b/tools/manifest.ts deleted file mode 100644 index 894b48b..0000000 --- a/tools/manifest.ts +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node -import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { moduleFiles } from "@pipobscure/bundle/files"; - -// Step 1 of packaging: the list of files that go into the archive, written to -// build/davsync.manifest for `bundle create --files`. -// -// Nothing of the app is compiled or bundled. The archive holds the sources node -// runs (src/), the setup page's modules and assets (web/, public/), and every -// runtime dependency with its own dependencies. The workspace packages under -// packages/ are built by `npm install` and go in as their dist/ output: node -// does not strip types from files below node_modules, which is where they sit. -// `npm run smoke` checks the result by starting the packed app. - -const ROOT = fileURLToPath(new URL("../", import.meta.url)); -const OUTPUT = join(ROOT, "build", "davsync.manifest"); - -const { dependencies } = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")) as { - dependencies: Record; -}; - -const files = moduleFiles({ - base: ROOT, - files: ["package.json", "LICENSE", "README.md"], - dirs: ["src", "web", "public"], - dependencies: Object.keys(dependencies), - // What runs, not what is read about it: no type declarations, source maps, - // compiled tests or editor config. Licenses stay with the code they cover. - filter: (name) => - !/\.(d\.ts|d\.mts|d\.cts|map|tsbuildinfo)$/.test(name) && - !/\.test\.js$/.test(name) && - !/(^|\/)\.dist-test\//.test(name) && - !/^node_modules\/@ordner\.fyi\/[^/]+\/(src|test)\//.test(name) && - !/(^|\/)tsconfig[^/]*\.json$/.test(name), -}); - -mkdirSync(join(ROOT, "build"), { recursive: true }); -writeFileSync(OUTPUT, `${files.join("\n")}\n`); -const packages = new Set( - files - .filter((file) => file.startsWith("node_modules/")) - .map((file) => { - const [, scopeOrName, name] = file.split("/"); - return scopeOrName!.startsWith("@") ? `${scopeOrName}/${name}` : scopeOrName; - }), -); -console.error(`* ${files.length} files (${packages.size} packages) -> build/davsync.manifest`); diff --git a/tools/pack.ts b/tools/pack.ts new file mode 100644 index 0000000..275746e --- /dev/null +++ b/tools/pack.ts @@ -0,0 +1,69 @@ +#!/usr/bin/env node +import { spawnSync } from "node:child_process"; +import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { moduleFiles } from "@pipobscure/bundle/files"; + +// Packs the app into build/davsync.nzip: one file, behind a launcher, so it runs +// by name (`./build/davsync.nzip setup`). +// +// Nothing of the app is compiled or bundled. The archive holds the sources node +// runs (src/), the setup page's modules and assets (web/, public/), and every +// runtime dependency with its own dependencies. The workspace packages under +// packages/ are built by `npm install` and go in as their dist/ output: node +// does not strip types from files below node_modules, which is where they sit. +// +// Every member is dated with the commit being built, and the archive is created +// in UTC, so anyone rebuilding a release from its tag gets the same bytes the +// release's attestation names. `npm run smoke` checks the result by starting it. + +const ROOT = fileURLToPath(new URL("../", import.meta.url)); +const BUILD = join(ROOT, "build"); +const MANIFEST = join(BUILD, "davsync.manifest"); +const OUTPUT = join(BUILD, "davsync.nzip"); +const BUNDLE = join(createRequire(import.meta.url).resolve("@pipobscure/bundle/cli"), "..", "main.js"); + +const { dependencies } = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")) as { + dependencies: Record; +}; + +const files = moduleFiles({ + base: ROOT, + files: ["package.json", "LICENSE", "README.md"], + dirs: ["src", "web", "public"], + dependencies: Object.keys(dependencies), + // What runs, not what is read about it: no type declarations, source maps, + // compiled tests or editor config. Licenses stay with the code they cover. + filter: (name) => + !/\.(d\.ts|d\.mts|d\.cts|map|tsbuildinfo)$/.test(name) && + !/\.test\.js$/.test(name) && + !/(^|\/)\.dist-test\//.test(name) && + !/^node_modules\/@ordner\.fyi\/[^/]+\/(src|test)\//.test(name) && + !/(^|\/)tsconfig[^/]*\.json$/.test(name), +}); + +mkdirSync(BUILD, { recursive: true }); +writeFileSync(MANIFEST, `${files.join("\n")}\n`); + +const result = spawnSync( + process.execPath, + ["--no-warnings", "--experimental-vfs", BUNDLE, "create", "--base", ROOT, "--files", MANIFEST, "--date", buildDate(), "--launcher", "--output", OUTPUT], + { encoding: "utf8", env: { ...process.env, TZ: "UTC" } }, +); +if (result.status !== 0) fail(`bundle create failed:\n${result.stderr}${result.stdout}`); +chmodSync(OUTPUT, 0o755); +console.error(`* ${relative(ROOT, OUTPUT)}: ${files.length} members`); + +/** The time every member is dated with: the commit being built, which anyone rebuilding a release has. */ +function buildDate(): string { + const commit = spawnSync("git", ["log", "-1", "--format=%ct"], { cwd: ROOT, encoding: "utf8" }); + if (commit.status !== 0 || !/^\d+$/.test(commit.stdout.trim())) fail("no commit to date the bundle with: build in a git checkout"); + return new Date(Number(commit.stdout.trim()) * 1000).toISOString().replace(/\.\d{3}Z$/, "Z"); +} + +function fail(reason: string): never { + console.error(`error: ${reason}`); + process.exit(1); +} diff --git a/tools/release.sh b/tools/release.sh new file mode 100755 index 0000000..64dd52b --- /dev/null +++ b/tools/release.sh @@ -0,0 +1,122 @@ +#!/bin/sh +# Release davsync from CI, on the spindle: build the bundle, attach it to the tag +# being released, vouch for it with an atproto attestation, and point the +# `davsync` bundle listing at it, so that +# +# bundle install @/davsync +# +# works, and `bundle update` follows new releases. +# +# The bundle is not signed: who stands behind it is said by the attestation +# (`bundle attest --kind published`), which installs check. +# +# Secrets (set them with `tangled secret set `): +# TANGLED_HANDLE, TANGLED_APP_PASSWORD the account that uploads the release file +# (the repository's owner, or a collaborator) +# Optional secrets: +# BUNDLE_ATPROTO_IDENTIFIER, BUNDLE_ATPROTO_PASSWORD +# another account to attest and publish as +# (default: the uploading account) +# TANGLED_RELEASE_TAG the tag (default: TANGLED_REF_NAME, set by the spindle) +# +# Run by hand it is a dry run, unless it is run on the tag and told to release +# that tag, which is how a release that failed part way is finished (every +# step can be repeated: the upload replaces, the attestation and listing are +# rewritten, and the bundle builds the same bytes again): +# +# tangled run trigger -w release.yml -r refs/tags/v0.1.0 -f release=v0.1.0 +set -eu + +DRY_RUN="${RELEASE_DRY_RUN:-}" +if [ "${TANGLED_PIPELINE_KIND:-}" = "manual" ]; then + if [ "${TANGLED_REF_TYPE:-}" = "tag" ] && [ -n "${TANGLED_INPUT_RELEASE:-}" ] && [ "${TANGLED_INPUT_RELEASE}" = "${TANGLED_REF_NAME:-}" ]; then + echo "* run by hand, and told to release ${TANGLED_REF_NAME}" + else + DRY_RUN=1 + fi +fi + +TAG="${TANGLED_RELEASE_TAG:-${TANGLED_REF_NAME:-}}" +if [ -z "$DRY_RUN" ]; then + : "${TAG:?no tag to release}" + : "${TANGLED_HANDLE:?set the TANGLED_HANDLE secret}" + : "${TANGLED_APP_PASSWORD:?set the TANGLED_APP_PASSWORD secret}" + BUNDLE_ATPROTO_IDENTIFIER="${BUNDLE_ATPROTO_IDENTIFIER:-$TANGLED_HANDLE}" + BUNDLE_ATPROTO_PASSWORD="${BUNDLE_ATPROTO_PASSWORD:-$TANGLED_APP_PASSWORD}" + export BUNDLE_ATPROTO_IDENTIFIER BUNDLE_ATPROTO_PASSWORD +fi + +echo "* releasing ${TAG:-(dry run)}" + +if ! node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>26||(a===26&&b>=11)?0:1)' 2>/dev/null; then + echo "error: node $(node --version 2>/dev/null || echo missing) is older than 26.11.1; add a newer nodejs to the workflow's dependencies" >&2 + exit 1 +fi +echo "* node $(node --version)" + +# The tag names the version the app reports; they must agree. +version="v$(node -p 'require("./package.json").version')" +if [ -z "$DRY_RUN" ] && [ "$TAG" != "$version" ]; then + echo "error: the tag is $TAG, but package.json says $version; set the version and tag that" >&2 + exit 1 +fi + +npm ci --no-audit --no-fund +npm test +npm run test:packages +npm run pack +npm run smoke +./build/davsync.nzip --version + +if [ -n "$DRY_RUN" ]; then + echo "* dry run: built and checked build/davsync.nzip ($(sha256sum build/davsync.nzip | cut -d' ' -f1)); nothing uploaded, attested or published" + exit 0 +fi + +BUNDLE="node --no-warnings --experimental-vfs node_modules/@pipobscure/bundle/dist/main.js" + +# The tangled CLI, to upload release files: installed with bundle, accepted +# only with its publisher's attestation (ci/policy.json), into this run alone. +CI_HOME="$PWD/.tangled-ci" +mkdir -p "$CI_HOME/bin" +env HOME="$CI_HOME" XDG_CONFIG_HOME="$CI_HOME/config" XDG_DATA_HOME="$CI_HOME/data" XDG_STATE_HOME="$CI_HOME/state" XDG_CACHE_HOME="$CI_HOME/cache" \ + BUNDLE_POLICY="$PWD/ci/policy.json" BUNDLE_SYSTEM_POLICY="$CI_HOME/none.json" \ + $BUNDLE install --dir "$CI_HOME/bin" @pipobscure.com/tangled < /dev/null +TANGLED="$CI_HOME/bin/tangled" +export TANGLED_CONFIG_DIR="$CI_HOME/tangled" TANGLED_NO_PROMPT=1 + +printf '%s\n' "$TANGLED_APP_PASSWORD" | "$TANGLED" auth login --app-password --password-stdin "$TANGLED_HANDLE" +"$TANGLED" release upload --clobber --json "$TAG" build/davsync.nzip > .release.json +cat .release.json + +# tangled.org serves a file once it has seen the record, which may take a +# while; the uploader's PDS serves it at once. Prefer the first, wait a little +# for it, and fall back to the second. Prints " " lines. +node -e ' + const files = JSON.parse(require("fs").readFileSync(".release.json", "utf8")); + const deadline = Date.now() + 5 * 60_000; + (async () => { + for (const entry of files) { + let url = entry.blobUrl; + for (;;) { + // tangled.org answers some errors with a 200 and an HTML page: only the file itself, at its size, counts. + const response = await fetch(entry.url).catch(() => undefined); + const bytes = response?.ok ? Buffer.from(await response.arrayBuffer()) : undefined; + const head = bytes?.subarray(0, 2).toString(); + if (bytes?.length === entry.size && (head === "#!" || head === "PK")) { url = entry.url; break; } + if (Date.now() > deadline) break; + await new Promise((resolve) => setTimeout(resolve, 10_000)); + } + console.log(`${entry.name} ${url}`); + } + })(); +' > .release-urls + +$BUNDLE attest --kind published --note "davsync $TAG" build/davsync.nzip + +$BUNDLE publish --as "$BUNDLE_ATPROTO_IDENTIFIER" --title davsync \ + --description "$(node -p 'require("./package.json").description')" \ + davsync "$(sed -n 's#^davsync.nzip ##p' .release-urls)" + +echo "* released $TAG:" +echo " bundle install @$BUNDLE_ATPROTO_IDENTIFIER/davsync" diff --git a/tools/smoke.ts b/tools/smoke.ts index e3c701c..7ed243f 100644 --- a/tools/smoke.ts +++ b/tools/smoke.ts @@ -1,26 +1,27 @@ #!/usr/bin/env node -import { spawn } from "node:child_process"; +import { spawn, spawnSync } from "node:child_process"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -// Starts a packed archive's setup window in a headless browser and waits for the +// After tools/pack.ts: runs the packed app by name, as it runs once installed. +// `--version` first, then the setup window in a headless browser, waiting for the // page to report that it loaded. That only happens once every module the server, // the sync engine and the page import has been found inside the archive, so it is // the check that the manifest is complete. // -// node tools/smoke.ts build/davsync.unsigned.nzip -// -// The archive is mounted directly with --vfs-load, so it can be unsigned. +// node tools/smoke.ts [build/davsync.nzip] -const archive = process.argv[2]; -if (!archive) { - console.error("usage: node tools/smoke.ts "); - process.exit(64); +const archive = resolve(process.argv[2] ?? "build/davsync.nzip"); +const version = spawnSync(archive, ["--version"], { encoding: "utf8" }); +if (version.status !== 0) { + console.error(`error: ${archive} --version failed (exit ${version.status}):\n${version.stderr}`); + process.exit(1); } +console.error(`* ${version.stdout.trim()}`); const scratch = mkdtempSync(join(tmpdir(), "davsync-smoke-")); -const child = spawn(process.execPath, ["--no-warnings", "--experimental-vfs", `--vfs-load=${resolve(archive)}`, "--", "setup"], { +const child = spawn(archive, ["setup"], { // Temporary config and state, so the run neither reads nor records real settings. env: { ...process.env,