diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index bc94a7a980..15d4f3abf0 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -52,11 +52,11 @@ jobs: os: [ubuntu-latest, macOS-latest, windows-latest] node-version: ['*'] # Must include the minimum deno version from the `DENO_VERSION_RANGE` constant in `node/bridge.ts`. - deno-version: ['v1.37.0', 'v1.44.4'] + deno-version: ['v1.39.0', 'v1.46.3'] include: - os: ubuntu-latest node-version: '14.16.0' - deno-version: 'v1.44.4' + deno-version: 'v1.46.3' fail-fast: false steps: # Increasing the maximum number of open files. See: @@ -192,7 +192,7 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v1.44.4 + deno-version: v1.46.3 if: ${{ !steps.release-check.outputs.IS_RELEASE }} - name: Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index afc3eb64d7..3373fd9e33 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,11 +1,11 @@ { - "packages/build-info": "7.14.3", - "packages/build": "29.54.4", + "packages/build-info": "7.15.2", + "packages/build": "29.56.1", "packages/edge-bundler": "12.2.3", "packages/cache-utils": "5.1.6", - "packages/config": "20.19.0", + "packages/config": "20.19.1", "packages/framework-info": "9.8.13", - "packages/functions-utils": "5.2.83", + "packages/functions-utils": "5.2.93", "packages/git-utils": "5.1.1", "packages/headers-parser": "7.1.4", "packages/js-client": "13.1.21", @@ -14,5 +14,5 @@ "packages/run-utils": "5.1.1", "packages/opentelemetry-sdk-setup": "1.1.3", "packages/opentelemetry-utils": "1.2.1", - "packages/zip-it-and-ship-it": "9.39.2" + "packages/zip-it-and-ship-it": "9.41.1" } diff --git a/package-lock.json b/package-lock.json index 6ff818c3e5..8731f30c43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -300,18 +300,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -399,12 +399,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -458,14 +458,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1560,10 +1559,11 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -1678,12 +1678,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -6675,9 +6677,9 @@ } }, "node_modules/@netlify/open-api": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/@netlify/open-api/-/open-api-2.34.0.tgz", - "integrity": "sha512-C4v7Od/vnGgZ1P4JK3Fn9uUi9HkTxeUqUtj4OLnGD+rGyaVrl4JY89xMCoVksijDtO8XylYFU59CSTnQNeNw7g==", + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/@netlify/open-api/-/open-api-2.35.0.tgz", + "integrity": "sha512-c6LpV29CKMgq6/eViItE6L2ova9UldBO9tHRvvwpJfSBgCwWaFhmiepe07E3xIW4GfTCGoWE816mNzXB/2ceZg==", "license": "MIT", "engines": { "node": ">=14" @@ -6705,9 +6707,9 @@ "link": true }, "node_modules/@netlify/serverless-functions-api": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.24.0.tgz", - "integrity": "sha512-W5LL8EJaZWmPXtjk99C/C6HOEtkQvtBgScQIVNgYPjATyGnYWo8KqJJ45zKxsLuby7jO1+YsRijBJm0nNRR3Nw==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.31.1.tgz", + "integrity": "sha512-SkNxzfCwctS5ETnCqJOJfZZ/jB0pTkbWEAsApHoL7HzUQGWoRM6wYf4baJAJVMTfZBQu534SbKuwRs7WDAs43A==", "license": "MIT", "dependencies": { "@netlify/node-cookies": "^0.1.0", @@ -8429,13 +8431,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.1.tgz", - "integrity": "sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.47.1" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -9099,12 +9101,12 @@ "license": "ISC" }, "node_modules/@vercel/nft": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.4.tgz", - "integrity": "sha512-Rioz3LJkEKicKCi9BSyc1RXZ5R6GmXosFMeBSThh6msWSOiArKhb7c75MiWwZEgPL7x0/l3TAfH/l0cxKNuUFA==", + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.6.tgz", + "integrity": "sha512-mwuyUxskdcV8dd7N7JnxBgvFEz1D9UOePI/WyLLzktv6HSCwgPNQGit/UJ2IykAWGlypKw4pBQjOKWvIbXITSg==", "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.5", + "@mapbox/node-pre-gyp": "^1.0.11", "@rollup/pluginutils": "^4.0.0", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", @@ -9113,7 +9115,7 @@ "estree-walker": "2.0.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "micromatch": "^4.0.2", + "micromatch": "^4.0.8", "node-gyp-build": "^4.2.2", "resolve-from": "^5.0.0" }, @@ -10450,12 +10452,14 @@ "license": "ISC" }, "node_modules/babel-loader": { - "version": "8.3.0", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", "dev": true, "license": "MIT", "dependencies": { "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", + "loader-utils": "^2.0.4", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" }, @@ -14461,16 +14465,17 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -14717,9 +14722,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", - "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "license": "MIT", "dependencies": { @@ -14743,9 +14748,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", "dependencies": { @@ -14757,7 +14762,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", @@ -14766,13 +14771,14 @@ "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { @@ -14864,6 +14870,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14880,6 +14887,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -14889,6 +14897,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -14904,6 +14913,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -14914,6 +14924,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14930,6 +14941,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -14942,6 +14954,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -14954,6 +14967,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -14970,6 +14984,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -14979,6 +14994,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -14995,6 +15011,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -15007,6 +15024,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15015,13 +15033,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -15037,6 +15057,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -15049,6 +15070,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -15064,6 +15086,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -15079,6 +15102,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15088,6 +15112,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -15100,6 +15125,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -15112,6 +15138,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -15341,9 +15368,9 @@ } }, "node_modules/fdir": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.3.0.tgz", - "integrity": "sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -16800,7 +16827,9 @@ } }, "node_modules/hot-shots": { - "version": "10.0.0", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-10.2.1.tgz", + "integrity": "sha512-tmjcyZkG/qADhcdC7UjAp8D7v7W2DOYFgaZ48fYMuayMQmVVUg8fntKmrjes/b40ef6yZ+qt1lB8kuEDfLC4zw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -20065,9 +20094,9 @@ } }, "node_modules/nock": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.5.tgz", - "integrity": "sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA==", + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -22006,13 +22035,13 @@ } }, "node_modules/playwright": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.1.tgz", - "integrity": "sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.1" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -22025,9 +22054,9 @@ } }, "node_modules/playwright-core": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.1.tgz", - "integrity": "sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -22437,9 +22466,9 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -24533,13 +24562,6 @@ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -24910,9 +24932,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -25181,10 +25203,11 @@ } }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", + "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -25266,6 +25289,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -25280,6 +25304,7 @@ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -26087,9 +26112,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -26312,30 +26337,31 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "packages/build": { "name": "@netlify/build", - "version": "29.54.4", + "version": "29.56.1", "license": "MIT", "dependencies": { "@bugsnag/js": "^7.0.0", "@netlify/blobs": "^7.4.0", "@netlify/cache-utils": "^5.1.6", - "@netlify/config": "^20.19.0", + "@netlify/config": "^20.19.1", "@netlify/edge-bundler": "12.2.3", "@netlify/framework-info": "^9.8.13", - "@netlify/functions-utils": "^5.2.83", + "@netlify/functions-utils": "^5.2.93", "@netlify/git-utils": "^5.1.1", "@netlify/opentelemetry-utils": "^1.2.1", "@netlify/plugins-list": "^6.80.0", "@netlify/run-utils": "^5.1.1", - "@netlify/zip-it-and-ship-it": "9.39.2", + "@netlify/zip-it-and-ship-it": "9.41.1", "@sindresorhus/slugify": "^2.0.0", "ansi-escapes": "^6.0.0", "chalk": "^5.0.0", @@ -26345,7 +26371,7 @@ "figures": "^5.0.0", "filter-obj": "^5.0.0", "got": "^12.0.0", - "hot-shots": "10.0.0", + "hot-shots": "10.2.1", "indent-string": "^5.0.0", "is-plain-obj": "^4.0.0", "js-yaml": "^4.0.0", @@ -26428,7 +26454,7 @@ }, "packages/build-info": { "name": "@netlify/build-info", - "version": "7.14.3", + "version": "7.15.2", "license": "MIT", "dependencies": { "@bugsnag/js": "^7.20.0", @@ -26438,7 +26464,7 @@ "minimatch": "^9.0.0", "read-pkg": "^7.1.0", "semver": "^7.3.8", - "yaml": "^2.1.3", + "yaml": "^2.6.1", "yargs": "^17.6.0" }, "bin": { @@ -26643,7 +26669,7 @@ }, "packages/config": { "name": "@netlify/config", - "version": "20.19.0", + "version": "20.19.1", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -27133,10 +27159,10 @@ }, "packages/functions-utils": { "name": "@netlify/functions-utils", - "version": "5.2.83", + "version": "5.2.93", "license": "MIT", "dependencies": { - "@netlify/zip-it-and-ship-it": "9.39.2", + "@netlify/zip-it-and-ship-it": "9.41.1", "cpy": "^9.0.0", "path-exists": "^5.0.0" }, @@ -27271,7 +27297,7 @@ "version": "13.1.21", "license": "MIT", "dependencies": { - "@netlify/open-api": "^2.34.0", + "@netlify/open-api": "^2.35.0", "lodash-es": "^4.17.21", "micro-api-client": "^3.3.0", "node-fetch": "^3.0.0", @@ -27588,13 +27614,13 @@ }, "packages/zip-it-and-ship-it": { "name": "@netlify/zip-it-and-ship-it", - "version": "9.39.2", + "version": "9.41.1", "license": "MIT", "dependencies": { - "@babel/parser": "^7.22.5", - "@babel/types": "7.25.6", + "@babel/parser": "^7.26.3", + "@babel/types": "7.26.3", "@netlify/binary-info": "^1.0.0", - "@netlify/serverless-functions-api": "^1.24.0", + "@netlify/serverless-functions-api": "^1.31.1", "@vercel/nft": "^0.27.1", "archiver": "^7.0.0", "common-path-prefix": "^3.0.0", diff --git a/packages/build-info/CHANGELOG.md b/packages/build-info/CHANGELOG.md index 7b4c441ee3..e7e975bb48 100644 --- a/packages/build-info/CHANGELOG.md +++ b/packages/build-info/CHANGELOG.md @@ -64,6 +64,35 @@ * dependencies * @netlify/framework-info bumped from ^9.8.8 to ^9.8.9 +## [7.15.2](https://github.com/netlify/build/compare/build-info-v7.15.1...build-info-v7.15.2) (2024-10-25) + + +### Bug Fixes + +* detect next if typescript config file is used ([#5893](https://github.com/netlify/build/issues/5893)) ([d07e0d5](https://github.com/netlify/build/commit/d07e0d52bf1e3d8410625e216e08914b4f0ae8cc)) + +## [7.15.1](https://github.com/netlify/build/compare/build-info-v7.15.0...build-info-v7.15.1) (2024-10-01) + + +### Bug Fixes + +* add some support for dotnet runtime detection ([#5858](https://github.com/netlify/build/issues/5858)) ([75eb6a2](https://github.com/netlify/build/commit/75eb6a2a5f902320dd70a808ec30256fcd8df29a)) +* framework detection for solidjs and solidstart ([#5856](https://github.com/netlify/build/issues/5856)) ([2002b42](https://github.com/netlify/build/commit/2002b42751bc0bd120086ab32c685f9bf6703237)) + +## [7.15.0](https://github.com/netlify/build/compare/build-info-v7.14.4...build-info-v7.15.0) (2024-10-01) + + +### Features + +* framework detection for tanstack router and tanstack start ([#5857](https://github.com/netlify/build/issues/5857)) ([1aa78f4](https://github.com/netlify/build/commit/1aa78f45de2a97001083bb3a29eceb2afc5064f7)) + +## [7.14.4](https://github.com/netlify/build/compare/build-info-v7.14.3...build-info-v7.14.4) (2024-09-23) + + +### Bug Fixes + +* set default nodejs version for redwoodjs ([#5842](https://github.com/netlify/build/issues/5842)) ([a3dfb7e](https://github.com/netlify/build/commit/a3dfb7e9664a9968e7575e43ab653a089a4ede0b)) + ## [7.14.3](https://github.com/netlify/build/compare/build-info-v7.14.2...build-info-v7.14.3) (2024-09-20) diff --git a/packages/build-info/assets/logos/solid-start/dark.svg b/packages/build-info/assets/logos/solid-start/dark.svg new file mode 100644 index 0000000000..fc61e36c94 --- /dev/null +++ b/packages/build-info/assets/logos/solid-start/dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/build-info/assets/logos/tanstack-router/default.png b/packages/build-info/assets/logos/tanstack-router/default.png new file mode 100644 index 0000000000..ec5d621427 Binary files /dev/null and b/packages/build-info/assets/logos/tanstack-router/default.png differ diff --git a/packages/build-info/assets/logos/tanstack-start/default.png b/packages/build-info/assets/logos/tanstack-start/default.png new file mode 100644 index 0000000000..ec5d621427 Binary files /dev/null and b/packages/build-info/assets/logos/tanstack-start/default.png differ diff --git a/packages/build-info/package.json b/packages/build-info/package.json index eb08f999d0..943fd21bf7 100644 --- a/packages/build-info/package.json +++ b/packages/build-info/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/build-info", - "version": "7.14.3", + "version": "7.15.2", "description": "Build info utility", "type": "module", "exports": { diff --git a/packages/build-info/src/frameworks/index.ts b/packages/build-info/src/frameworks/index.ts index 624ddd522b..9cd1b52edc 100644 --- a/packages/build-info/src/frameworks/index.ts +++ b/packages/build-info/src/frameworks/index.ts @@ -34,11 +34,13 @@ import { RedwoodJS } from './redwoodjs.js' import { Remix } from './remix.js' import { Roots } from './roots.js' import { Sapper } from './sapper.js' +import { SolidJs } from './solid-js.js' import { SolidStart } from './solid-start.js' -import { Solid } from './solid.js' import { Stencil } from './stencil.js' import { SvelteKit } from './svelte-kit.js' import { Svelte } from './svelte.js' +import { TanStackRouter } from './tanstack-router.js' +import { TanStackStart } from './tanstack-start.js' import { Vite } from './vite.js' import { Vue } from './vue.js' import { VuePress } from './vuepress.js' @@ -66,9 +68,11 @@ export const frameworks = [ ReactStatic, RedwoodJS, Remix, - Solid, + SolidJs, SolidStart, Stencil, + TanStackRouter, + TanStackStart, VuePress, Assemble, DocPad, diff --git a/packages/build-info/src/frameworks/next.ts b/packages/build-info/src/frameworks/next.ts index be9c0bc2fc..15eee5c62e 100644 --- a/packages/build-info/src/frameworks/next.ts +++ b/packages/build-info/src/frameworks/next.ts @@ -8,7 +8,7 @@ export class Next extends BaseFramework implements Framework { category = Category.SSG npmDependencies = ['next'] excludedNpmDependencies = ['@nrwl/next'] - configFiles = ['next.config.js', 'next.config.mjs'] + configFiles = ['next.config.js', 'next.config.mjs', 'next.config.ts'] dev = { command: 'next', diff --git a/packages/build-info/src/frameworks/solid-js.test.ts b/packages/build-info/src/frameworks/solid-js.test.ts new file mode 100644 index 0000000000..3b703fbcff --- /dev/null +++ b/packages/build-info/src/frameworks/solid-js.test.ts @@ -0,0 +1,42 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects a Solid.js site', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ + scripts: { + start: 'vite', + dev: 'vite', + build: 'vite build', + serve: 'vite preview', + }, + dependencies: { + 'solid-js': '^1.8.11', + }, + devDependencies: { + 'solid-devtools': '^0.29.2', + vite: '^5.0.11', + 'vite-plugin-solid': '^2.8.2', + }, + }), + }) + const detected = await new Project(fs, cwd).detectFrameworks() + + const detectedFrameworks = (detected ?? []).map((framework) => framework.id) + expect(detectedFrameworks).not.toContain('vinxi') + expect(detectedFrameworks).not.toContain('vite') + expect(detectedFrameworks).not.toContain('solid-start') + + expect(detected?.[0]?.id).toBe('solid-js') + expect(detected?.[0]?.build?.command).toBe('vite build') + expect(detected?.[0]?.build?.directory).toBe('dist') + expect(detected?.[0]?.dev?.command).toBe('vite dev') + expect(detected?.[0]?.dev?.port).toBe(3000) +}) diff --git a/packages/build-info/src/frameworks/solid.ts b/packages/build-info/src/frameworks/solid-js.ts similarity index 66% rename from packages/build-info/src/frameworks/solid.ts rename to packages/build-info/src/frameworks/solid-js.ts index 36b11ac9f8..93da954c6f 100644 --- a/packages/build-info/src/frameworks/solid.ts +++ b/packages/build-info/src/frameworks/solid-js.ts @@ -1,21 +1,21 @@ import { BaseFramework, Category, Framework } from './framework.js' -export class Solid extends BaseFramework implements Framework { +export class SolidJs extends BaseFramework implements Framework { readonly id = 'solid-js' name = 'SolidJS' npmDependencies = ['solid-js'] - excludedNpmDependencies = ['solid-start'] + excludedNpmDependencies = ['solid-start', '@solidjs/start'] category = Category.SSG dev = { - command: 'npm run dev', + command: 'vite dev', port: 3000, pollingStrategies: [{ name: 'TCP' }], } build = { - command: 'npm run build', - directory: 'netlify', + command: 'vite build', + directory: 'dist', } logo = { diff --git a/packages/build-info/src/frameworks/solid-start.test.ts b/packages/build-info/src/frameworks/solid-start.test.ts new file mode 100644 index 0000000000..d427f23067 --- /dev/null +++ b/packages/build-info/src/frameworks/solid-start.test.ts @@ -0,0 +1,76 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects and configures a SolidStart site that uses the old `solid-start` package', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ + scripts: { + dev: 'solid-start dev', + build: 'solid-start build', + start: 'solid-start start', + }, + dependencies: { + '@solidjs/meta': '^0.28.5', + '@solidjs/router': '^0.8.2', + 'solid-js': '^1.7.8', + 'solid-start': '^0.2.26', + }, + devDependencies: { + 'solid-start-netlify': '^0.2.26', + 'solid-start-node': '^0.2.26', + vite: '3.2.5', + }, + }), + }) + const detected = await new Project(fs, cwd).detectFrameworks() + + const detectedFrameworks = (detected ?? []).map((framework) => framework.id) + expect(detectedFrameworks).not.toContain('vinxi') + expect(detectedFrameworks).not.toContain('vite') + expect(detectedFrameworks).not.toContain('solidjs') + + expect(detected?.[0]?.id).toBe('solid-start') + expect(detected?.[0]?.build?.command).toBe('solid-start build') + expect(detected?.[0]?.build?.directory).toBe('netlify') + expect(detected?.[0]?.dev?.command).toBe('solid-start dev') + expect(detected?.[0]?.dev?.port).toBe(3000) +}) + +test('detects and configures a SolidStart site that uses the new `@solidjs/start` package', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ + scripts: { + dev: 'vinxi dev', + build: 'vinxi build', + start: 'vinxi start', + }, + dependencies: { + '@solidjs/router': '^0.14.1', + '@solidjs/start': '^1.0.6', + 'solid-js': '^1.8.18', + }, + devDependencies: { + vinxi: '^0.4.1', + }, + }), + }) + const detected = await new Project(fs, cwd).detectFrameworks() + + const detectedFrameworks = (detected ?? []).map((framework) => framework.id) + expect(detectedFrameworks).not.toContain('vinxi') + expect(detectedFrameworks).not.toContain('vite') + expect(detectedFrameworks).not.toContain('solidjs') + + expect(detected?.[0]?.id).toBe('solid-start') + expect(detected?.[0]?.build?.command).toBe('vinxi build') + expect(detected?.[0]?.build?.directory).toBe('dist') + expect(detected?.[0]?.dev?.command).toBe('vinxi dev') + expect(detected?.[0]?.dev?.port).toBe(3000) +}) diff --git a/packages/build-info/src/frameworks/solid-start.ts b/packages/build-info/src/frameworks/solid-start.ts index 7b290b8c5d..fe78c161a0 100644 --- a/packages/build-info/src/frameworks/solid-start.ts +++ b/packages/build-info/src/frameworks/solid-start.ts @@ -1,25 +1,52 @@ -import { BaseFramework, Category, Framework } from './framework.js' +import { BaseFramework, Category, type DetectedFramework, type Framework } from './framework.js' + +const LEGACY_PACKAGE_NAME = 'solid-start' +const LEGACY_DEV = { + command: 'solid-start dev', + port: 3000, +} +const LEGACY_BUILD = { + command: 'solid-start build', + directory: 'netlify', +} export class SolidStart extends BaseFramework implements Framework { readonly id = 'solid-start' name = 'Solid Start' - npmDependencies = ['solid-start'] + npmDependencies = [ + // Used this name up to 0.3.11 + 'solid-start', + // Renamed starting at 0.4.0 + '@solidjs/start', + ] category = Category.SSG dev = { - command: 'solid-start dev', + command: 'vinxi dev', port: 3000, - pollingStrategies: [{ name: 'TCP' }], } build = { - command: 'solid-start build', - directory: 'netlify', + command: 'vinxi build', + directory: 'dist', } logo = { default: '/logos/solid-start/default.svg', light: '/logos/solid-start/default.svg', - dark: '/logos/solid-start/default.svg', + dark: '/logos/solid-start/dark.svg', + } + + async detect(): Promise { + await super.detect() + + if (this.detected) { + if (this.detected.package?.name === LEGACY_PACKAGE_NAME) { + this.dev = LEGACY_DEV + this.build = LEGACY_BUILD + return this as DetectedFramework + } + return this as DetectedFramework + } } } diff --git a/packages/build-info/src/frameworks/tanstack-router.test.ts b/packages/build-info/src/frameworks/tanstack-router.test.ts new file mode 100644 index 0000000000..0ae21dffd3 --- /dev/null +++ b/packages/build-info/src/frameworks/tanstack-router.test.ts @@ -0,0 +1,44 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects a TanStack Router site', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ + scripts: { + dev: 'vite --port=3001', + build: 'vite build && tsc --noEmit', + serve: 'vite preview', + start: 'vite', + }, + dependencies: { + '@tanstack/react-router': '^1.58.15', + '@tanstack/router-devtools': '^1.58.15', + '@tanstack/router-plugin': '^1.58.12', + react: '^18.2.0', + 'react-dom': '^18.2.0', + }, + devDependencies: { + vite: '^5.4.5', + }, + }), + }) + const detected = await new Project(fs, cwd).detectFrameworks() + + const detectedFrameworks = (detected ?? []).map((framework) => framework.id) + expect(detectedFrameworks).not.toContain('vinxi') + expect(detectedFrameworks).not.toContain('vite') + expect(detectedFrameworks).not.toContain('tanstack-start') + + expect(detected?.[0]?.id).toBe('tanstack-router') + expect(detected?.[0]?.build?.command).toBe('vite build') + expect(detected?.[0]?.build?.directory).toBe('dist') + expect(detected?.[0]?.dev?.command).toBe('vite dev') + expect(detected?.[0]?.dev?.port).toBe(3000) +}) diff --git a/packages/build-info/src/frameworks/tanstack-router.ts b/packages/build-info/src/frameworks/tanstack-router.ts new file mode 100644 index 0000000000..6dd1820aa2 --- /dev/null +++ b/packages/build-info/src/frameworks/tanstack-router.ts @@ -0,0 +1,26 @@ +import { BaseFramework, Category, Framework } from './framework.js' + +export class TanStackRouter extends BaseFramework implements Framework { + readonly id = 'tanstack-router' + name = 'TanStack Router' + npmDependencies = ['@tanstack/react-router'] + excludedNpmDependencies = ['@tanstack/start'] + category = Category.SSG + + dev = { + command: 'vite dev', + port: 3000, + pollingStrategies: [{ name: 'TCP' }], + } + + build = { + command: 'vite build', + directory: 'dist', + } + + logo = { + default: '/logos/tanstack-router/default.png', + light: '/logos/tanstack-router/default.png', + dark: '/logos/tanstack-router/default.png', + } +} diff --git a/packages/build-info/src/frameworks/tanstack-start.test.ts b/packages/build-info/src/frameworks/tanstack-start.test.ts new file mode 100644 index 0000000000..e739b9263f --- /dev/null +++ b/packages/build-info/src/frameworks/tanstack-start.test.ts @@ -0,0 +1,43 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects a TanStack Start site', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ + scripts: { + dev: 'vinxi dev', + build: 'vinxi build', + start: 'vinxi start', + }, + dependencies: { + '@tanstack/react-router': '^1.58.15', + '@tanstack/router-devtools': '^1.58.15', + '@tanstack/start': '^1.58.15', + react: '^18.3.1', + 'react-dom': '^18.3.1', + }, + devDependencies: { + vinxi: '0.4.3', + }, + }), + }) + const detected = await new Project(fs, cwd).detectFrameworks() + + const detectedFrameworks = (detected ?? []).map((framework) => framework.id) + expect(detectedFrameworks).not.toContain('vinxi') + expect(detectedFrameworks).not.toContain('vite') + expect(detectedFrameworks).not.toContain('tanstack-router') + + expect(detected?.[0]?.id).toBe('tanstack-start') + expect(detected?.[0]?.build?.command).toBe('vinxi build') + expect(detected?.[0]?.build?.directory).toBe('dist') + expect(detected?.[0]?.dev?.command).toBe('vinxi dev') + expect(detected?.[0]?.dev?.port).toBe(3000) +}) diff --git a/packages/build-info/src/frameworks/tanstack-start.ts b/packages/build-info/src/frameworks/tanstack-start.ts new file mode 100644 index 0000000000..d148af6538 --- /dev/null +++ b/packages/build-info/src/frameworks/tanstack-start.ts @@ -0,0 +1,25 @@ +import { BaseFramework, Category, Framework } from './framework.js' + +export class TanStackStart extends BaseFramework implements Framework { + readonly id = 'tanstack-start' + name = 'TanStack Start' + npmDependencies = ['@tanstack/start'] + category = Category.SSG + + dev = { + command: 'vinxi dev', + port: 3000, + pollingStrategies: [{ name: 'TCP' }], + } + + build = { + command: 'vinxi build', + directory: 'dist', + } + + logo = { + default: '/logos/tanstack-start/default.png', + light: '/logos/tanstack-start/default.png', + dark: '/logos/tanstack-start/default.png', + } +} diff --git a/packages/build-info/src/frameworks/vite.ts b/packages/build-info/src/frameworks/vite.ts index 52f2a258c1..ec83c9e2e2 100644 --- a/packages/build-info/src/frameworks/vite.ts +++ b/packages/build-info/src/frameworks/vite.ts @@ -10,8 +10,13 @@ export class Vite extends BaseFramework implements Framework { '@remix-run/server-runtime', '@shopify/hydrogen', '@builder.io/qwik', + // Used this name up to 0.3.11 'solid-start', + // Renamed starting at 0.4.0 + '@solidjs/start', 'solid-js', + '@tanstack/react-router', + '@tanstack/start', '@sveltejs/kit', '@analogjs/platform', ] diff --git a/packages/build-info/src/runtime/bun.test.ts b/packages/build-info/src/runtime/bun.test.ts index 65e7d4e789..07f4e102a8 100644 --- a/packages/build-info/src/runtime/bun.test.ts +++ b/packages/build-info/src/runtime/bun.test.ts @@ -17,6 +17,15 @@ test('detects node when bunfig.toml is present', async ({ fs }) => { expect(detected[0].name).toBe('Bun') }) +test('detects node when bun.lock is present', async ({ fs }) => { + const cwd = mockFileSystem({ + 'bun.lock': '', + }) + + const detected = await new Project(fs, cwd).detectRuntime() + expect(detected[0].name).toBe('Bun') +}) + test('detects node when bun.lockb is present', async ({ fs }) => { const cwd = mockFileSystem({ 'bun.lockb': '', diff --git a/packages/build-info/src/runtime/bun.ts b/packages/build-info/src/runtime/bun.ts index 8b11e6f667..3112d4c7a3 100644 --- a/packages/build-info/src/runtime/bun.ts +++ b/packages/build-info/src/runtime/bun.ts @@ -3,5 +3,5 @@ import { LangRuntime } from './runtime.js' export class Bun extends LangRuntime { id = 'bun' name = 'Bun' - configFiles = ['bun.lockb', 'bunfig.toml'] + configFiles = ['bun.lock', 'bun.lockb', 'bunfig.toml'] } diff --git a/packages/build-info/src/runtime/dotnet.test.ts b/packages/build-info/src/runtime/dotnet.test.ts new file mode 100644 index 0000000000..6fbf44ffa5 --- /dev/null +++ b/packages/build-info/src/runtime/dotnet.test.ts @@ -0,0 +1,27 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects dotnet when Program.cs is present', async ({ fs }) => { + const cwd = mockFileSystem({ + 'Program.cs': '', + }) + + const detected = await new Project(fs, cwd).detectRuntime() + expect(detected[0].name).toBe('Dotnet') +}) + +test('detects dotnet when appsettings.json is present', async ({ fs }) => { + const cwd = mockFileSystem({ + 'appsettings.json': '', + }) + + const detected = await new Project(fs, cwd).detectRuntime() + expect(detected[0].name).toBe('Dotnet') +}) diff --git a/packages/build-info/src/runtime/dotnet.ts b/packages/build-info/src/runtime/dotnet.ts new file mode 100644 index 0000000000..e8fb7445a5 --- /dev/null +++ b/packages/build-info/src/runtime/dotnet.ts @@ -0,0 +1,7 @@ +import { LangRuntime } from './runtime.js' + +export class Dotnet extends LangRuntime { + id = 'dotnet' + name = 'Dotnet' + configFiles = ['Program.cs', 'appsettings.json'] +} diff --git a/packages/build-info/src/runtime/erlang.test.ts b/packages/build-info/src/runtime/erlang.test.ts new file mode 100644 index 0000000000..1a46882ca7 --- /dev/null +++ b/packages/build-info/src/runtime/erlang.test.ts @@ -0,0 +1,29 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects erlang when rebar.config is present', async ({ fs }) => { + const cwd = mockFileSystem({ + 'rebar.config': '', + }) + + const detected = await new Project(fs, cwd).detectRuntime() + expect(detected.length).toBe(1) + expect(detected[0].name).toBe('Erlang') +}) + +test('detects erlang when rebar.lock is present', async ({ fs }) => { + const cwd = mockFileSystem({ + 'rebar.lock': '', + }) + + const detected = await new Project(fs, cwd).detectRuntime() + expect(detected.length).toBe(1) + expect(detected[0].name).toBe('Erlang') +}) diff --git a/packages/build-info/src/runtime/erlang.ts b/packages/build-info/src/runtime/erlang.ts new file mode 100644 index 0000000000..322f761603 --- /dev/null +++ b/packages/build-info/src/runtime/erlang.ts @@ -0,0 +1,7 @@ +import { LangRuntime } from './runtime.js' + +export class Erlang extends LangRuntime { + id = 'erlang' + name = 'Erlang' + configFiles = ['rebar.config', 'rebar.lock'] +} diff --git a/packages/build-info/src/runtime/index.ts b/packages/build-info/src/runtime/index.ts index 080500ba4c..0c321ca4be 100644 --- a/packages/build-info/src/runtime/index.ts +++ b/packages/build-info/src/runtime/index.ts @@ -1,6 +1,8 @@ import { Brew } from './brew.js' import { Bun } from './bun.js' import { Emacs } from './cask.js' +import { Dotnet } from './dotnet.js' +import { Erlang } from './erlang.js' import { Go } from './go.js' import { Java } from './java.js' import { Node } from './node.js' @@ -10,4 +12,4 @@ import { Ruby } from './ruby.js' import { Rust } from './rust.js' import { Swift } from './swift.js' -export const runtimes = [Node, Ruby, Brew, Bun, Emacs, Go, Java, Php, Rust, Swift, Python] +export const runtimes = [Node, Ruby, Brew, Bun, Dotnet, Emacs, Erlang, Go, Java, Php, Rust, Swift, Python] diff --git a/packages/build/CHANGELOG.md b/packages/build/CHANGELOG.md index 1e649a7a93..23d39219bc 100644 --- a/packages/build/CHANGELOG.md +++ b/packages/build/CHANGELOG.md @@ -105,6 +105,149 @@ * dependencies * @netlify/config bumped from ^20.8.0 to ^20.8.1 +## [29.56.1](https://github.com/netlify/build/compare/build-v29.56.0...build-v29.56.1) (2024-11-15) + + +### Bug Fixes + +* **deps:** update dependency hot-shots to v10.2.1 ([#5906](https://github.com/netlify/build/issues/5906)) ([ced3f33](https://github.com/netlify/build/commit/ced3f33658b54855ac5f7cbb5ec12ee234ef1370)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/config bumped from ^20.19.0 to ^20.19.1 + +## [29.56.0](https://github.com/netlify/build/compare/build-v29.55.5...build-v29.56.0) (2024-10-29) + + +### Features + +* keep logging pipe for trusted plugins ([#5899](https://github.com/netlify/build/issues/5899)) ([a631d8c](https://github.com/netlify/build/commit/a631d8c71316b083017cfd702f138e723043c6da)) + +## [29.55.5](https://github.com/netlify/build/compare/build-v29.55.4...build-v29.55.5) (2024-10-25) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.92 to ^5.2.93 + * @netlify/zip-it-and-ship-it bumped from 9.41.0 to 9.41.1 + +## [29.55.4](https://github.com/netlify/build/compare/build-v29.55.3...build-v29.55.4) (2024-10-21) + + +### Bug Fixes + +* various fixes to extension dev/test flow ([#5885](https://github.com/netlify/build/issues/5885)) ([07e567c](https://github.com/netlify/build/commit/07e567c61d25dbade69c3f913a300c055d8f465a)) + +## [29.55.3](https://github.com/netlify/build/compare/build-v29.55.2...build-v29.55.3) (2024-10-21) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.91 to ^5.2.92 + * @netlify/zip-it-and-ship-it bumped from 9.40.2 to 9.41.0 + +## [29.55.2](https://github.com/netlify/build/compare/build-v29.55.1...build-v29.55.2) (2024-10-11) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.90 to ^5.2.91 + * @netlify/zip-it-and-ship-it bumped from 9.40.1 to 9.40.2 + +## [29.55.1](https://github.com/netlify/build/compare/build-v29.55.0...build-v29.55.1) (2024-10-11) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.89 to ^5.2.90 + * @netlify/zip-it-and-ship-it bumped from 9.40.0 to 9.40.1 + +## [29.55.0](https://github.com/netlify/build/compare/build-v29.54.9...build-v29.55.0) (2024-10-10) + + +### Features + +* write metadata file ([#5875](https://github.com/netlify/build/issues/5875)) ([7dfac4c](https://github.com/netlify/build/commit/7dfac4cc38058e135c4952d53ef6b63c8034f962)) + + +### Bug Fixes + +* reading data from spawned process ([#5877](https://github.com/netlify/build/issues/5877)) ([5860e72](https://github.com/netlify/build/commit/5860e7272263dd092c97add354dfa2021518eb5c)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.88 to ^5.2.89 + * @netlify/zip-it-and-ship-it bumped from 9.39.7 to 9.40.0 + +## [29.54.9](https://github.com/netlify/build/compare/build-v29.54.8...build-v29.54.9) (2024-10-01) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.87 to ^5.2.88 + * @netlify/zip-it-and-ship-it bumped from 9.39.6 to 9.39.7 + +## [29.54.8](https://github.com/netlify/build/compare/build-v29.54.7...build-v29.54.8) (2024-10-01) + + +### Bug Fixes + +* **deps:** update dependency hot-shots to v10.1.1 ([#5861](https://github.com/netlify/build/issues/5861)) ([c6a3f3b](https://github.com/netlify/build/commit/c6a3f3b2a5ead444601459909449c63236ba7b5d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.86 to ^5.2.87 + * @netlify/zip-it-and-ship-it bumped from 9.39.5 to 9.39.6 + +## [29.54.7](https://github.com/netlify/build/compare/build-v29.54.6...build-v29.54.7) (2024-09-26) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.85 to ^5.2.86 + * @netlify/zip-it-and-ship-it bumped from 9.39.4 to 9.39.5 + +## [29.54.6](https://github.com/netlify/build/compare/build-v29.54.5...build-v29.54.6) (2024-09-24) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.84 to ^5.2.85 + * @netlify/zip-it-and-ship-it bumped from 9.39.3 to 9.39.4 + +## [29.54.5](https://github.com/netlify/build/compare/build-v29.54.4...build-v29.54.5) (2024-09-23) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/functions-utils bumped from ^5.2.83 to ^5.2.84 + * @netlify/zip-it-and-ship-it bumped from 9.39.2 to 9.39.3 + ## [29.54.4](https://github.com/netlify/build/compare/build-v29.54.3...build-v29.54.4) (2024-09-20) diff --git a/packages/build/package.json b/packages/build/package.json index 7f23d7ff5c..92c5c87979 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/build", - "version": "29.54.4", + "version": "29.56.1", "description": "Netlify build module", "type": "module", "exports": "./lib/index.js", @@ -70,15 +70,15 @@ "@bugsnag/js": "^7.0.0", "@netlify/blobs": "^7.4.0", "@netlify/cache-utils": "^5.1.6", - "@netlify/config": "^20.19.0", + "@netlify/config": "^20.19.1", "@netlify/edge-bundler": "12.2.3", "@netlify/framework-info": "^9.8.13", - "@netlify/functions-utils": "^5.2.83", + "@netlify/functions-utils": "^5.2.93", "@netlify/git-utils": "^5.1.1", "@netlify/opentelemetry-utils": "^1.2.1", "@netlify/plugins-list": "^6.80.0", "@netlify/run-utils": "^5.1.1", - "@netlify/zip-it-and-ship-it": "9.39.2", + "@netlify/zip-it-and-ship-it": "9.41.1", "@sindresorhus/slugify": "^2.0.0", "ansi-escapes": "^6.0.0", "chalk": "^5.0.0", @@ -88,7 +88,7 @@ "figures": "^5.0.0", "filter-obj": "^5.0.0", "got": "^12.0.0", - "hot-shots": "10.0.0", + "hot-shots": "10.2.1", "indent-string": "^5.0.0", "is-plain-obj": "^4.0.0", "js-yaml": "^4.0.0", diff --git a/packages/build/src/core/build.ts b/packages/build/src/core/build.ts index 8f438479f1..63d1a4ad8f 100644 --- a/packages/build/src/core/build.ts +++ b/packages/build/src/core/build.ts @@ -449,6 +449,11 @@ const initAndRunBuild = async function ({ edgeFunctionsBootstrapURL, eventHandlers, }) { + const pluginsEnv = { + ...childEnv, + ...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }), + } + const { pluginsOptions: pluginsOptionsA, timers: timersA } = await getPluginsOptions({ pluginsOptions, netlifyConfig, @@ -469,13 +474,9 @@ const initAndRunBuild = async function ({ integrations, context, systemLog, + pluginsEnv, }) - const pluginsEnv = { - ...childEnv, - ...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }), - } - if (pluginsOptionsA?.length) { const buildPlugins = {} for (const plugin of pluginsOptionsA) { diff --git a/packages/build/src/core/lingering.js b/packages/build/src/core/lingering.js index ef6d18507f..a436a7d061 100644 --- a/packages/build/src/core/lingering.js +++ b/packages/build/src/core/lingering.js @@ -66,6 +66,7 @@ const IGNORED_COMMANDS = [ // Internal buildbot commands '[build]', + 'sleep 5', /buildbot.*\[node]/, // buildbot's main Bash script '/opt/build-bin/build', diff --git a/packages/build/src/install/missing.js b/packages/build/src/install/missing.js index 8df8d5eb29..45c60b810b 100644 --- a/packages/build/src/install/missing.js +++ b/packages/build/src/install/missing.js @@ -34,6 +34,8 @@ export const installIntegrationPlugins = async function ({ logs, context, testOpts, + pluginsEnv, + buildDir, }) { const integrationsToBuild = integrations.filter( (integration) => typeof integration.dev !== 'undefined' && context === 'dev', @@ -46,7 +48,11 @@ export const installIntegrationPlugins = async function ({ ) } const packages = ( - await Promise.all(integrations.map((integration) => getIntegrationPackage({ integration, context, testOpts }))) + await Promise.all( + integrations.map((integration) => + getIntegrationPackage({ integration, context, testOpts, buildDir, pluginsEnv }), + ), + ) ).filter(Boolean) logInstallIntegrations( logs, @@ -64,7 +70,13 @@ export const installIntegrationPlugins = async function ({ await addExactDependencies({ packageRoot: autoPluginsDir, isLocal: mode !== 'buildbot', packages }) } -const getIntegrationPackage = async function ({ integration: { version, dev }, context, testOpts = {} }) { +const getIntegrationPackage = async function ({ + integration: { version, dev }, + context, + testOpts = {}, + buildDir, + pluginsEnv, +}) { if (typeof version !== 'undefined') { return `${version}/packages/buildhooks.tgz` } @@ -72,9 +84,10 @@ const getIntegrationPackage = async function ({ integration: { version, dev }, c if (typeof dev !== 'undefined' && context === 'dev') { const { path } = dev - const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(path) + const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(buildDir, path) + try { - const res = await execa('npm', ['run', 'build'], { cwd: integrationDir }) + const res = await execa('npm', ['run', 'build'], { cwd: integrationDir, env: pluginsEnv }) // This is horrible and hacky, but `npm run build` will // return status code 0 even if it fails @@ -82,7 +95,7 @@ const getIntegrationPackage = async function ({ integration: { version, dev }, c throw new Error(res.stdout) } } catch (e) { - throw new Error(`Failed to build integration`) + throw new Error(`Failed to build integration. Error:\n\n${e.stack}`) } return undefined diff --git a/packages/build/src/log/messages/core_steps.js b/packages/build/src/log/messages/core_steps.js index 6fce440850..e5587e288a 100644 --- a/packages/build/src/log/messages/core_steps.js +++ b/packages/build/src/log/messages/core_steps.js @@ -15,6 +15,13 @@ const logBundleResultFunctions = ({ functions, headerMessage, logs, error }) => logArray(logs, functionNames) } +/** + * Logs the result of bundling functions + * + * @param {object} options + * @param {any} options.logs + * @param {import("@netlify/zip-it-and-ship-it").FunctionResult[]} options.results + */ export const logBundleResults = ({ logs, results = [] }) => { const resultsWithErrors = results.filter(({ bundlerErrors }) => bundlerErrors && bundlerErrors.length !== 0) const resultsWithWarnings = results.filter( diff --git a/packages/build/src/plugins/ipc.js b/packages/build/src/plugins/ipc.js index 19509b4d49..0dbf6955f2 100644 --- a/packages/build/src/plugins/ipc.js +++ b/packages/build/src/plugins/ipc.js @@ -39,8 +39,8 @@ export const getEventFromChild = async function (childProcess, callId) { throw getChildExitError('Could not receive event from child process because it already exited.') } - const messagePromise = pEvent(childProcess, 'message', { filter: ([actualCallId]) => actualCallId === callId }) - const errorPromise = pEvent(childProcess, 'message', { filter: ([actualCallId]) => actualCallId === 'error' }) + const messagePromise = pEvent(childProcess, 'message', { filter: (data) => data?.[0] === callId }) + const errorPromise = pEvent(childProcess, 'message', { filter: (data) => data?.[0] === 'error' }) const exitPromise = pEvent(childProcess, 'exit', { multiArgs: true }) try { return await Promise.race([getMessage(messagePromise), getError(errorPromise), getExit(exitPromise)]) diff --git a/packages/build/src/plugins/options.ts b/packages/build/src/plugins/options.ts index f895006514..e9347f7bba 100644 --- a/packages/build/src/plugins/options.ts +++ b/packages/build/src/plugins/options.ts @@ -32,6 +32,7 @@ const tGetPluginsOptions = async function ({ integrations, context, systemLog, + pluginsEnv, }) { const pluginsOptionsA = await resolvePluginsPath({ pluginsOptions, @@ -51,6 +52,7 @@ const tGetPluginsOptions = async function ({ integrations, context, systemLog, + pluginsEnv, }) const pluginsOptionsB = await Promise.all( pluginsOptionsA.map((pluginOptions) => loadPluginFiles({ pluginOptions, debug })), diff --git a/packages/build/src/plugins/resolve.js b/packages/build/src/plugins/resolve.js index c39a9aef7e..7ff2dc9dcc 100644 --- a/packages/build/src/plugins/resolve.js +++ b/packages/build/src/plugins/resolve.js @@ -33,6 +33,7 @@ export const resolvePluginsPath = async function ({ integrations, context, systemLog, + pluginsEnv, }) { const autoPluginsDir = getAutoPluginsDir(buildDir, packagePath) const pluginsOptionsA = await Promise.all( @@ -77,6 +78,7 @@ export const resolvePluginsPath = async function ({ buildDir, context, testOpts, + pluginsEnv, }) return [...pluginsOptionsE, ...integrationPluginOptions] @@ -164,9 +166,27 @@ const handleMissingPlugins = async function ({ pluginsOptions, autoPluginsDir, m return Promise.all(pluginsOptions.map((pluginOptions) => resolveMissingPluginPath({ pluginOptions, autoPluginsDir }))) } -const handleIntegrations = async function ({ integrations, autoPluginsDir, mode, logs, buildDir, context, testOpts }) { +const handleIntegrations = async function ({ + integrations, + autoPluginsDir, + mode, + logs, + buildDir, + context, + testOpts, + pluginsEnv, +}) { const toInstall = integrations.filter((integration) => integration.has_build) - await installIntegrationPlugins({ integrations: toInstall, autoPluginsDir, mode, logs, context, testOpts }) + await installIntegrationPlugins({ + integrations: toInstall, + autoPluginsDir, + mode, + logs, + context, + testOpts, + buildDir, + pluginsEnv, + }) if (toInstall.length === 0) { return [] @@ -188,7 +208,7 @@ const handleIntegrations = async function ({ integrations, autoPluginsDir, mode, const resolveIntegration = async function ({ integration, autoPluginsDir, buildDir, context, testOpts }) { if (typeof integration.dev !== 'undefined' && context === 'dev') { const { path } = integration.dev - const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(path) + const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(buildDir, path) const pluginPath = await resolvePath(`${integrationDir}/.ntli/build`, buildDir) return { pluginPath, packageName: `${integration.slug}`, isIntegration: true, integration, loadedFrom: 'local' } diff --git a/packages/build/src/plugins/spawn.ts b/packages/build/src/plugins/spawn.ts index 75073ad482..7e67a30a52 100644 --- a/packages/build/src/plugins/spawn.ts +++ b/packages/build/src/plugins/spawn.ts @@ -1,10 +1,11 @@ import { createRequire } from 'module' +import { platform } from 'os' import { fileURLToPath, pathToFileURL } from 'url' import { promisify } from 'util' import { trace } from '@opentelemetry/api' import { ExecaChildProcess, execaNode } from 'execa' -import { gte } from 'semver' +import { gte, satisfies } from 'semver' import { FeatureFlags } from '../core/feature_flags.js' import { addErrorInfo } from '../error/info.js' @@ -217,5 +218,17 @@ const stopPlugin = async function ({ }) childProcess.disconnect() } - childProcess.kill() + + // On Windows with Node 21+, there's a bug where attempting to kill a child process + // results in an EPERM error. Ignore the error in that case. + // See: https://github.com/nodejs/node/issues/51766 + // We also disable execa's `forceKillAfterTimeout` in this case + // which can cause unhandled rejection. + try { + childProcess.kill('SIGTERM', { + forceKillAfterTimeout: platform() === 'win32' && satisfies(process.version, '>=21') ? false : undefined, + }) + } catch { + // no-op + } } diff --git a/packages/build/src/plugins_core/blobs_upload/index.ts b/packages/build/src/plugins_core/blobs_upload/index.ts index b7d76a52a7..7ee7596146 100644 --- a/packages/build/src/plugins_core/blobs_upload/index.ts +++ b/packages/build/src/plugins_core/blobs_upload/index.ts @@ -72,7 +72,8 @@ const coreStep: CoreStepFunction = async function ({ systemLog(`Uploading blob ${key}`) const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath) - await blobStore.set(key, data, { metadata }) + const arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.length) + await blobStore.set(key, arrayBuffer, { metadata }) }, { concurrency: 10 }, ) diff --git a/packages/build/src/plugins_core/dev_blobs_upload/index.ts b/packages/build/src/plugins_core/dev_blobs_upload/index.ts index 525a8066b2..2b7a645067 100644 --- a/packages/build/src/plugins_core/dev_blobs_upload/index.ts +++ b/packages/build/src/plugins_core/dev_blobs_upload/index.ts @@ -77,7 +77,8 @@ const coreStep: CoreStepFunction = async function ({ log(logs, `- Uploading blob ${key}`, { indent: true }) } const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath) - await blobStore.set(key, data, { metadata }) + const arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.length) + await blobStore.set(key, arrayBuffer, { metadata }) }, { concurrency: 10 }, ) diff --git a/packages/build/src/plugins_core/functions/index.ts b/packages/build/src/plugins_core/functions/index.ts index 29beaec90f..a5819f8179 100644 --- a/packages/build/src/plugins_core/functions/index.ts +++ b/packages/build/src/plugins_core/functions/index.ts @@ -1,6 +1,6 @@ import { resolve } from 'path' -import { NodeBundlerName, RUNTIME, zipFunctions } from '@netlify/zip-it-and-ship-it' +import { NodeBundlerName, RUNTIME, zipFunctions, FunctionResult } from '@netlify/zip-it-and-ship-it' import { pathExists } from 'path-exists' import { addErrorInfo } from '../../error/info.js' @@ -13,7 +13,7 @@ import { getUserAndInternalFunctions, validateFunctionsSrc } from './utils.js' import { getZisiParameters } from './zisi.js' // Get a list of all unique bundlers in this run -const getBundlers = (results: Awaited> = []) => +const getBundlers = (results: FunctionResult[] = []) => // using a Set to filter duplicates new Set( results @@ -38,7 +38,7 @@ const eventTriggeredFunctions = new Set([ 'identity-login', ]) -const validateCustomRoutes = function (functions: Awaited>) { +const validateCustomRoutes = function (functions: FunctionResult[]) { for (const { routes, name, schedule } of functions) { if (!routes || routes.length === 0) continue @@ -61,6 +61,7 @@ const validateCustomRoutes = function (functions: Awaited { const zisiParameters = getZisiParameters({ + branch, buildDir, childEnv, featureFlags, @@ -118,6 +120,7 @@ const coreStep = async function ({ FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, + branch, packagePath, logs, netlifyConfig, @@ -166,6 +169,7 @@ const coreStep = async function ({ } const { bundlers } = await zipFunctionsAndLogResults({ + branch, buildDir, childEnv, featureFlags, @@ -237,7 +241,7 @@ export const bundleFunctions = { // `zip-it-and-ship-it` methods. Therefore, we need to use an intermediary // function and export them so tests can use it. export const zipItAndShipIt = { - async zipFunctions(...args: Parameters) { + async zipFunctions(...args: Parameters): Promise { return await zipFunctions(...args) }, } diff --git a/packages/build/src/plugins_core/functions/zisi.ts b/packages/build/src/plugins_core/functions/zisi.ts index 41651e3cd3..d6204b3094 100644 --- a/packages/build/src/plugins_core/functions/zisi.ts +++ b/packages/build/src/plugins_core/functions/zisi.ts @@ -9,6 +9,7 @@ import type { FeatureFlags } from '../../core/feature_flags.js' import { getZisiFeatureFlags } from './feature_flags.js' type GetZisiParametersType = { + branch?: string buildDir: string childEnv: Record featureFlags: FeatureFlags @@ -40,6 +41,7 @@ const getLambdaNodeVersion = (childEnv: Record, userNodeVersion: } export const getZisiParameters = ({ + branch, buildDir, childEnv, featureFlags, @@ -65,6 +67,7 @@ export const getZisiParameters = ({ return { basePath: buildDir, + branch, config, manifest, featureFlags: zisiFeatureFlags, diff --git a/packages/build/src/plugins_core/pre_dev_cleanup/index.ts b/packages/build/src/plugins_core/pre_dev_cleanup/index.ts index c4a41a9c1c..95c50eefa1 100644 --- a/packages/build/src/plugins_core/pre_dev_cleanup/index.ts +++ b/packages/build/src/plugins_core/pre_dev_cleanup/index.ts @@ -3,7 +3,6 @@ import { resolve } from 'node:path' import { listFrameworks } from '@netlify/framework-info' -import { log } from '../../log/logger.js' import { CoreStep, CoreStepCondition, CoreStepFunction, CoreStepFunctionArgs } from '../types.js' const dirExists = async (path: string): Promise => { @@ -37,7 +36,7 @@ const coreStep: CoreStepFunction = async (input) => { for (const dir of dirs) { await rm(resolve(input.buildDir, dir), { recursive: true, force: true }) } - log(input.logs, `Cleaned up ${dirs.join(', ')}.`) + input.systemLog(input.logs, `Cleaned up ${dirs.join(', ')}.`) return {} } diff --git a/packages/build/src/steps/plugin.js b/packages/build/src/steps/plugin.js index 58053fbf23..c4ef936492 100644 --- a/packages/build/src/steps/plugin.js +++ b/packages/build/src/steps/plugin.js @@ -108,7 +108,9 @@ export const firePluginStep = async function ({ }) return { newError } } finally { - await unpipePluginOutput(childProcess, logs, listeners, standardStreams) + if (!isTrustedPlugin(pluginPackageJson?.name) || listeners) { + await unpipePluginOutput(childProcess, logs, listeners, standardStreams) + } logStepCompleted(logs, verbose) } } diff --git a/packages/build/tests/functions/fixtures/v2/netlify/functions/test.mjs b/packages/build/tests/functions/fixtures/v2/netlify/functions/test.mjs new file mode 100644 index 0000000000..11d0241321 --- /dev/null +++ b/packages/build/tests/functions/fixtures/v2/netlify/functions/test.mjs @@ -0,0 +1,3 @@ +export default async () => new Response("Hello") + +export const config = { path: "/hello" } diff --git a/packages/build/tests/functions/tests.js b/packages/build/tests/functions/tests.js index 3d7d505bf9..a23893c647 100644 --- a/packages/build/tests/functions/tests.js +++ b/packages/build/tests/functions/tests.js @@ -1,9 +1,9 @@ import { readdir, readFile, rm, stat, writeFile } from 'fs/promises' -import { resolve } from 'path' +import { join, resolve } from 'path' import { version as nodeVersion } from 'process' import { fileURLToPath } from 'url' -import { Fixture, normalizeOutput, removeDir, getTempName } from '@netlify/testing' +import { Fixture, normalizeOutput, removeDir, getTempName, unzipFile } from '@netlify/testing' import test from 'ava' import { pathExists } from 'path-exists' import semver from 'semver' @@ -204,3 +204,39 @@ if (semver.gte(nodeVersion, '16.9.0')) { t.true(app2FunctionsDist.includes('worker.zip')) }) } + +test('Functions: creates metadata file', async (t) => { + const fixture = await new Fixture('./fixtures/v2').withCopyRoot({ git: false }) + const build = await fixture + .withFlags({ + branch: 'my-branch', + cwd: fixture.repositoryRoot, + featureFlags: { zisi_add_metadata_file: true }, + }) + .runWithBuildAndIntrospect() + + t.true(build.success) + + const functionsDistPath = resolve(fixture.repositoryRoot, '.netlify/functions') + const functionsDistFiles = await readdir(functionsDistPath) + + t.true(functionsDistFiles.includes('manifest.json')) + t.true(functionsDistFiles.includes('test.zip')) + + const unzipPath = join(functionsDistPath, `.netlify-test-${Date.now()}`) + + await unzipFile(join(functionsDistPath, 'test.zip'), unzipPath) + + const functionFiles = await readdir(unzipPath) + + t.true(functionFiles.includes('___netlify-bootstrap.mjs')) + t.true(functionFiles.includes('___netlify-entry-point.mjs')) + t.true(functionFiles.includes('___netlify-metadata.json')) + t.true(functionFiles.includes('test.mjs')) + + const metadata = JSON.parse(await readFile(join(unzipPath, '___netlify-metadata.json'), 'utf8')) + + t.is(semver.valid(metadata.bootstrap_version), metadata.bootstrap_version) + t.is(metadata.branch, 'my-branch') + t.is(metadata.version, 1) +}) diff --git a/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/.gitkeep b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/index.js b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/index.js index e69de29bb2..8c1f901340 100644 --- a/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/index.js +++ b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/index.js @@ -0,0 +1,3 @@ +export const onPreBuild = function () { + console.log("Hello world"); +} diff --git a/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/manifest.yml b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/manifest.yml new file mode 100644 index 0000000000..bbc1fee978 --- /dev/null +++ b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/manifest.yml @@ -0,0 +1,2 @@ +name: abc-integration +inputs: [] diff --git a/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/package.json b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/package.json new file mode 100644 index 0000000000..507b311db0 --- /dev/null +++ b/packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/package.json @@ -0,0 +1,7 @@ +{ + "main": "index.js", + "type": "module", + "version": "0.0.0", + "name": "abc-integration", + "dependencies": {} +} diff --git a/packages/build/tests/install/fixtures/local_missing_integration/integration/manifest.yml b/packages/build/tests/install/fixtures/local_missing_integration/integration/manifest.yml index a3512f0259..bbc1fee978 100644 --- a/packages/build/tests/install/fixtures/local_missing_integration/integration/manifest.yml +++ b/packages/build/tests/install/fixtures/local_missing_integration/integration/manifest.yml @@ -1,2 +1,2 @@ -name: test +name: abc-integration inputs: [] diff --git a/packages/build/tests/install/fixtures/local_missing_integration/integration/package.json b/packages/build/tests/install/fixtures/local_missing_integration/integration/package.json index 8521f0f9aa..26fafe3703 100644 --- a/packages/build/tests/install/fixtures/local_missing_integration/integration/package.json +++ b/packages/build/tests/install/fixtures/local_missing_integration/integration/package.json @@ -1,5 +1,5 @@ { - "name": "plugin_deps_plugin", + "name": "abc-integration", "version": "0.0.1", "type": "module", "scripts": { diff --git a/packages/build/tests/install/fixtures/local_missing_integration/netlify.toml b/packages/build/tests/install/fixtures/local_missing_integration/netlify.toml index c7204d2e8c..45a6d67bac 100644 --- a/packages/build/tests/install/fixtures/local_missing_integration/netlify.toml +++ b/packages/build/tests/install/fixtures/local_missing_integration/netlify.toml @@ -1,5 +1,5 @@ [[integrations]] -name = "abc-integration" +name = "test" [integrations.dev] path = "./integration" diff --git a/packages/build/tests/install/snapshots/tests.js.md b/packages/build/tests/install/snapshots/tests.js.md index 9b23f807b5..5f3ce01703 100644 --- a/packages/build/tests/install/snapshots/tests.js.md +++ b/packages/build/tests/install/snapshots/tests.js.md @@ -1051,7 +1051,6 @@ Generated by [AVA](https://avajs.dev). debug: true␊ repositoryRoot: packages/build/tests/install/fixtures/local_missing_integration␊ testOpts:␊ - cwd: ./tests/install/fixtures/local_missing_integration/␊ pluginsListUrl: test␊ silentLingeringProcesses: true␊ ␊ @@ -1070,10 +1069,18 @@ Generated by [AVA](https://avajs.dev). dev␊ ␊ > Building integrations␊ - - abc-integration from ./integration␊ + - test from ./integration␊ ␊ > Loading integrations␊ - - abc-integration␊ + - test␊ + ␊ + test (onPreBuild event) ␊ + ────────────────────────────────────────────────────────────────␊ + ␊ + Hello world␊ + ␊ + (test onPreBuild completed in 1ms)␊ + Build step duration: test onPreBuild completed in 1ms␊ ␊ Netlify Build Complete ␊ ────────────────────────────────────────────────────────────────␊ diff --git a/packages/build/tests/install/snapshots/tests.js.snap b/packages/build/tests/install/snapshots/tests.js.snap index 8828ee6096..490600bcfb 100644 Binary files a/packages/build/tests/install/snapshots/tests.js.snap and b/packages/build/tests/install/snapshots/tests.js.snap differ diff --git a/packages/build/tests/install/tests.js b/packages/build/tests/install/tests.js index 343da99ad0..1a81eb2ac4 100644 --- a/packages/build/tests/install/tests.js +++ b/packages/build/tests/install/tests.js @@ -154,14 +154,7 @@ test('Install local plugin dependencies: missing plugin in netlify.toml', async }) test('In integration dev mode, install local plugins and install the integration when forcing build', async (t) => { - const output = await new Fixture('./fixtures/local_missing_integration') - .withFlags({ - context: 'dev', - testOpts: { - cwd: './tests/install/fixtures/local_missing_integration/', - }, - }) - .runWithBuild() + const output = await new Fixture('./fixtures/local_missing_integration').withFlags({ context: 'dev' }).runWithBuild() t.snapshot(normalizeOutput(output)) }) diff --git a/packages/build/tests/plugins/fixtures/process_send_object/manifest.yml b/packages/build/tests/plugins/fixtures/process_send_object/manifest.yml new file mode 100644 index 0000000000..a3512f0259 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/process_send_object/manifest.yml @@ -0,0 +1,2 @@ +name: test +inputs: [] diff --git a/packages/build/tests/plugins/fixtures/process_send_object/netlify.toml b/packages/build/tests/plugins/fixtures/process_send_object/netlify.toml new file mode 100644 index 0000000000..4b06556c85 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/process_send_object/netlify.toml @@ -0,0 +1,2 @@ +[[plugins]] +package = "./plugin.js" diff --git a/packages/build/tests/plugins/fixtures/process_send_object/plugin.js b/packages/build/tests/plugins/fixtures/process_send_object/plugin.js new file mode 100644 index 0000000000..9a99d0ee52 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/process_send_object/plugin.js @@ -0,0 +1,5 @@ +import process from 'process'; + +export const onPreBuild = function () { + process.send({}); +} diff --git a/packages/build/tests/plugins/tests.js b/packages/build/tests/plugins/tests.js index fdb4d7ffaf..0ddd21e14d 100644 --- a/packages/build/tests/plugins/tests.js +++ b/packages/build/tests/plugins/tests.js @@ -401,3 +401,8 @@ test('Plugins have a pre-populated Blobs context', async (t) => { t.is(netlifyConfig.build.command, `echo ""Hello there""`) }) + +test('Plugins can respond anything to parent process', async (t) => { + const build = await new Fixture('./fixtures/process_send_object').runBuildBinary() + t.true(build.exitCode === 0) +}) diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 6bee6a4aaa..a216de5f51 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -76,6 +76,13 @@ * dependencies * netlify bumped from ^13.1.8 to ^13.1.9 +## [20.19.1](https://github.com/netlify/build/compare/config-v20.19.0...config-v20.19.1) (2024-11-15) + + +### Bug Fixes + +* environment variable build context filtering fix ([#5887](https://github.com/netlify/build/issues/5887)) ([c0ab547](https://github.com/netlify/build/commit/c0ab5479bd533315d39983e50a9c8a1bee5966ff)) + ## [20.19.0](https://github.com/netlify/build/compare/config-v20.18.0...config-v20.19.0) (2024-08-21) diff --git a/packages/config/package.json b/packages/config/package.json index 92e9d3c8f5..571897ba0c 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/config", - "version": "20.19.0", + "version": "20.19.1", "description": "Netlify config module", "type": "module", "exports": "./lib/index.js", diff --git a/packages/config/src/api/site_info.ts b/packages/config/src/api/site_info.ts index 699219768d..4ac6db793e 100644 --- a/packages/config/src/api/site_info.ts +++ b/packages/config/src/api/site_info.ts @@ -35,53 +35,18 @@ export const getSiteInfo = async function ({ context, offline = false, testOpts = {}, - featureFlags = {}, siteFeatureFlagPrefix, }: GetSiteInfoOpts) { const { env: testEnv = false } = testOpts - const useV2Endpoint = !!accountId && featureFlags.cli_integration_installations_meta - - if (useV2Endpoint) { - if (api === undefined || mode === 'buildbot' || testEnv) { - const siteInfo: { id?: string; account_id?: string } = {} - - if (siteId !== undefined) siteInfo.id = siteId - if (accountId !== undefined) siteInfo.account_id = accountId - - const integrations = - mode === 'buildbot' && !offline - ? await getIntegrations({ siteId, testOpts, offline, useV2Endpoint, accountId }) - : [] - - return { siteInfo, accounts: [], addons: [], integrations } - } - - const promises = [ - getSite(api, siteId, siteFeatureFlagPrefix), - getAccounts(api), - getAddons(api, siteId), - getIntegrations({ siteId, testOpts, offline, useV2Endpoint, accountId }), - ] - - const [siteInfo, accounts, addons, integrations] = await Promise.all(promises) - - if (siteInfo.use_envelope) { - const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, siteId, context }) - - siteInfo.build_settings.env = envelope - } - - return { siteInfo, accounts, addons, integrations } - } - if (api === undefined || mode === 'buildbot' || testEnv) { const siteInfo: { id?: string; account_id?: string } = {} if (siteId !== undefined) siteInfo.id = siteId if (accountId !== undefined) siteInfo.account_id = accountId - const integrations = mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline }) : [] + const integrations = + mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts, offline, accountId }) : [] return { siteInfo, accounts: [], addons: [], integrations } } @@ -90,7 +55,7 @@ export const getSiteInfo = async function ({ getSite(api, siteId, siteFeatureFlagPrefix), getAccounts(api), getAddons(api, siteId), - getIntegrations({ siteId, testOpts, offline }), + getIntegrations({ siteId, testOpts, offline, accountId }), ] const [siteInfo, accounts, addons, integrations] = await Promise.all(promises) @@ -144,7 +109,6 @@ type GetIntegrationsOpts = { accountId?: string testOpts: TestOptions offline: boolean - useV2Endpoint?: boolean } const getIntegrations = async function ({ @@ -152,7 +116,6 @@ const getIntegrations = async function ({ accountId, testOpts, offline, - useV2Endpoint, }: GetIntegrationsOpts): Promise { if (!siteId || offline) { return [] @@ -162,8 +125,8 @@ const getIntegrations = async function ({ const baseUrl = new URL(host ? `http://${host}` : `https://api.netlifysdk.com`) - // use future state feature flag - const url = useV2Endpoint + // if accountId isn't present, use safe v1 endpoint + const url = accountId ? `${baseUrl}team/${accountId}/integrations/installations/meta/${siteId}` : `${baseUrl}site/${siteId}/integrations/safe` diff --git a/packages/config/src/case.js b/packages/config/src/case.ts similarity index 68% rename from packages/config/src/case.js rename to packages/config/src/case.ts index e8755ed876..0bbdad2180 100644 --- a/packages/config/src/case.js +++ b/packages/config/src/case.ts @@ -1,10 +1,18 @@ // Some properties can be optionally capitalized. We normalize them to lowercase -export const normalizeConfigCase = function ({ Build, build = Build, ...config }) { +export const normalizeConfigCase = function ({ + Build, + build = Build, + ...config +}: { + Build: Record + build: Record + [key: string]: unknown +}): Record { const buildA = normalizeBuildCase(build) return { ...config, build: buildA } } -const normalizeBuildCase = function ({ +const normalizeBuildCase = ({ Base, base = Base, Command, @@ -22,7 +30,7 @@ const normalizeBuildCase = function ({ Publish, publish = Publish, ...build -} = {}) { +}: Record = {}): Record => { return { ...build, base, diff --git a/packages/config/src/env/envelope.ts b/packages/config/src/env/envelope.ts index d75e0786ee..3f4d88e1e3 100644 --- a/packages/config/src/env/envelope.ts +++ b/packages/config/src/env/envelope.ts @@ -17,10 +17,10 @@ export const getEnvelope = async function ({ try { const environmentVariables = await (api as any).getEnvVars({ accountId, siteId, context_name: context }) - const sortedEnvVarsFromDevContext = environmentVariables + const sortedEnvVarsFromContext = environmentVariables .sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1)) .reduce((acc, cur) => { - const envVar = cur.values.find((val) => ['dev', 'all'].includes(val.context)) + const envVar = cur.values.find((val) => ['all', context].includes(val.context)) if (envVar && envVar.value) { return { ...acc, @@ -29,7 +29,7 @@ export const getEnvelope = async function ({ } return acc }, {}) - return sortedEnvVarsFromDevContext + return sortedEnvVarsFromContext } catch { return {} } diff --git a/packages/config/src/env/main.ts b/packages/config/src/env/main.ts index ea73911eaf..5ce066ba93 100644 --- a/packages/config/src/env/main.ts +++ b/packages/config/src/env/main.ts @@ -181,7 +181,7 @@ const getAccountEnv = async function ({ context?: string }) { if (siteInfo.use_envelope) { - const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, context }) + const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, siteId: siteInfo.site_id, context }) return envelope } const { site_env: siteEnv = {} } = accounts.find(({ slug }) => slug === siteInfo.account_slug) || {} diff --git a/packages/config/tests/api/tests.js b/packages/config/tests/api/tests.js index 8910aea2a8..897c9cc9ce 100644 --- a/packages/config/tests/api/tests.js +++ b/packages/config/tests/api/tests.js @@ -20,7 +20,7 @@ const SITE_INTEGRATIONS_RESPONSE = { response: [ { slug: 'test', - version: 'so-cool', + version: 'so-cool-v1', has_build: true, }, ], @@ -31,7 +31,7 @@ const TEAM_INSTALLATIONS_META_RESPONSE = { response: [ { slug: 'test', - version: 'so-cool', + version: 'so-cool-v2', has_build: true, }, ], @@ -42,6 +42,11 @@ const SITE_INTEGRATIONS_EMPTY_RESPONSE = { response: [], } +const TEAM_INSTALLATIONS_META_EMPTY_RESPONSE = { + path: '/team/account1/integrations/installations/meta/test', + response: [], +} + const SITE_INFO_BUILD_SETTINGS = { path: SITE_INFO_PATH, response: { @@ -222,7 +227,7 @@ test('Build settings are not used in CI', async (t) => { t.snapshot(normalizeOutput(output)) }) -test('Integrations are returned if feature flag is true', async (t) => { +test('Integrations are returned from getSiteInfo from v1 safe API when there is not accountID', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ token: 'test', @@ -235,7 +240,7 @@ test('Integrations are returned if feature flag is true', async (t) => { t.assert(config.integrations) t.assert(config.integrations.length === 1) t.assert(config.integrations[0].slug === 'test') - t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].version === 'so-cool-v1') t.assert(config.integrations[0].has_build === true) }) @@ -244,8 +249,9 @@ test('Integration specified in config is also returned if integration is availab .withFlags({ token: 'test', siteId: 'test', + accountId: 'account1', }) - .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_RESPONSE]) const config = JSON.parse(output) @@ -262,8 +268,9 @@ test('Integration specified in config is not returned if integration is not avai .withFlags({ token: 'test', siteId: 'test', + accountId: 'account1', }) - .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) @@ -278,8 +285,9 @@ test('In integration dev mode, integration specified in config is returned even token: 'test', siteId: 'test', context: 'dev', + accountId: 'account1', }) - .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) @@ -297,8 +305,9 @@ test('In integration dev mode, integration specified in config is returned even token: 'test', siteId: 'test', context: 'dev', + accountId: 'account1', }) - .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_EMPTY_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_EMPTY_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) @@ -309,8 +318,8 @@ test('In integration dev mode, integration specified in config is returned even t.assert(config.integrations[0].version === undefined) }) -test('In integration dev mode, integration specified in config is returned and build is forced by config', async (t) => { - const { output } = await new Fixture('./fixtures/dev_integration_with_force_build') +test('In integration dev mode, integration specified in config is returned even if integration is not enabled on site and accountId not present', async (t) => { + const { output } = await new Fixture('./fixtures/dev_integration') .withFlags({ token: 'test', siteId: 'test', @@ -323,76 +332,52 @@ test('In integration dev mode, integration specified in config is returned and b t.assert(config.integrations) t.assert(config.integrations.length === 1) t.assert(config.integrations[0].slug === 'abc-integration') - t.assert(config.integrations[0].has_build === true) + t.assert(config.integrations[0].has_build === false) t.assert(config.integrations[0].version === undefined) }) -test('Integrations are not returned if offline', async (t) => { - const { output } = await new Fixture('./fixtures/base') +test('In integration dev mode, integration specified in config is returned and build is forced by config', async (t) => { + const { output } = await new Fixture('./fixtures/dev_integration_with_force_build') .withFlags({ - offline: true, + token: 'test', siteId: 'test', - mode: 'buildbot', + context: 'dev', + accountId: 'account1', }) - .runConfigServer([SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_EMPTY_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) t.assert(config.integrations) - t.assert(config.integrations.length === 0) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'abc-integration') + t.assert(config.integrations[0].has_build === true) + t.assert(config.integrations[0].version === undefined) }) -test('Integrations and account id are returned if feature flag is false and mode is buildbot', async (t) => { +test('Integrations are not returned if offline', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ + offline: true, siteId: 'test', mode: 'buildbot', accountId: 'account1', - token: 'test', }) - .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) t.assert(config.integrations) - t.is(config.integrations.length, 1) - t.is(config.integrations[0].slug, 'test') - t.is(config.integrations[0].version, 'so-cool') - t.is(config.integrations[0].has_build, true) - - // account id is also available - t.assert(config.siteInfo) - t.is(config.siteInfo.account_id, 'account1') -}) - -test('Integrations are returned if feature flag is false and mode is dev', async (t) => { - const { output } = await new Fixture('./fixtures/base') - .withFlags({ - siteId: 'test', - mode: 'dev', - token: 'test', - }) - .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) - - const config = JSON.parse(output) - - t.assert(config.integrations) - t.assert(config.integrations.length === 1) - t.assert(config.integrations[0].slug === 'test') - t.assert(config.integrations[0].version === 'so-cool') - t.assert(config.integrations[0].has_build === true) + t.assert(config.integrations.length === 0) }) -test('Integrations and account id are returned if flag is true for site and mode is buildbot', async (t) => { +test('Integrations and account id are returned if mode is buildbot', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ siteId: 'test', mode: 'buildbot', - token: 'test', accountId: 'account1', - featureFlags: { - cli_integration_installations_meta: true, - }, + token: 'test', }) .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) @@ -401,7 +386,7 @@ test('Integrations and account id are returned if flag is true for site and mode t.assert(config.integrations) t.is(config.integrations.length, 1) t.is(config.integrations[0].slug, 'test') - t.is(config.integrations[0].version, 'so-cool') + t.is(config.integrations[0].version, 'so-cool-v2') t.is(config.integrations[0].has_build, true) // account id is also available @@ -409,16 +394,13 @@ test('Integrations and account id are returned if flag is true for site and mode t.is(config.siteInfo.account_id, 'account1') }) -test('Integrations are returned if flag is true for site and mode is dev', async (t) => { +test('Integrations are returned if accountId is present and mode is dev', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ siteId: 'test', mode: 'dev', token: 'test', accountId: 'account1', - featureFlags: { - cli_integration_installations_meta: true, - }, }) .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) @@ -427,7 +409,7 @@ test('Integrations are returned if flag is true for site and mode is dev', async t.assert(config.integrations) t.assert(config.integrations.length === 1) t.assert(config.integrations[0].slug === 'test') - t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].version === 'so-cool-v2') t.assert(config.integrations[0].has_build === true) }) diff --git a/packages/config/tests/env/tests.js b/packages/config/tests/env/tests.js index 0d82d7f9d9..a8d6463132 100644 --- a/packages/config/tests/env/tests.js +++ b/packages/config/tests/env/tests.js @@ -310,7 +310,7 @@ test('Sets environment variables when configured to use Envelope', async (t) => t.deepEqual(env.URL.sources, ['general']) t.is(env.URL.value, 'test') t.is(env.SHARED_ENV_VAR.value, 'ENVELOPE_TEAM_ALL') - t.is(env.SITE_ENV_VAR.value, 'ENVELOPE_SITE_DEV') + t.is(env.SITE_ENV_VAR.value, 'ENVELOPE_SITE_PROD') t.is(env.MONGO_ENV_VAR, undefined) }) diff --git a/packages/edge-bundler/node/bridge.ts b/packages/edge-bundler/node/bridge.ts index 8405c60978..a597bd3275 100644 --- a/packages/edge-bundler/node/bridge.ts +++ b/packages/edge-bundler/node/bridge.ts @@ -13,11 +13,10 @@ import { getBinaryExtension } from './platform.js' const DENO_VERSION_FILE = 'version.txt' -// When updating DENO_VERSION_RANGE, ensure that the deno version installed in the -// build-image/buildbot does satisfy this range! -// We're pinning the range because of an issue with v1.45.0 of the Deno CLI: -// https://linear.app/netlify/issue/FRP-775/deno-cli-v1450-causing-issues -const DENO_VERSION_RANGE = '1.37.0 - 1.44.4' +// When updating DENO_VERSION_RANGE, ensure that the deno version +// on the netlify/buildbot build image satisfies this range! +// https://github.com/netlify/buildbot/blob/f9c03c9dcb091d6570e9d0778381560d469e78ad/build-image/noble/Dockerfile#L410 +const DENO_VERSION_RANGE = '1.39.0 - 1.46.3' type OnBeforeDownloadHook = () => void | Promise type OnAfterDownloadHook = (error?: Error) => void | Promise diff --git a/packages/edge-bundler/node/server/util.ts b/packages/edge-bundler/node/server/util.ts index 058018456a..e3c416c634 100644 --- a/packages/edge-bundler/node/server/util.ts +++ b/packages/edge-bundler/node/server/util.ts @@ -1,6 +1,9 @@ +import { platform } from 'os' + import { ExecaChildProcess } from 'execa' import fetch from 'node-fetch' import waitFor from 'p-wait-for' +import { satisfies } from 'semver' // 1 second const SERVER_KILL_TIMEOUT = 1e3 @@ -43,9 +46,19 @@ const killProcess = (ps: ExecaChildProcess) => { ps.on('close', resolve) ps.on('error', reject) - ps.kill('SIGTERM', { - forceKillAfterTimeout: SERVER_KILL_TIMEOUT, - }) + // On Windows with Node 21+, there's a bug where attempting to kill a child process + // results in an EPERM error. Ignore the error in that case. + // See: https://github.com/nodejs/node/issues/51766 + // We also disable execa's `forceKillAfterTimeout` in this case + // which can cause unhandled rejection. + try { + ps.kill('SIGTERM', { + forceKillAfterTimeout: + platform() === 'win32' && satisfies(process.version, '>=21') ? false : SERVER_KILL_TIMEOUT, + }) + } catch { + // no-op + } }) } diff --git a/packages/functions-utils/CHANGELOG.md b/packages/functions-utils/CHANGELOG.md index 0201f9f226..36edc74196 100644 --- a/packages/functions-utils/CHANGELOG.md +++ b/packages/functions-utils/CHANGELOG.md @@ -1,5 +1,95 @@ # Changelog +## [5.2.93](https://github.com/netlify/build/compare/functions-utils-v5.2.92...functions-utils-v5.2.93) (2024-10-25) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.41.0 to 9.41.1 + +## [5.2.92](https://github.com/netlify/build/compare/functions-utils-v5.2.91...functions-utils-v5.2.92) (2024-10-21) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.40.2 to 9.41.0 + +## [5.2.91](https://github.com/netlify/build/compare/functions-utils-v5.2.90...functions-utils-v5.2.91) (2024-10-11) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.40.1 to 9.40.2 + +## [5.2.90](https://github.com/netlify/build/compare/functions-utils-v5.2.89...functions-utils-v5.2.90) (2024-10-11) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.40.0 to 9.40.1 + +## [5.2.89](https://github.com/netlify/build/compare/functions-utils-v5.2.88...functions-utils-v5.2.89) (2024-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.39.7 to 9.40.0 + +## [5.2.88](https://github.com/netlify/build/compare/functions-utils-v5.2.87...functions-utils-v5.2.88) (2024-10-01) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.39.6 to 9.39.7 + +## [5.2.87](https://github.com/netlify/build/compare/functions-utils-v5.2.86...functions-utils-v5.2.87) (2024-10-01) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.39.5 to 9.39.6 + +## [5.2.86](https://github.com/netlify/build/compare/functions-utils-v5.2.85...functions-utils-v5.2.86) (2024-09-26) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.39.4 to 9.39.5 + +## [5.2.85](https://github.com/netlify/build/compare/functions-utils-v5.2.84...functions-utils-v5.2.85) (2024-09-24) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.39.3 to 9.39.4 + +## [5.2.84](https://github.com/netlify/build/compare/functions-utils-v5.2.83...functions-utils-v5.2.84) (2024-09-23) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @netlify/zip-it-and-ship-it bumped from 9.39.2 to 9.39.3 + ## [5.2.83](https://github.com/netlify/build/compare/functions-utils-v5.2.82...functions-utils-v5.2.83) (2024-09-20) diff --git a/packages/functions-utils/package.json b/packages/functions-utils/package.json index 6b8c5a83c5..9f108be7d0 100644 --- a/packages/functions-utils/package.json +++ b/packages/functions-utils/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/functions-utils", - "version": "5.2.83", + "version": "5.2.93", "description": "Utility for adding Functions files in Netlify Build", "type": "module", "exports": "./lib/main.js", @@ -50,7 +50,7 @@ }, "license": "MIT", "dependencies": { - "@netlify/zip-it-and-ship-it": "9.39.2", + "@netlify/zip-it-and-ship-it": "9.41.1", "cpy": "^9.0.0", "path-exists": "^5.0.0" }, diff --git a/packages/js-client/package.json b/packages/js-client/package.json index 65c132544f..a2e876746f 100644 --- a/packages/js-client/package.json +++ b/packages/js-client/package.json @@ -41,7 +41,7 @@ "node client" ], "dependencies": { - "@netlify/open-api": "^2.34.0", + "@netlify/open-api": "^2.35.0", "lodash-es": "^4.17.21", "micro-api-client": "^3.3.0", "node-fetch": "^3.0.0", diff --git a/packages/testing/src/fs.ts b/packages/testing/src/fs.ts new file mode 100644 index 0000000000..93d06f1838 --- /dev/null +++ b/packages/testing/src/fs.ts @@ -0,0 +1,14 @@ +import { mkdir } from 'fs/promises' +import { platform } from 'process' + +import { execa } from 'execa' + +export const unzipFile = async function (path: string, dest: string): Promise { + await mkdir(dest, { recursive: true }) + + if (platform === 'win32') { + await execa('tar', ['-xf', path, '-C', dest]) + } else { + await execa('unzip', ['-o', path, '-d', dest]) + } +} diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index ebb6511823..4b25185540 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -1,5 +1,6 @@ export * from './dir.js' export * from './fixture.js' +export * from './fs.js' export * from './normalize.js' export * from './server.js' export * from './tcp_server.js' diff --git a/packages/zip-it-and-ship-it/CHANGELOG.md b/packages/zip-it-and-ship-it/CHANGELOG.md index ed3a81abdd..a1074ce18a 100644 --- a/packages/zip-it-and-ship-it/CHANGELOG.md +++ b/packages/zip-it-and-ship-it/CHANGELOG.md @@ -7,6 +7,81 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +## [9.41.1](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.41.0...zip-it-and-ship-it-v9.41.1) (2024-10-25) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.31.0 ([#5898](https://github.com/netlify/build/issues/5898)) ([a849029](https://github.com/netlify/build/commit/a849029e10547c717c2291c5f6cd56f8ba72e5d3)) + +## [9.41.0](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.40.2...zip-it-and-ship-it-v9.41.0) (2024-10-21) + + +### Features + +* add `bootstrapVersion` field to manifest ([#5890](https://github.com/netlify/build/issues/5890)) ([46de664](https://github.com/netlify/build/commit/46de664d2815c430f7d1b45c8bd92d42a5cc4502)) + +## [9.40.2](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.40.1...zip-it-and-ship-it-v9.40.2) (2024-10-11) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.30.1 ([#5882](https://github.com/netlify/build/issues/5882)) ([bdb19d8](https://github.com/netlify/build/commit/bdb19d8dd26b8b8eeff37311680e3b329e177f14)) + +## [9.40.1](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.40.0...zip-it-and-ship-it-v9.40.1) (2024-10-11) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.29.1 ([#5879](https://github.com/netlify/build/issues/5879)) ([959d7a5](https://github.com/netlify/build/commit/959d7a55c7a395a739f92bee541d762d1629e322)) +* **deps:** update dependency @netlify/serverless-functions-api to ^1.30.0 ([#5881](https://github.com/netlify/build/issues/5881)) ([cc6443c](https://github.com/netlify/build/commit/cc6443c323e61b36dba35d2c983641276ecd28c1)) + +## [9.40.0](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.7...zip-it-and-ship-it-v9.40.0) (2024-10-10) + + +### Features + +* write metadata file ([#5875](https://github.com/netlify/build/issues/5875)) ([7dfac4c](https://github.com/netlify/build/commit/7dfac4cc38058e135c4952d53ef6b63c8034f962)) + +## [9.39.7](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.6...zip-it-and-ship-it-v9.39.7) (2024-10-01) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.28.1 ([#5867](https://github.com/netlify/build/issues/5867)) ([3a78085](https://github.com/netlify/build/commit/3a780856002ac59df7f6da50b3d521ca14c09f7c)) +* **deps:** update dependency @netlify/serverless-functions-api to ^1.29.0 ([#5869](https://github.com/netlify/build/issues/5869)) ([d4966fb](https://github.com/netlify/build/commit/d4966fbd18ba01d2721334ddcecc0f188bb3bb3c)) + +## [9.39.6](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.5...zip-it-and-ship-it-v9.39.6) (2024-10-01) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.27.0 ([#5864](https://github.com/netlify/build/issues/5864)) ([dd86809](https://github.com/netlify/build/commit/dd8680932f0c02aec5dd6fba0ac43af3347eb213)) +* **deps:** update dependency @netlify/serverless-functions-api to ^1.28.0 ([#5865](https://github.com/netlify/build/issues/5865)) ([672281e](https://github.com/netlify/build/commit/672281ec6a943e911f86ec5f67db3b2a9c4c84a5)) + +## [9.39.5](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.4...zip-it-and-ship-it-v9.39.5) (2024-09-26) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.26.1 ([#5854](https://github.com/netlify/build/issues/5854)) ([c5e7f26](https://github.com/netlify/build/commit/c5e7f26141b9237e75a52b0ed9ecc7ccb5e504a4)) + +## [9.39.4](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.3...zip-it-and-ship-it-v9.39.4) (2024-09-24) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.24.1 ([#5847](https://github.com/netlify/build/issues/5847)) ([501b555](https://github.com/netlify/build/commit/501b55544e2c9a01f0e5b7474217d445bda7754d)) +* **deps:** update dependency @netlify/serverless-functions-api to ^1.25.0 ([#5849](https://github.com/netlify/build/issues/5849)) ([3998337](https://github.com/netlify/build/commit/3998337b86c7f1538d112c6dbb8b861531070862)) +* **deps:** update dependency @netlify/serverless-functions-api to ^1.26.0 ([#5850](https://github.com/netlify/build/issues/5850)) ([efd600c](https://github.com/netlify/build/commit/efd600c8411cab0a9a9a8ccbbc50942724bd1cd5)) + +## [9.39.3](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.2...zip-it-and-ship-it-v9.39.3) (2024-09-23) + + +### Bug Fixes + +* **deps:** update dependency @netlify/serverless-functions-api to ^1.24.0 ([#5843](https://github.com/netlify/build/issues/5843)) ([7c88c52](https://github.com/netlify/build/commit/7c88c52732cf2916247ba5bbe8875eabcb709bfd)) + ## [9.39.2](https://github.com/netlify/build/compare/zip-it-and-ship-it-v9.39.1...zip-it-and-ship-it-v9.39.2) (2024-09-20) diff --git a/packages/zip-it-and-ship-it/package.json b/packages/zip-it-and-ship-it/package.json index 135737e74b..d7240c135e 100644 --- a/packages/zip-it-and-ship-it/package.json +++ b/packages/zip-it-and-ship-it/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/zip-it-and-ship-it", - "version": "9.39.2", + "version": "9.41.1", "description": "Zip it and ship it", "main": "./dist/main.js", "type": "module", @@ -42,9 +42,9 @@ }, "dependencies": { "@babel/parser": "^7.22.5", - "@babel/types": "7.25.6", + "@babel/types": "7.26.3", "@netlify/binary-info": "^1.0.0", - "@netlify/serverless-functions-api": "^1.24.0", + "@netlify/serverless-functions-api": "^1.31.1", "@vercel/nft": "^0.27.1", "archiver": "^7.0.0", "common-path-prefix": "^3.0.0", diff --git a/packages/zip-it-and-ship-it/src/feature_flags.ts b/packages/zip-it-and-ship-it/src/feature_flags.ts index c8198d07e1..9f1a39ab25 100644 --- a/packages/zip-it-and-ship-it/src/feature_flags.ts +++ b/packages/zip-it-and-ship-it/src/feature_flags.ts @@ -33,8 +33,8 @@ export const defaultFlags = { // Adds the `___netlify-telemetry.mjs` file to the function bundle. zisi_add_instrumentation_loader: true, - // Adds a `___netlify-bootstrap-version` file to the function bundle. - zisi_add_version_file: false, + // Adds a `___netlify-metadata.json` file to the function bundle. + zisi_add_metadata_file: false, } as const export type FeatureFlags = Partial> diff --git a/packages/zip-it-and-ship-it/src/manifest.ts b/packages/zip-it-and-ship-it/src/manifest.ts index e6d54ce851..41a4481235 100644 --- a/packages/zip-it-and-ship-it/src/manifest.ts +++ b/packages/zip-it-and-ship-it/src/manifest.ts @@ -9,20 +9,20 @@ import type { ExtendedRoute, Route } from './utils/routes.js' interface ManifestFunction { buildData?: Record + bundler?: string + displayName?: string + excludedRoutes?: Route[] + generator?: string invocationMode?: InvocationMode mainFile: string name: string path: string + priority?: number routes?: ExtendedRoute[] - excludedRoutes?: Route[] runtime: string runtimeVersion?: string schedule?: string - displayName?: string - bundler?: string - generator?: string timeout?: number - priority?: number trafficRules?: TrafficRules } @@ -51,6 +51,7 @@ export const createManifest = async ({ functions, path }: { functions: FunctionR } const formatFunctionForManifest = ({ + bootstrapVersion, bundler, displayName, excludedRoutes, @@ -74,7 +75,7 @@ const formatFunctionForManifest = ({ generator, timeout, invocationMode, - buildData: { runtimeAPIVersion }, + buildData: { bootstrapVersion, runtimeAPIVersion }, mainFile, name, priority, diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/index.ts b/packages/zip-it-and-ship-it/src/runtimes/node/index.ts index 3ed6e10eb8..f488d55d55 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/index.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/index.ts @@ -37,6 +37,7 @@ const getSrcFilesWithBundler: GetSrcFilesFunction = async (parameters) => { const zipFunction: ZipFunction = async function ({ archiveFormat, basePath, + branch, cache, config = {}, destFolder, @@ -109,10 +110,11 @@ const zipFunction: ZipFunction = async function ({ createPluginsModulesPathAliases(srcFiles, pluginsModulesPath, aliases, finalBasePath) const generator = mergedConfig?.generator || getInternalValue(isInternal) - const zipPath = await zipNodeJs({ + const zipResult = await zipNodeJs({ aliases, archiveFormat, basePath: finalBasePath, + branch, cache, destFolder, extension, @@ -150,11 +152,12 @@ const zipFunction: ZipFunction = async function ({ const trafficRules = mergedConfig?.rateLimit ? getTrafficRulesConfig(mergedConfig.rateLimit) : undefined return { + bootstrapVersion: zipResult.bootstrapVersion, bundler: bundlerName, bundlerWarnings, config: mergedConfig, displayName: mergedConfig?.name, - entryFilename: zipPath.entryFilename, + entryFilename: zipResult.entryFilename, generator, timeout: mergedConfig?.timeout, inputs, @@ -163,7 +166,7 @@ const zipFunction: ZipFunction = async function ({ invocationMode, outputModuleFormat, nativeNodeModules, - path: zipPath.path, + path: zipResult.path, priority, trafficRules, runtimeVersion: diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts index 5e1cf7ab17..837cbe8def 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts @@ -18,6 +18,7 @@ import { normalizeFilePath } from './normalize_path.js' export const ENTRY_FILE_NAME = '___netlify-entry-point' export const BOOTSTRAP_FILE_NAME = '___netlify-bootstrap.mjs' export const BOOTSTRAP_VERSION_FILE_NAME = '___netlify-bootstrap-version' +export const METADATA_FILE_NAME = '___netlify-metadata.json' export const TELEMETRY_FILE_NAME = '___netlify-telemetry.mjs' const require = createRequire(import.meta.url) diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/metadata_file.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/metadata_file.ts new file mode 100644 index 0000000000..ad4edf327c --- /dev/null +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/metadata_file.ts @@ -0,0 +1,11 @@ +export interface MetadataFile { + bootstrap_version?: string + branch?: string + version: number +} + +export const getMetadataFile = (bootstrapVersion?: string, branch?: string): MetadataFile => ({ + bootstrap_version: bootstrapVersion, + branch, + version: 1, +}) diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/node_runtime.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/node_runtime.ts index 8b399ace61..194320bac4 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/node_runtime.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/node_runtime.ts @@ -5,6 +5,7 @@ const validRuntimeMap = { 16: 'nodejs16.x', 18: 'nodejs18.x', 20: 'nodejs20.x', + 22: 'nodejs22.x', } as const const minimumV2Version = 18 diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts index 657e0583e5..b079430083 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts @@ -23,13 +23,14 @@ import { cachedLstat, mkdirAndWriteFile } from '../../../utils/fs.js' import { BOOTSTRAP_FILE_NAME, - BOOTSTRAP_VERSION_FILE_NAME, + METADATA_FILE_NAME, conflictsWithEntryFile, EntryFile, getEntryFile, getTelemetryFile, isNamedLikeEntryFile, } from './entry_file.js' +import { getMetadataFile } from './metadata_file.js' import { ModuleFormat } from './module_format.js' import { normalizeFilePath } from './normalize_path.js' import { getPackageJsonIfAvailable } from './package_json.js' @@ -44,6 +45,7 @@ const DEFAULT_USER_SUBDIRECTORY = 'src' interface ZipNodeParameters { aliases?: Map basePath: string + branch?: string cache: RuntimeCache destFolder: string extension: string @@ -186,6 +188,7 @@ const createDirectory = async function ({ const createZipArchive = async function ({ aliases = new Map(), basePath, + branch, cache, destFolder, extension, @@ -226,6 +229,7 @@ const createZipArchive = async function ({ const userNamespace = hasEntryFileConflict ? DEFAULT_USER_SUBDIRECTORY : '' let entryFilename = `${basename(filename, extname(filename))}.js` + let bootstrapVersion: string | undefined if (needsEntryFile) { const entryFile = getEntryFile({ @@ -251,12 +255,13 @@ const createZipArchive = async function ({ if (runtimeAPIVersion === 2) { const bootstrapPath = addBootstrapFile(srcFiles, aliases) - if (featureFlags.zisi_add_version_file === true) { - const { version: bootstrapVersion } = await getPackageJsonIfAvailable(bootstrapPath) + if (featureFlags.zisi_add_metadata_file === true) { + const { version } = await getPackageJsonIfAvailable(bootstrapPath) + const payload = JSON.stringify(getMetadataFile(version, branch)) - if (bootstrapVersion) { - addZipContent(archive, bootstrapVersion, BOOTSTRAP_VERSION_FILE_NAME) - } + bootstrapVersion = version + + addZipContent(archive, payload, METADATA_FILE_NAME) } } @@ -279,16 +284,19 @@ const createZipArchive = async function ({ await endZip(archive, output) - return { path: destPath, entryFilename } + return { path: destPath, entryFilename, bootstrapVersion } +} + +interface ZipNodeJsResult { + bootstrapVersion?: string + entryFilename: string + path: string } export const zipNodeJs = function ({ archiveFormat, ...options -}: ZipNodeParameters & { archiveFormat: ArchiveFormat }): Promise<{ - path: string - entryFilename: string -}> { +}: ZipNodeParameters & { archiveFormat: ArchiveFormat }): Promise { if (archiveFormat === ARCHIVE_FORMAT.ZIP) { return createZipArchive(options) } diff --git a/packages/zip-it-and-ship-it/src/runtimes/runtime.ts b/packages/zip-it-and-ship-it/src/runtimes/runtime.ts index e527c97adc..2361eb5b55 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/runtime.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/runtime.ts @@ -66,6 +66,7 @@ export type ZipFunction = ( args: { archiveFormat: ArchiveFormat basePath?: string + branch?: string cache: RuntimeCache config: FunctionConfig destFolder: string diff --git a/packages/zip-it-and-ship-it/src/utils/format_result.ts b/packages/zip-it-and-ship-it/src/utils/format_result.ts index e5e1d30417..3e2cbb3564 100644 --- a/packages/zip-it-and-ship-it/src/utils/format_result.ts +++ b/packages/zip-it-and-ship-it/src/utils/format_result.ts @@ -5,6 +5,7 @@ import { removeUndefined } from './remove_undefined.js' import type { ExtendedRoute, Route } from './routes.js' export type FunctionResult = Omit & { + bootstrapVersion?: string routes?: ExtendedRoute[] excludedRoutes?: Route[] runtime: RuntimeName diff --git a/packages/zip-it-and-ship-it/src/zip.ts b/packages/zip-it-and-ship-it/src/zip.ts index c805e2a934..995ccaa2e0 100644 --- a/packages/zip-it-and-ship-it/src/zip.ts +++ b/packages/zip-it-and-ship-it/src/zip.ts @@ -13,7 +13,7 @@ import { getFunctionsFromPaths } from './runtimes/index.js' import { MODULE_FORMAT } from './runtimes/node/utils/module_format.js' import { addArchiveSize } from './utils/archive_size.js' import { RuntimeCache } from './utils/cache.js' -import { formatZipResult } from './utils/format_result.js' +import { formatZipResult, FunctionResult } from './utils/format_result.js' import { listFunctionsDirectories, resolveFunctionsDirectories } from './utils/fs.js' import { getLogger, LogFunction } from './utils/logger.js' import { nonNullable } from './utils/non_nullable.js' @@ -21,6 +21,7 @@ import { nonNullable } from './utils/non_nullable.js' export interface ZipFunctionOptions { archiveFormat?: ArchiveFormat basePath?: string + branch?: string config?: Config featureFlags?: FeatureFlags repositoryRoot?: string @@ -53,6 +54,7 @@ export const zipFunctions = async function ( { archiveFormat = ARCHIVE_FORMAT.ZIP, basePath, + branch, config = {}, configFileDirectories, featureFlags: inputFeatureFlags, @@ -63,7 +65,7 @@ export const zipFunctions = async function ( debug, internalSrcFolder, }: ZipFunctionsOptions = {}, -) { +): Promise { validateArchiveFormat(archiveFormat) const logger = getLogger(systemLog, debug) @@ -94,6 +96,7 @@ export const zipFunctions = async function ( const zipResult = await func.runtime.zipFunction({ archiveFormat, basePath, + branch, cache, config: func.config, destFolder, @@ -145,7 +148,7 @@ export const zipFunction = async function ( debug, internalSrcFolder, }: ZipFunctionOptions = {}, -) { +): Promise { validateArchiveFormat(archiveFormat) const logger = getLogger(systemLog, debug) diff --git a/packages/zip-it-and-ship-it/tests/v2api.test.ts b/packages/zip-it-and-ship-it/tests/v2api.test.ts index 1cddf65d41..a79c6348ac 100644 --- a/packages/zip-it-and-ship-it/tests/v2api.test.ts +++ b/packages/zip-it-and-ship-it/tests/v2api.test.ts @@ -709,22 +709,79 @@ describe.runIf(semver.gte(nodeVersion, '18.13.0'))('V2 functions API', () => { expect(files[0].runtimeVersion).toBe('nodejs20.x') }) - test('Adds a file with the bootstrap version to the ZIP archive', async () => { - const fixtureName = 'v2-api' - const { files } = await zipFixture(fixtureName, { + describe('Adds a file with metadata', () => { + test('Without a branch', async () => { + const fixtureName = 'v2-api' + const { files } = await zipFixture(fixtureName, { + fixtureDir: FIXTURES_ESM_DIR, + opts: { + featureFlags: { + zisi_add_metadata_file: true, + }, + }, + }) + const [unzippedFunction] = await unzipFiles(files) + const bootstrapPath = getBootstrapPath() + const bootstrapPackageJson = await readFile(resolve(bootstrapPath, '..', '..', 'package.json'), 'utf8') + const { version: bootstrapVersion } = JSON.parse(bootstrapPackageJson) + const versionFileContents = await readFile(join(unzippedFunction.unzipPath, '___netlify-metadata.json'), 'utf8') + + expect(JSON.parse(versionFileContents)).toEqual({ bootstrap_version: bootstrapVersion, version: 1 }) + }) + + test('With a branch', async () => { + const fixtureName = 'v2-api' + const { files } = await zipFixture(fixtureName, { + fixtureDir: FIXTURES_ESM_DIR, + opts: { + branch: 'main', + featureFlags: { + zisi_add_metadata_file: true, + }, + }, + }) + const [unzippedFunction] = await unzipFiles(files) + const bootstrapPath = getBootstrapPath() + const bootstrapPackageJson = await readFile(resolve(bootstrapPath, '..', '..', 'package.json'), 'utf8') + const { version: bootstrapVersion } = JSON.parse(bootstrapPackageJson) + const versionFileContents = await readFile(join(unzippedFunction.unzipPath, '___netlify-metadata.json'), 'utf8') + + expect(JSON.parse(versionFileContents)).toEqual({ + bootstrap_version: bootstrapVersion, + branch: 'main', + version: 1, + }) + }) + }) + + test('Adds a `buildData` object to each function entry in the manifest file', async () => { + const bootstrapPath = getBootstrapPath() + const bootstrapPackageJson = await readFile(resolve(bootstrapPath, '..', '..', 'package.json'), 'utf8') + const { version: bootstrapVersion } = JSON.parse(bootstrapPackageJson) + + const { path: tmpDir } = await getTmpDir({ prefix: 'zip-it-test' }) + const manifestPath = join(tmpDir, 'manifest.json') + + const { files } = await zipFixture('v2-api', { fixtureDir: FIXTURES_ESM_DIR, opts: { featureFlags: { - zisi_add_version_file: true, + zisi_add_metadata_file: true, }, + manifest: manifestPath, }, }) - const [unzippedFunction] = await unzipFiles(files) - const bootstrapPath = getBootstrapPath() - const bootstrapPackageJson = await readFile(resolve(bootstrapPath, '..', '..', 'package.json'), 'utf8') - const { version: bootstrapVersion } = JSON.parse(bootstrapPackageJson) - const versionFileContents = await readFile(join(unzippedFunction.unzipPath, '___netlify-bootstrap-version'), 'utf8') - expect(versionFileContents).toBe(bootstrapVersion) + expect(files.length).toBe(1) + expect(files[0].name).toBe('function') + expect(files[0].bootstrapVersion).toBe(bootstrapVersion) + expect(files[0].runtimeAPIVersion).toBe(2) + + const manifestString = await readFile(manifestPath, { encoding: 'utf8' }) + const manifest = JSON.parse(manifestString) + + expect(manifest.functions.length).toBe(1) + expect(manifest.functions[0].name).toBe('function') + expect(manifest.functions[0].buildData).toEqual({ bootstrapVersion, runtimeAPIVersion: 2 }) }) })