diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 981a5573eb3..7d471e3f421 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,6 +9,8 @@ "version": "14.18.0", "license": "MIT", "dependencies": { + "@apphosting/build": "^0.1.6", + "@apphosting/common": "^0.0.8", "@electric-sql/pglite": "^0.3.3", "@electric-sql/pglite-tools": "^0.2.8", "@google-cloud/cloud-sql-connector": "^1.3.3", @@ -300,6 +302,38 @@ "js-yaml": "^3.13.1" } }, + "node_modules/@apphosting/build": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@apphosting/build/-/build-0.1.6.tgz", + "integrity": "sha512-nXK1wsR1tehaq9uSRDCGQmN+Dp0xbyGohssYd7g4W8ZbzHfUiab+Pabv34pHVTS03VaSVkjdNcR1g9hezi6s8g==", + "license": "Apache-2.0", + "dependencies": { + "@apphosting/common": "^0.0.8", + "@npmcli/promise-spawn": "^3.0.0", + "colorette": "^2.0.20", + "commander": "^11.1.0", + "npm-pick-manifest": "^9.0.0", + "ts-node": "^10.9.1" + }, + "bin": { + "apphosting-local-build": "dist/bin/localbuild.js" + } + }, + "node_modules/@apphosting/build/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apphosting/common": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@apphosting/common/-/common-0.0.8.tgz", + "integrity": "sha512-RJu5gXs2HYV7+anxpVPpp04oXeuHbV3qn402AdXVlnuYM/uWo7aceqmngpfp6Bi376UzRqGjfpdwFHxuwsEGXQ==", + "license": "Apache-2.0" + }, "node_modules/@astrojs/compiler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-1.3.1.tgz", @@ -913,7 +947,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -925,7 +958,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -3588,7 +3620,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -3605,8 +3636,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.15", @@ -4281,6 +4311,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", @@ -4575,26 +4617,22 @@ "node_modules/@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" }, "node_modules/@tsconfig/node12": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" }, "node_modules/@tsconfig/node14": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" }, "node_modules/@tsconfig/node16": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" }, "node_modules/@types/archiver": { "version": "6.0.2", @@ -5807,7 +5845,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5828,7 +5865,6 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -6246,8 +6282,7 @@ "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "node_modules/argparse": { "version": "2.0.1", @@ -7934,9 +7969,10 @@ } }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, "node_modules/colornames": { "version": "1.1.1", @@ -8420,8 +8456,7 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "node_modules/cross-env": { "version": "7.0.3", @@ -12396,8 +12431,7 @@ "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "optional": true + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "node_modules/inflight": { "version": "1.0.6", @@ -13797,8 +13831,7 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/make-fetch-happen": { "version": "10.2.1", @@ -15791,6 +15824,75 @@ "node": ">=0.10.0" } }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -17314,6 +17416,15 @@ "node": ">=6" } }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -20253,7 +20364,6 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20296,7 +20406,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -20413,7 +20522,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21043,8 +21151,7 @@ "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/valid-url": { "version": "1.0.9", @@ -21061,6 +21168,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -21700,7 +21816,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, "engines": { "node": ">=6" } @@ -21926,6 +22041,31 @@ "js-yaml": "^3.13.1" } }, + "@apphosting/build": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@apphosting/build/-/build-0.1.6.tgz", + "integrity": "sha512-nXK1wsR1tehaq9uSRDCGQmN+Dp0xbyGohssYd7g4W8ZbzHfUiab+Pabv34pHVTS03VaSVkjdNcR1g9hezi6s8g==", + "requires": { + "@apphosting/common": "^0.0.8", + "@npmcli/promise-spawn": "^3.0.0", + "colorette": "^2.0.20", + "commander": "^11.1.0", + "npm-pick-manifest": "^9.0.0", + "ts-node": "^10.9.1" + }, + "dependencies": { + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==" + } + } + }, + "@apphosting/common": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@apphosting/common/-/common-0.0.8.tgz", + "integrity": "sha512-RJu5gXs2HYV7+anxpVPpp04oXeuHbV3qn402AdXVlnuYM/uWo7aceqmngpfp6Bi376UzRqGjfpdwFHxuwsEGXQ==" + }, "@astrojs/compiler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-1.3.1.tgz", @@ -22390,7 +22530,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -22399,7 +22538,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -24351,8 +24489,7 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", @@ -24363,8 +24500,7 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { "version": "0.3.15", @@ -24792,6 +24928,14 @@ } } }, + "@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "requires": { + "infer-owner": "^1.0.4" + } + }, "@opentelemetry/api": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", @@ -25026,26 +25170,22 @@ "@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" }, "@tsconfig/node12": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" }, "@tsconfig/node14": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" }, "@tsconfig/node16": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" }, "@types/archiver": { "version": "6.0.2", @@ -26100,8 +26240,7 @@ "acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" }, "acorn-jsx": { "version": "5.3.2", @@ -26113,8 +26252,7 @@ "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" }, "agent-base": { "version": "6.0.2", @@ -26395,8 +26533,7 @@ "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "argparse": { "version": "2.0.1", @@ -27600,9 +27737,9 @@ "optional": true }, "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "colornames": { "version": "1.1.1", @@ -27946,8 +28083,7 @@ "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "cross-env": { "version": "7.0.3", @@ -30914,8 +31050,7 @@ "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "optional": true + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflight": { "version": "1.0.6", @@ -32017,8 +32152,7 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "make-fetch-happen": { "version": "10.2.1", @@ -33391,6 +33525,56 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==" + }, + "npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "requires": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "requires": { + "lru-cache": "^10.0.1" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + } + } + }, + "npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "requires": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + } + }, "npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -34499,6 +34683,11 @@ "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", "dev": true }, + "proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==" + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -36734,7 +36923,6 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -36754,8 +36942,7 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" } } }, @@ -36842,8 +37029,7 @@ "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" }, "typescript-json-schema": { "version": "0.65.1", @@ -37291,8 +37477,7 @@ "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "valid-url": { "version": "1.0.9", @@ -37309,6 +37494,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -37774,8 +37964,7 @@ "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 30d7d498069..9ed285db493 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,8 @@ ] }, "dependencies": { + "@apphosting/build": "^0.1.6", + "@apphosting/common": "^0.0.8", "@electric-sql/pglite": "^0.3.3", "@electric-sql/pglite-tools": "^0.2.8", "@google-cloud/cloud-sql-connector": "^1.3.3", diff --git a/schema/firebase-config.json b/schema/firebase-config.json index b2bbeca6691..2a1ef0a06d7 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -1107,6 +1107,9 @@ }, "type": "array" }, + "localBuild": { + "type": "boolean" + }, "rootDir": { "type": "string" } @@ -1134,6 +1137,9 @@ }, "type": "array" }, + "localBuild": { + "type": "boolean" + }, "rootDir": { "type": "string" } diff --git a/src/apphosting/localbuilds.spec.ts b/src/apphosting/localbuilds.spec.ts new file mode 100644 index 00000000000..7fff66afcde --- /dev/null +++ b/src/apphosting/localbuilds.spec.ts @@ -0,0 +1,47 @@ +import * as sinon from "sinon"; +import { expect } from "chai"; +import * as localBuildModule from "@apphosting/build"; +import { localBuild } from "./localbuilds"; + +describe("localBuild", () => { + afterEach(() => { + sinon.restore(); + }); + + it("returns the expected output", async () => { + const bundleConfig = { + version: "v1" as const, + runConfig: { + runCommand: "npm run build:prod", + }, + metadata: { + adapterPackageName: "@apphosting/angular-adapter", + adapterVersion: "14.1", + framework: "nextjs", + }, + outputFiles: { + serverApp: { + include: ["./next/standalone"], + }, + }, + }; + const expectedAnnotations = { + adapterPackageName: "@apphosting/angular-adapter", + adapterVersion: "14.1", + framework: "nextjs", + }; + const expectedOutputFiles = ["./next/standalone"]; + const expectedBuildConfig = { + runCommand: "npm run build:prod", + env: [], + }; + const localApphostingBuildStub: sinon.SinonStub = sinon + .stub(localBuildModule, "localBuild") + .resolves(bundleConfig); + const { outputFiles, annotations, buildConfig } = await localBuild("./", "nextjs"); + expect(annotations).to.deep.equal(expectedAnnotations); + expect(buildConfig).to.deep.equal(expectedBuildConfig); + expect(outputFiles).to.deep.equal(expectedOutputFiles); + sinon.assert.calledWith(localApphostingBuildStub, "./", "nextjs"); + }); +}); diff --git a/src/apphosting/localbuilds.ts b/src/apphosting/localbuilds.ts new file mode 100644 index 00000000000..ed8bef0d077 --- /dev/null +++ b/src/apphosting/localbuilds.ts @@ -0,0 +1,37 @@ +import { BuildConfig, Env } from "../gcp/apphosting"; +import { localBuild as localAppHostingBuild } from "@apphosting/build"; + +/** + * Triggers a local apphosting build. + */ +export async function localBuild( + projectRoot: string, + framework: string, +): Promise<{ + outputFiles: string[]; + annotations: Record; + buildConfig: BuildConfig; +}> { + const apphostingBuildOutput = await localAppHostingBuild(projectRoot, framework); + + const annotations: Record = Object.fromEntries( + Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]), + ); + + const env: Env[] | undefined = apphostingBuildOutput.runConfig.environmentVariables?.map( + ({ variable, value, availability }) => ({ + variable, + value, + availability, + }), + ); + + return { + outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [], + annotations, + buildConfig: { + runCommand: apphostingBuildOutput.runConfig.runCommand, + env: env ?? [], + }, + }; +} diff --git a/src/deploy/apphosting/args.ts b/src/deploy/apphosting/args.ts index e18e2d16e59..7abaddc5f39 100644 --- a/src/deploy/apphosting/args.ts +++ b/src/deploy/apphosting/args.ts @@ -1,7 +1,15 @@ import { AppHostingSingle } from "../../firebaseConfig"; +import { BuildConfig } from "../../gcp/apphosting"; + +export interface LocalBuild { + buildConfig: BuildConfig; + buildDir: string; + annotations: Record; +} export interface Context { backendConfigs: Record; backendLocations: Record; backendStorageUris: Record; + backendLocalBuilds: Record; } diff --git a/src/deploy/apphosting/deploy.spec.ts b/src/deploy/apphosting/deploy.spec.ts index 90479e459ca..52d275cb348 100644 --- a/src/deploy/apphosting/deploy.spec.ts +++ b/src/deploy/apphosting/deploy.spec.ts @@ -30,9 +30,22 @@ function initializeContext(): Context { rootDir: "/", ignore: [], }, + fooLocalBuild: { + backendId: "fooLocalBuild", + rootDir: "/", + ignore: [], + localBuild: true, + }, }, - backendLocations: { foo: "us-central1" }, + backendLocations: { foo: "us-central1", fooLocalBuild: "us-central1" }, backendStorageUris: {}, + backendLocalBuilds: { + fooLocalBuild: { + buildDir: "./nextjs/standalone", + buildConfig: {}, + annotations: {}, + }, + }, }; } @@ -59,17 +72,25 @@ describe("apphosting", () => { sinon.verifyAndRestore(); }); - describe("deploy", () => { + describe("deploy local source", () => { const opts = { ...BASE_OPTS, projectId: "my-project", only: "apphosting", config: new Config({ - apphosting: { - backendId: "foo", - rootDir: "/", - ignore: [], - }, + apphosting: [ + { + backendId: "foo", + rootDir: "/", + ignore: [], + }, + { + backendId: "fooLocalBuild", + rootDir: "/", + ignore: [], + localBuild: true, + }, + ], }), }; @@ -77,15 +98,23 @@ describe("apphosting", () => { const context = initializeContext(); getProjectNumberStub.resolves("000000000000"); upsertBucketStub.resolves(); - createArchiveStub.resolves("path/to/foo-1234.zip"); - uploadObjectStub.resolves({ + createArchiveStub.onFirstCall().resolves("path/to/foo-1234.zip"); + createArchiveStub.onSecondCall().resolves("path/to/foo-local-build-1234.zip"); + uploadObjectStub.onFirstCall().resolves({ bucket: "firebaseapphosting-sources-12345678-us-central1", object: "foo-1234", }); + uploadObjectStub.onSecondCall().resolves({ + bucket: "firebaseapphosting-build-12345678-us-central1", + object: "foo-local-build-1234", + }); + createReadStreamStub.resolves(); await deploy(context, opts); + // assert backend foo calls + expect(upsertBucketStub).to.be.calledWith({ product: "apphosting", createMessage: @@ -104,17 +133,53 @@ describe("apphosting", () => { }, }, }); + + // assert backend fooLocalBuild calls + expect(upsertBucketStub).to.be.calledWith({ + product: "apphosting", + createMessage: + "Creating Cloud Storage bucket in us-central1 to store App Hosting source code uploads at firebaseapphosting-build-000000000000-us-central1...", + projectId: "my-project", + req: { + name: "firebaseapphosting-build-000000000000-us-central1", + location: "us-central1", + lifecycle: { + rule: [ + { + action: { type: "Delete" }, + condition: { age: 30 }, + }, + ], + }, + }, + }); + expect(createArchiveStub).to.be.calledWithExactly( + context.backendConfigs["fooLocalBuild"], + process.cwd(), + "./nextjs/standalone", + ); + expect(uploadObjectStub).to.be.calledWithMatch( + sinon.match.any, + "firebaseapphosting-build-000000000000-us-central1", + ); }); it("correctly creates and sets storage URIs", async () => { const context = initializeContext(); getProjectNumberStub.resolves("000000000000"); upsertBucketStub.resolves(); - createArchiveStub.resolves("path/to/foo-1234.zip"); - uploadObjectStub.resolves({ - bucket: "firebaseapphosting-sources-12345678-us-central1", + createArchiveStub.onFirstCall().resolves("path/to/foo-1234.zip"); + createArchiveStub.onSecondCall().resolves("path/to/foo-local-build-1234.zip"); + + uploadObjectStub.onFirstCall().resolves({ + bucket: "firebaseapphosting-sources-000000000000-us-central1", object: "foo-1234", }); + + uploadObjectStub.onSecondCall().resolves({ + bucket: "firebaseapphosting-build-000000000000-us-central1", + object: "foo-local-build-1234", + }); createReadStreamStub.resolves(); await deploy(context, opts); @@ -122,6 +187,9 @@ describe("apphosting", () => { expect(context.backendStorageUris["foo"]).to.equal( "gs://firebaseapphosting-sources-000000000000-us-central1/foo-1234.zip", ); + expect(context.backendStorageUris["fooLocalBuild"]).to.equal( + "gs://firebaseapphosting-build-000000000000-us-central1/foo-local-build-1234.zip", + ); }); }); }); diff --git a/src/deploy/apphosting/deploy.ts b/src/deploy/apphosting/deploy.ts index 7bb480b2474..eefcd27b4d1 100644 --- a/src/deploy/apphosting/deploy.ts +++ b/src/deploy/apphosting/deploy.ts @@ -25,8 +25,15 @@ export default async function (context: Context, options: Options): Promise { - const bucketName = `firebaseapphosting-sources-${options.projectNumber}-${loc.toLowerCase()}`; + Object.entries(context.backendLocations).map(async ([backendId, loc]) => { + const cfg = context.backendConfigs[backendId]; + if (!cfg) { + throw new FirebaseError( + `Failed to find config for backend ${backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`, + ); + } + + const bucketName = `firebaseapphosting-${cfg.localBuild ? "build" : "sources"}-${options.projectNumber}-${loc.toLowerCase()}`; await gcs.upsertBucket({ product: "apphosting", createMessage: `Creating Cloud Storage bucket in ${loc} to store App Hosting source code uploads at ${bucketName}...`, @@ -51,10 +58,23 @@ export default async function (context: Context, options: Options): Promise { - const projectSourcePath = options.projectRoot ? options.projectRoot : process.cwd(); - const zippedSourcePath = await createArchive(cfg, projectSourcePath); + const rootDir = options.projectRoot ?? process.cwd(); + let builtAppDir; + if (cfg.localBuild) { + builtAppDir = context.backendLocalBuilds[cfg.backendId].buildDir; + if (!builtAppDir) { + throw new FirebaseError(`No local build dir found for ${cfg.backendId}`); + } + } + const zippedSourcePath = await createArchive(cfg, rootDir, builtAppDir); + logLabeledBullet( + "apphosting", + `Zipped ${cfg.localBuild ? "built app" : "source"} for backend ${cfg.backendId}`, + ); + const backendLocation = context.backendLocations[cfg.backendId]; if (!backendLocation) { throw new FirebaseError( @@ -63,9 +83,10 @@ export default async function (context: Context, options: Options): Promise { }); describe("prepare", () => { + it("correctly creates configs for localBuild backends", async () => { + const optsWithLocalBuild = { + ...opts, + config: new Config({ + apphosting: { + backendId: "foo", + rootDir: "/", + ignore: [], + localBuild: true, + }, + }), + }; + const context = initializeContext(); + + const annotations = { + adapterPackageName: "@apphosting/angular-adapter", + adapterVersion: "14.1", + framework: "nextjs", + }; + const buildConfig = { + runCommand: "npm run build:prod", + env: [], + }; + sinon.stub(localbuilds, "localBuild").resolves({ + outputFiles: ["./next/standalone"], + buildConfig, + annotations, + }); + listBackendsStub.onFirstCall().resolves({ + backends: [ + { + name: "projects/my-project/locations/us-central1/backends/foo", + }, + ], + }); + + await prepare(context, optsWithLocalBuild); + + expect(context.backendLocations["foo"]).to.equal("us-central1"); + expect(context.backendConfigs["foo"]).to.deep.equal({ + backendId: "foo", + rootDir: "/", + ignore: [], + localBuild: true, + }); + expect(context.backendLocalBuilds["foo"]).to.deep.equal({ + buildDir: "./next/standalone", + buildConfig, + annotations, + }); + }); + it("links to existing backend if it already exists", async () => { const context = initializeContext(); listBackendsStub.onFirstCall().resolves({ @@ -91,6 +145,7 @@ describe("apphosting", () => { rootDir: "/", ignore: [], }); + expect(context.backendLocalBuilds["foo"]).to.be.undefined; }); it("creates a backend if it doesn't exist yet", async () => { @@ -111,6 +166,7 @@ describe("apphosting", () => { rootDir: "/", ignore: [], }); + expect(context.backendLocalBuilds["foo"]).to.be.undefined; }); it("skips backend deployment if alwaysDeployFromSource is false", async () => { @@ -141,6 +197,7 @@ describe("apphosting", () => { expect(context.backendLocations["foo"]).to.be.undefined; expect(context.backendConfigs["foo"]).to.be.undefined; + expect(context.backendLocalBuilds["foo"]).to.be.undefined; }); it("prompts user if codebase is already connected and alwaysDeployFromSource is undefined", async () => { @@ -170,6 +227,7 @@ describe("apphosting", () => { ignore: [], alwaysDeployFromSource: true, }); + expect(context.backendLocalBuilds["foo"]).to.undefined; }); }); diff --git a/src/deploy/apphosting/prepare.ts b/src/deploy/apphosting/prepare.ts index ccf01a58bfb..87086e2f2ab 100644 --- a/src/deploy/apphosting/prepare.ts +++ b/src/deploy/apphosting/prepare.ts @@ -11,7 +11,9 @@ import { Options } from "../../options"; import { needProjectId } from "../../projectUtils"; import { checkbox, confirm } from "../../prompt"; import { logLabeledBullet, logLabeledWarning } from "../../utils"; +import { localBuild } from "../../apphosting/localbuilds"; import { Context } from "./args"; +import { FirebaseError } from "../../error"; /** * Prepare backend targets to deploy from source. Checks that all required APIs are enabled, @@ -26,6 +28,7 @@ export default async function (context: Context, options: Options): Promise cfg.backendId).join(", ")}.`, ); } - return; + + for (const cfg of Object.values(context.backendConfigs)) { + if (!cfg.localBuild) { + continue; + } + logLabeledBullet("apphosting", `Starting local build for backend ${cfg.backendId}`); + try { + const { outputFiles, annotations, buildConfig } = await localBuild( + options.projectRoot || "./", + "nextjs", + ); + if (outputFiles.length !== 1) { + throw new FirebaseError( + `Local build for backend ${cfg.backendId} failed: No output files found.`, + ); + } + context.backendLocalBuilds[cfg.backendId] = { + // TODO(9114): This only works for nextjs. + buildDir: outputFiles[0], + buildConfig, + annotations, + }; + } catch (e) { + throw new FirebaseError(`Local Build for backend ${cfg.backendId} failed: ${e}`); + } + } } /** diff --git a/src/deploy/apphosting/release.spec.ts b/src/deploy/apphosting/release.spec.ts index d79accffa35..0695f99fd2c 100644 --- a/src/deploy/apphosting/release.spec.ts +++ b/src/deploy/apphosting/release.spec.ts @@ -19,31 +19,9 @@ const BASE_OPTS = { json: false, }; -function initializeContext(): Context { - return { - backendConfigs: { - foo: { - backendId: "foo", - rootDir: "/", - ignore: [], - }, - }, - backendLocations: { foo: "us-central1" }, - backendStorageUris: { - foo: "gs://firebaseapphosting-sources-us-central1/foo-1234.zip", - }, - }; -} - describe("apphosting", () => { let orchestrateRolloutStub: sinon.SinonStub; - beforeEach(() => { - orchestrateRolloutStub = sinon - .stub(rollout, "orchestrateRollout") - .throws("Unexpected orchestrateRollout call"); - }); - afterEach(() => { sinon.verifyAndRestore(); }); @@ -63,7 +41,25 @@ describe("apphosting", () => { }; it("does not block rollouts of other backends if one rollout fails", async () => { - const context = initializeContext(); + const context: Context = { + backendConfigs: { + foo: { + backendId: "foo", + rootDir: "/", + ignore: [], + }, + }, + backendLocations: { foo: "us-central1" }, + backendStorageUris: { + foo: "gs://firebaseapphosting-sources-us-central1/foo-1234.zip", + }, + backendLocalBuilds: {}, + }; + + orchestrateRolloutStub = sinon + .stub(rollout, "orchestrateRollout") + .throws("Unexpected orchestrateRollout call"); + orchestrateRolloutStub.onFirstCall().rejects(); orchestrateRolloutStub.onSecondCall().resolves(); diff --git a/src/deploy/apphosting/release.ts b/src/deploy/apphosting/release.ts index 3c2dce12da9..d4927603e5d 100644 --- a/src/deploy/apphosting/release.ts +++ b/src/deploy/apphosting/release.ts @@ -29,12 +29,24 @@ export default async function (context: Context, options: Options): Promise !missingBackends.includes(id)); } + const localBuildBackends = backendIds.filter((id) => context.backendLocalBuilds[id]); + if (localBuildBackends.length > 0) { + logLabeledWarning( + "apphosting", + `Skipping backend(s) ${localBuildBackends.join(", ")}. Local Builds are not supported yet.`, + ); + backendIds = backendIds.filter((id) => !localBuildBackends.includes(id)); + } + if (backendIds.length === 0) { return; } const projectId = needProjectId(options); const rollouts = backendIds.map((backendId) => + // TODO(9114): Add run_command + // TODO(914): Set the buildConfig. + // TODO(914): Set locallyBuiltSource. orchestrateRollout({ projectId, backendId, diff --git a/src/deploy/apphosting/util.ts b/src/deploy/apphosting/util.ts index 8e593ab509d..24d2364961e 100644 --- a/src/deploy/apphosting/util.ts +++ b/src/deploy/apphosting/util.ts @@ -10,7 +10,11 @@ import * as fsAsync from "../../fsAsync"; * Locates the source code for a backend and creates an archive to eventually upload to GCS. * Based heavily on functions upload logic in src/deploy/functions/prepareFunctionsUpload.ts. */ -export async function createArchive(config: AppHostingSingle, rootDir: string): Promise { +export async function createArchive( + config: AppHostingSingle, + rootDir: string, + targetSubDir?: string, +): Promise { const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".zip" }).name; const fileStream = fs.createWriteStream(tmpFile, { flags: "w", @@ -18,14 +22,15 @@ export async function createArchive(config: AppHostingSingle, rootDir: string): }); const archive = archiver("zip"); + const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir; // We must ignore firebase-debug.log or weird things happen if you're in the public dir when you deploy. const ignore = config.ignore || ["node_modules", ".git"]; ignore.push("firebase-debug.log", "firebase-debug.*.log"); - const gitIgnorePatterns = parseGitIgnorePatterns(rootDir); + const gitIgnorePatterns = parseGitIgnorePatterns(targetDir); ignore.push(...gitIgnorePatterns); try { const files = await fsAsync.readdirRecursive({ - path: rootDir, + path: targetDir, ignore: ignore, isGitIgnore: true, }); diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index e2161f3872a..fc94a368351 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -308,6 +308,7 @@ export type AppHostingSingle = { rootDir: string; ignore: string[]; alwaysDeployFromSource?: boolean; + localBuild?: boolean; }; export type AppHostingMultiple = AppHostingSingle[]; diff --git a/src/gcp/apphosting.ts b/src/gcp/apphosting.ts index 1654ad3741b..57c02079b0d 100644 --- a/src/gcp/apphosting.ts +++ b/src/gcp/apphosting.ts @@ -99,9 +99,20 @@ export type BuildOutputOnlyFields = assertImplements>(); +export type Availability = "BUILD" | "RUNTIME"; + +export interface Env { + variable: string; + secret?: string; + value?: string; + availability?: Availability[]; +} + export interface BuildConfig { minInstances?: number; memory?: string; + env?: Env[]; + runCommand?: string; } interface BuildSource { @@ -129,6 +140,7 @@ interface ArchiveSource { // end oneof reference rootDirectory?: string; author?: SourceUserMetadata; + locallyBuiltSource?: boolean; } interface SourceUserMetadata {