From a174b0e12c47613e4908c451f5ab35f0f24cf469 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:39:46 +0000 Subject: [PATCH 01/19] fix(deps): update font awesome to v6.5.2 (#1039) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 66 +++++++++++++++++++++++------------------------ package.json | 6 ++--- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8cd47fe572..a4b2928a71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,9 @@ "@emotion/styled": "11.11.5", "@fontsource/barlow": "4.5.9", "@fontsource/material-icons": "4.5.4", - "@fortawesome/fontawesome-svg-core": "6.5.1", - "@fortawesome/free-brands-svg-icons": "6.5.1", - "@fortawesome/free-solid-svg-icons": "6.5.1", + "@fortawesome/fontawesome-svg-core": "6.5.2", + "@fortawesome/free-brands-svg-icons": "6.5.2", + "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", "@mui/icons-material": "5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -2102,45 +2102,45 @@ "integrity": "sha512-YGmXkkEdu6EIgpFKNmB/nIXzZocwSmbI01Ninpmml8x8BT0M6RR++V1KqOfpzZ6Cw/FQ2/KYonQ3x4IY/4VRRA==" }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", "hasInstallScript": true, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz", - "integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" @@ -10117,32 +10117,32 @@ "integrity": "sha512-YGmXkkEdu6EIgpFKNmB/nIXzZocwSmbI01Ninpmml8x8BT0M6RR++V1KqOfpzZ6Cw/FQ2/KYonQ3x4IY/4VRRA==" }, "@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==" }, "@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" } }, "@fortawesome/free-brands-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz", - "integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" } }, "@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" } }, "@fortawesome/react-fontawesome": { diff --git a/package.json b/package.json index f352a2e312..be52051750 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,9 @@ "@emotion/styled": "11.11.5", "@fontsource/barlow": "4.5.9", "@fontsource/material-icons": "4.5.4", - "@fortawesome/fontawesome-svg-core": "6.5.1", - "@fortawesome/free-brands-svg-icons": "6.5.1", - "@fortawesome/free-solid-svg-icons": "6.5.1", + "@fortawesome/fontawesome-svg-core": "6.5.2", + "@fortawesome/free-brands-svg-icons": "6.5.2", + "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", "@mui/icons-material": "5.15.14", "@mui/lab": "5.0.0-alpha.169", From 7865dd96cfb8f939974fe864fa11c70bfc940ff1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 01:22:04 +0000 Subject: [PATCH 02/19] fix(deps): update material-ui monorepo (#1040) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 78 +++++++++++++++++++++++------------------------ package.json | 6 ++-- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4b2928a71..b24947fca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,9 +17,9 @@ "@fortawesome/free-brands-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.15.14", - "@mui/lab": "5.0.0-alpha.169", - "@mui/material": "5.15.14", + "@mui/icons-material": "5.15.15", + "@mui/lab": "5.0.0-alpha.170", + "@mui/material": "5.15.15", "@mui/x-date-pickers": "5.0.20", "@reduxjs/toolkit": "1.9.7", "axios": "1.6.8", @@ -2415,18 +2415,18 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", - "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz", - "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -2449,13 +2449,13 @@ } }, "node_modules/@mui/lab": { - "version": "5.0.0-alpha.169", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.169.tgz", - "integrity": "sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA==", + "version": "5.0.0-alpha.170", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.170.tgz", + "integrity": "sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/system": "^5.15.14", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "clsx": "^2.1.0", @@ -2497,14 +2497,14 @@ } }, "node_modules/@mui/material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", - "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", + "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.14", - "@mui/system": "^5.15.14", + "@mui/core-downloads-tracker": "^5.15.15", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", @@ -2611,9 +2611,9 @@ } }, "node_modules/@mui/system": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", - "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.15.14", @@ -10320,26 +10320,26 @@ } }, "@mui/core-downloads-tracker": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", - "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==" + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==" }, "@mui/icons-material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz", - "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", "requires": { "@babel/runtime": "^7.23.9" } }, "@mui/lab": { - "version": "5.0.0-alpha.169", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.169.tgz", - "integrity": "sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA==", + "version": "5.0.0-alpha.170", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.170.tgz", + "integrity": "sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==", "requires": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/system": "^5.15.14", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "clsx": "^2.1.0", @@ -10354,14 +10354,14 @@ } }, "@mui/material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", - "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", + "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", "requires": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.14", - "@mui/system": "^5.15.14", + "@mui/core-downloads-tracker": "^5.15.15", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", @@ -10406,9 +10406,9 @@ } }, "@mui/system": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", - "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", "requires": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.15.14", diff --git a/package.json b/package.json index be52051750..5d18f2aa0b 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,9 @@ "@fortawesome/free-brands-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.15.14", - "@mui/lab": "5.0.0-alpha.169", - "@mui/material": "5.15.14", + "@mui/icons-material": "5.15.15", + "@mui/lab": "5.0.0-alpha.170", + "@mui/material": "5.15.15", "@mui/x-date-pickers": "5.0.20", "@reduxjs/toolkit": "1.9.7", "axios": "1.6.8", From ed337eca62dfac48c827c324ddaeb96ea8cf937f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:30:43 +0000 Subject: [PATCH 03/19] chore(deps): update dependency css-loader to v6.11.0 (#1042) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 62 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index b24947fca6..9772b8ebac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "@svgr/webpack": "6.5.1", "babel-loader": "9.1.3", "babel-preset-react": "6.24.1", - "css-loader": "6.10.0", + "css-loader": "6.11.0", "file-loader": "6.2.0", "html-webpack-plugin": "5.6.0", "style-loader": "3.3.4", @@ -4475,16 +4475,16 @@ } }, "node_modules/css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -6945,9 +6945,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -6957,9 +6957,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", - "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -6974,9 +6974,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", - "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -11794,16 +11794,16 @@ } }, "css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "requires": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -13590,16 +13590,16 @@ } }, "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "requires": {} }, "postcss-modules-local-by-default": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", - "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "requires": { "icss-utils": "^5.0.0", @@ -13608,9 +13608,9 @@ } }, "postcss-modules-scope": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", - "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.4" diff --git a/package.json b/package.json index 5d18f2aa0b..03a34beb12 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@svgr/webpack": "6.5.1", "babel-loader": "9.1.3", "babel-preset-react": "6.24.1", - "css-loader": "6.10.0", + "css-loader": "6.11.0", "file-loader": "6.2.0", "html-webpack-plugin": "5.6.0", "style-loader": "3.3.4", From 292ffec5ae06637cb11b1c73b2bc86da33da48c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 08:23:07 +0000 Subject: [PATCH 04/19] fix(deps): update module github.com/antchfx/xmlquery to v1.4.0 (#1043) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ef7399c541..406dba8807 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.2 require ( github.com/Code-Hex/Neo-cowsay/v2 v2.0.4 - github.com/antchfx/xmlquery v1.3.18 + github.com/antchfx/xmlquery v1.4.0 github.com/bazelbuild/buildtools v0.0.0-20230425225026-3dcc8d67e8ea github.com/client9/misspell v0.3.4 github.com/dustin/go-humanize v1.0.1 @@ -54,7 +54,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/PuerkitoBio/goquery v1.5.1 // indirect github.com/andybalholm/cascadia v1.1.0 // indirect - github.com/antchfx/xpath v1.2.4 // indirect + github.com/antchfx/xpath v1.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect diff --git a/go.sum b/go.sum index 195d01f9c5..5f2b9ac701 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,12 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0= github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= +github.com/antchfx/xmlquery v1.4.0 h1:xg2HkfcRK2TeTbdb0m1jxCYnvsPaGY/oeZWTGqX/0hA= +github.com/antchfx/xmlquery v1.4.0/go.mod h1:Ax2aeaeDjfIw3CwXKDQ0GkwZ6QlxoChlIBP+mGnDFjI= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc= +github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= From ce0d2740f93e77d7312735dec89fd61b28dad279 Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Thu, 18 Apr 2024 12:26:39 -0400 Subject: [PATCH 05/19] runtime: Support applets with multiple Starlark files (#1041) With `NewAppletFromFS`, process all the Starlark files in the root directory to look for `main()` and `schema()` functions. Also allow files to load other Starlark files. --- runtime/applet.go | 304 +++++++++++++++++++++++++++-------------- runtime/applet_test.go | 124 ++++++++++++++--- runtime/render_test.go | 6 +- 3 files changed, 310 insertions(+), 124 deletions(-) diff --git a/runtime/applet.go b/runtime/applet.go index fc8d731cea..c515515f69 100644 --- a/runtime/applet.go +++ b/runtime/applet.go @@ -2,11 +2,14 @@ package runtime import ( "context" - "crypto/md5" "encoding/json" "fmt" + "io/fs" + "path/filepath" + "slices" "strings" "testing" + "testing/fstest" starlibgzip "github.com/qri-io/starlib/compress/gzip" starlibbase64 "github.com/qri-io/starlib/encoding/base64" @@ -51,13 +54,15 @@ type ThreadInitializer func(thread *starlark.Thread) *starlark.Thread type Applet struct { ID string - src []byte loader ModuleLoader initializers []ThreadInitializer - globals starlark.StringDict - main *starlark.Function + globals map[string]starlark.StringDict + mainFile string + mainFun *starlark.Function + + schemaFile string schema *schema.Schema schemaJSON []byte } @@ -69,13 +74,6 @@ func WithModuleLoader(loader ModuleLoader) AppletOption { } } -func WithThreadInitializer(initializer ThreadInitializer) AppletOption { - return func(a *Applet) error { - a.initializers = append(a.initializers, initializer) - return nil - } -} - func WithSecretDecryptionKey(key *SecretDecryptionKey) AppletOption { return func(a *Applet) error { if decrypter, err := key.decrypterForApp(a); err != nil { @@ -105,9 +103,24 @@ func WithPrintDisabled() AppletOption { } func NewApplet(id string, src []byte, opts ...AppletOption) (*Applet, error) { + fn := id + if !strings.HasSuffix(fn, ".star") { + fn += ".star" + } + + vfs := fstest.MapFS{ + fn: &fstest.MapFile{ + Data: src, + }, + } + + return NewAppletFromFS(id, vfs, opts...) +} + +func NewAppletFromFS(id string, fsys fs.FS, opts ...AppletOption) (*Applet, error) { a := &Applet{ - ID: id, - src: src, + ID: id, + globals: make(map[string]starlark.StringDict), } for _, opt := range opts { @@ -116,70 +129,13 @@ func NewApplet(id string, src []byte, opts ...AppletOption) (*Applet, error) { } } - if err := a.load(); err != nil { + if err := a.load(fsys); err != nil { return nil, err } return a, nil } -func (a *Applet) load() (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic while executing %s: %v", a.ID, r) - } - }() - - predeclared := starlark.StringDict{ - "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), - } - - globals, err := starlark.ExecFileOptions( - &syntax.FileOptions{ - Set: true, - Recursion: true, - }, - a.newThread(context.Background()), - a.ID, - a.src, - predeclared, - ) - if err != nil { - return fmt.Errorf("starlark.ExecFile: %v", err) - } - a.globals = globals - - mainFun, found := globals["main"] - if !found { - return fmt.Errorf("%s didn't export a main() function", a.ID) - } - main, ok := mainFun.(*starlark.Function) - if !ok { - return fmt.Errorf("%s exported a main() that is not function", a.ID) - } - a.main = main - - schemaFun, _ := a.globals[schema.SchemaFunctionName].(*starlark.Function) - if schemaFun != nil { - schemaVal, err := a.Call(context.Background(), schemaFun) - if err != nil { - return fmt.Errorf("calling schema function for %s: %w", a.ID, err) - } - - a.schema, err = schema.FromStarlark(schemaVal, a.globals) - if err != nil { - return fmt.Errorf("parsing schema for %s: %w", a.ID, err) - } - - a.schemaJSON, err = json.Marshal(a.schema) - if err != nil { - return fmt.Errorf("serializing schema to JSON for %s: %w", a.ID, err) - } - } - - return nil -} - // Run executes the applet's main function. It returns the render roots that are // returned by the applet. func (a *Applet) Run(ctx context.Context) (roots []render.Root, err error) { @@ -190,12 +146,12 @@ func (a *Applet) Run(ctx context.Context) (roots []render.Root, err error) { // starlark dict. It returns the render roots that are returned by the applet. func (a *Applet) RunWithConfig(ctx context.Context, config map[string]string) (roots []render.Root, err error) { var args starlark.Tuple - if a.main.NumParams() > 0 { + if a.mainFun.NumParams() > 0 { starlarkConfig := AppletConfig(config) args = starlark.Tuple{starlarkConfig} } - returnValue, err := a.Call(ctx, a.main, args...) + returnValue, err := a.Call(ctx, a.mainFun, args...) if err != nil { return nil, err } @@ -253,7 +209,7 @@ func (app *Applet) CallSchemaHandler(ctx context.Context, handlerName, parameter return options, nil case schema.ReturnSchema: - sch, err := schema.FromStarlark(resultVal, app.globals) + sch, err := schema.FromStarlark(resultVal, app.globals[app.schemaFile]) if err != nil { return "", err } @@ -291,18 +247,19 @@ func (app *Applet) RunTests(t *testing.T) { return thread }) - for name, global := range app.globals { - if !strings.HasPrefix(name, "test_") { - continue - } + for file, globals := range app.globals { + for name, global := range globals { + if !strings.HasPrefix(name, "test_") { + continue + } - fun, ok := global.(*starlark.Function) - if ok && fun != app.main { - t.Run(name, func(t *testing.T) { - if _, err := app.Call(context.Background(), fun); err != nil { - t.Error(err) - } - }) + if fun, ok := global.(*starlark.Function); ok { + t.Run(fmt.Sprintf("%s/%s", file, name), func(t *testing.T) { + if _, err := app.Call(context.Background(), fun); err != nil { + t.Error(err) + } + }) + } } } } @@ -339,6 +296,164 @@ func (a *Applet) Call(ctx context.Context, callable *starlark.Function, args ... return resultVal, nil } +func (a *Applet) load(fsys fs.FS) (err error) { + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, walkDirErr error) error { + if walkDirErr != nil { + return walkDirErr + } + + if d.IsDir() || filepath.Dir(path) != "." { + // only process files in the root directory + return nil + } + + if !strings.HasSuffix(path, ".star") { + // not a starlark file + return nil + } + + return a.ensureLoaded(fsys, path) + }); err != nil { + return err + } + + if a.mainFun == nil { + return fmt.Errorf("no main() function found in %s", a.ID) + } + + return nil +} + +func (a *Applet) ensureLoaded(fsys fs.FS, path string, currentlyLoading ...string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic while executing %s: %v", a.ID, r) + } + }() + + // normalize path so that it can be used as a key + path = filepath.Clean(path) + if _, ok := a.globals[path]; ok { + // already loaded, good to go + return nil + } + + // use the currentlyLoading slice to detect circular dependencies + if slices.Contains(currentlyLoading, path) { + return fmt.Errorf("circular dependency detected: %s -> %s", strings.Join(currentlyLoading, " -> "), path) + } else { + // mark this file as currently loading. if we encounter it again, + // we have a circular dependency. + currentlyLoading = append(currentlyLoading, path) + } + + src, err := fs.ReadFile(fsys, path) + if err != nil { + return fmt.Errorf("reading %s: %v", path, err) + } + + predeclared := starlark.StringDict{ + "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), + } + + thread := a.newThread(context.Background()) + + // override loader to allow loading starlark files + thread.Load = func(thread *starlark.Thread, module string) (starlark.StringDict, error) { + // normalize module path + modulePath := filepath.Clean(module) + + // if the module exists on the filesystem, load it + if _, err := fs.Stat(fsys, modulePath); err == nil { + // ensure the module is loaded, and pass the currentlyLoading slice + // to detect circular dependencies + if err := a.ensureLoaded(fsys, modulePath, currentlyLoading...); err != nil { + return nil, err + } + + if g, ok := a.globals[modulePath]; !ok { + return nil, fmt.Errorf("module %s not loaded", modulePath) + } else { + return g, nil + } + } + + // fallback to default loader + return a.loadModule(thread, module) + } + + globals, err := starlark.ExecFileOptions( + &syntax.FileOptions{ + Set: true, + Recursion: true, + }, + thread, + a.ID, + src, + predeclared, + ) + if err != nil { + return fmt.Errorf("starlark.ExecFile: %v", err) + } + a.globals[path] = globals + + // if the file is in the root directory, check for the main function + // and schema function + mainFun, _ := globals["main"].(*starlark.Function) + if mainFun != nil { + if a.mainFile != "" { + return fmt.Errorf("multiple files with a main() function:\n- %s\n- %s", path, a.mainFile) + } + + a.mainFile = path + a.mainFun = mainFun + } + + schemaFun, _ := globals[schema.SchemaFunctionName].(*starlark.Function) + if schemaFun != nil { + if a.schemaFile != "" { + return fmt.Errorf("multiple files with a %s() function:\n- %s\n- %s", schema.SchemaFunctionName, path, a.schemaFile) + } + a.schemaFile = path + + schemaVal, err := a.Call(context.Background(), schemaFun) + if err != nil { + return fmt.Errorf("calling schema function for %s: %w", a.ID, err) + } + + a.schema, err = schema.FromStarlark(schemaVal, globals) + if err != nil { + return fmt.Errorf("parsing schema for %s: %w", a.ID, err) + } + + a.schemaJSON, err = json.Marshal(a.schema) + if err != nil { + return fmt.Errorf("serializing schema to JSON for %s: %w", a.ID, err) + } + } + + return nil +} + +func (a *Applet) newThread(ctx context.Context) *starlark.Thread { + t := &starlark.Thread{ + Name: a.ID, + Load: a.loadModule, + Print: func(thread *starlark.Thread, msg string) { + fmt.Printf("[%s] %s\n", a.ID, msg) + }, + } + + starlarkutil.AttachThreadContext(ctx, t) + random.AttachToThread(t) + + for _, init := range a.initializers { + t = init(t) + } + + return t +} + func (a *Applet) loadModule(thread *starlark.Thread, module string) (starlark.StringDict, error) { if a.loader != nil { mod, err := a.loader(thread, module) @@ -439,22 +554,3 @@ func (a *Applet) loadModule(thread *starlark.Thread, module string) (starlark.St return nil, fmt.Errorf("invalid module: %s", module) } } - -func (a *Applet) newThread(ctx context.Context) *starlark.Thread { - t := &starlark.Thread{ - Name: fmt.Sprintf("%s/%x", a.ID, md5.Sum(a.src)), - Load: a.loadModule, - Print: func(thread *starlark.Thread, msg string) { - fmt.Printf("[%s] %s\n", a.ID, msg) - }, - } - - starlarkutil.AttachThreadContext(ctx, t) - random.AttachToThread(t) - - for _, init := range a.initializers { - t = init(t) - } - - return t -} diff --git a/runtime/applet_test.go b/runtime/applet_test.go index 7997d03331..6ba07d8af6 100644 --- a/runtime/applet_test.go +++ b/runtime/applet_test.go @@ -4,13 +4,16 @@ import ( "archive/zip" "bytes" "context" + "encoding/json" "fmt" "testing" + "testing/fstest" starlibbase64 "github.com/qri-io/starlib/encoding/base64" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.starlark.net/starlark" + "tidbyt.dev/pixlet/schema" ) func TestLoadEmptySrc(t *testing.T) { @@ -173,6 +176,47 @@ def main(config): assert.Equal(t, 3, len(roots)) } +func TestLoadMultipleFiles(t *testing.T) { + mainSrc := ` +load("render.star", "render") +def main(): + return render.Root(child=render.Box()) +` + schemaDefSrc := ` +load("schema.star", "schema") +def get_schema(): + return schema.Schema( + version = "1", + fields = [], + ) +` + vfs := fstest.MapFS{ + "main.star": {Data: []byte(mainSrc)}, + "schema_def.star": {Data: []byte(schemaDefSrc)}, + } + + app, err := NewAppletFromFS("multiple_files", vfs) + require.NoError(t, err) + require.NotNil(t, app) + + jsonSchema := app.GetSchema() + var s schema.Schema + json.Unmarshal([]byte(jsonSchema), &s) + assert.Equal(t, "1", s.Version) + + roots, err := app.Run(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, roots) + assert.Equal(t, 1, len(roots)) + + // multiple main functions should fail + vfs["main2.star"] = &fstest.MapFile{ + Data: []byte(mainSrc), + } + _, err = NewAppletFromFS("multiple_files_multiple_mains", vfs) + assert.Error(t, err) +} + func TestModuleLoading(t *testing.T) { // Our basic set of modules can be imported src := ` @@ -230,33 +274,79 @@ def main(): roots, err = app.Run(context.Background()) assert.NoError(t, err) assert.Equal(t, 1, len(roots)) - } -func TestThreadInitializer(t *testing.T) { +func TestDependency(t *testing.T) { + // src.star depends on hello.star src := ` load("render.star", "render") +load("hello.star", "hello") + def main(): - print('foobar') - return render.Root(child=render.Box()) + if hello.world() != "hello world": + fail("something went wrong") + return render.Root(child=render.Box()) ` - // override the print function of the thread - var printedText string - initializer := func(thread *starlark.Thread) *starlark.Thread { - thread.Print = func(thread *starlark.Thread, msg string) { - printedText += msg - } - return thread + + helloSrc := ` +def _world(): + return "hello world" + +hello = struct( + world = _world, +) +` + + vfs := fstest.MapFS{ + "src.star": {Data: []byte(src)}, + "hello.star": {Data: []byte(helloSrc)}, } - app, err := NewApplet("test.star", []byte(src), WithThreadInitializer(initializer)) - assert.NoError(t, err) - assert.NotNil(t, app) - _, err = app.Run(context.Background()) + app, err := NewAppletFromFS("test", vfs) assert.NoError(t, err) + if assert.NotNil(t, app) { + roots, err := app.Run(context.Background()) + assert.NoError(t, err) + assert.Equal(t, 1, len(roots)) + } - // our print function should have been called - assert.Equal(t, "foobar", printedText) + // src2.star shouldn't be able to access private function from hello.star + src2 := ` +load("render.star", "render") +load("hello.star", "_world") + +def main(): + return render.Root(child=render.Box()) + ` + + vfs2 := fstest.MapFS{ + "src2.star": {Data: []byte(src2)}, + "hello.star": {Data: []byte(helloSrc)}, + } + + _, err = NewAppletFromFS("test", vfs2) + assert.ErrorContains(t, err, "not exported") +} + +func TestCircularDependency(t *testing.T) { + // Module A depends on module B + srcA := ` +load("b.star", "b") +def a(): + return b.b() +` + // Module B depends on module A + srcB := ` +load("a.star", "a") +def b(): + return a.a() +` + vfs := fstest.MapFS{ + "a.star": {Data: []byte(srcA)}, + "b.star": {Data: []byte(srcB)}, + } + _, err := NewAppletFromFS("circular_dependency", vfs) + assert.ErrorContains(t, err, "circular dependency") } func TestTimezoneDatabase(t *testing.T) { diff --git a/runtime/render_test.go b/runtime/render_test.go index 961c58b3dd..68117e8816 100644 --- a/runtime/render_test.go +++ b/runtime/render_test.go @@ -161,7 +161,7 @@ def main(): app, err := NewApplet(filename, []byte(src)) assert.NoError(t, err) - b := app.globals["b"] + b := app.globals["test_box.star"]["b"] assert.IsType(t, &render_runtime.Box{}, b) widget := b.(*render_runtime.Box).AsRenderWidget() @@ -196,7 +196,7 @@ def main(): app, err := NewApplet(filename, []byte(src)) assert.NoError(t, err) - txt := app.globals["t"] + txt := app.globals["test_text.star"]["t"] assert.IsType(t, &render_runtime.Text{}, txt) widget := txt.(*render_runtime.Text).AsRenderWidget() @@ -240,7 +240,7 @@ def main(): app, err := NewApplet(filename, []byte(src)) assert.NoError(t, err) - starlarkP := app.globals["img"] + starlarkP := app.globals["test_png.star"]["img"] require.IsType(t, &render_runtime.Image{}, starlarkP) actualIm := render.PaintWidget(starlarkP.(*render_runtime.Image).AsRenderWidget(), image.Rect(0, 0, 64, 32), 0) From 0baa5881410c61da0282eae29769abe244d12025 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:32:23 +0000 Subject: [PATCH 06/19] fix(deps): update module github.com/go-git/go-git/v5 to v5.12.0 (#1044) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 406dba8807..ad3de6e38e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/gitsight/go-vcsurl v1.0.1 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.12.0 github.com/go-playground/validator/v10 v10.15.1 github.com/google/pprof v0.0.0-20240416155748-26353dc0451f github.com/google/tink/go v1.7.0 @@ -51,12 +51,12 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/Code-Hex/go-wordwrap v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/PuerkitoBio/goquery v1.5.1 // indirect github.com/andybalholm/cascadia v1.1.0 // indirect github.com/antchfx/xpath v1.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emirpasic/gods v1.18.1 // indirect @@ -87,8 +87,8 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sergi/go-diff v1.1.0 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index 5f2b9ac701..4f67882d75 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -59,6 +61,8 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -107,6 +111,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -283,12 +289,16 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -518,6 +528,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 757a72afb62fd1d4763da3e1c1ef2701a648fe18 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:40:02 +0000 Subject: [PATCH 07/19] fix(deps): update module golang.org/x/oauth2 to v0.19.0 (#1046) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ad3de6e38e..7b9f152072 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/zachomedia/go-bdf v0.0.0-20220611021443-a3af701111be go.starlark.net v0.0.0-20240411212711-9b43f0afd521 golang.org/x/image v0.15.0 - golang.org/x/oauth2 v0.18.0 + golang.org/x/oauth2 v0.19.0 golang.org/x/sync v0.7.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 4f67882d75..cb7658d840 100644 --- a/go.sum +++ b/go.sum @@ -418,6 +418,8 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From fe59c716a9aa53f1683a2aed009a0101ffd13f62 Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Wed, 24 Apr 2024 06:22:59 -0400 Subject: [PATCH 08/19] Add the ability to read files from Starlark (#1045) * Add the ability to read files from Starlark Starlark scripts can now load arbitrary files from their bundle and read them using a Python-like `open()` and `read()` API. A `readall()` convenience function is also provided. The examples have been updated to use this functionality where relevant. For example: ``` load("icon.png", icon = "file") BTC_ICON = icon.readall() ``` The `pixlet render` command can now accept an entire directory containing multiple Starlark files and static resources. * Remove `file.open` and `read()` Remove the `open()` and `read()` functions, so the only way to read a file is `readall()`. If there is demand for the other methods, we can add them back later. * Add directory support to `pixlet serve` * Implement `pixlet bundle` for directories * Fix path problems on Windows `fs.FS` always uses slash-separated paths, even on Windows. So we should be manipulating those paths with `path`, not `filepath`. * Switch tarfs implementations for Windows support It appears that `nlepage/go-tarfs` correctly supports Windows by [using `path` instead of `filepath`][1]. [1]: https://github.com/nlepage/go-tarfs/pull/7 --- .gitignore | 4 +- README.md | 10 +- bundle/bundle.go | 147 ++++++++---------- bundle/bundle_test.go | 24 ++- bundle/testdata/testapp/a_subdirectory/hi.jpg | Bin 0 -> 32762 bytes bundle/testdata/testapp/manifest.yaml | 2 - bundle/testdata/testapp/test_app.star | 6 + bundle/testdata/testapp/unused.txt | 1 + cmd/community/manifestprompt.go | 12 +- cmd/community/validatemanifest.go | 5 - cmd/render.go | 55 +++++-- cmd/serve.go | 9 +- docs/modules.md | 6 +- docs/tutorial.md | 8 +- examples/{ => bitcoin}/bitcoin.star | 9 +- examples/bitcoin/icon.png | Bin 0 -> 207 bytes examples/{ => clock}/clock.star | 0 examples/{ => font-preview}/font-preview.star | 0 examples/{ => hello_world}/hello_world.star | 0 examples/{ => humanize}/humanize.star | 0 examples/life/gosper_glider.txt | 42 +++++ examples/{ => life}/life.star | 4 +- examples/{ => qrcode}/qrcode.star | 0 examples/{ => quadrants}/quadrants.star | 0 .../schema_hello_world.star | 0 examples/{ => sunrise}/sunrise.star | 0 go.mod | 6 +- go.sum | 38 +---- main.go | 1 - main_nonjs.go | 1 + manifest/manifest.go | 29 ++-- manifest/manifest_test.go | 34 ++-- manifest/testdata/manifest.yaml | 2 - manifest/validate.go | 43 ----- manifest/validate_test.go | 52 ------- runtime/applet.go | 141 ++++++++++------- runtime/file.go | 112 +++++++++++++ runtime/file_test.go | 39 +++++ server/loader/loader.go | 13 +- server/loader/script.go | 14 +- server/loader/script_js.go | 26 ---- server/server.go | 35 ++++- server/{watcher/fs.go => watcher.go} | 32 +++- server/watcher/fs_js.go | 11 -- server/watcher/watcher.go | 21 --- starlarkutil/onexit.go | 27 ++++ tools/generator/generator.go | 13 +- tools/singlefilefs.go | 27 ++++ 48 files changed, 606 insertions(+), 455 deletions(-) create mode 100644 bundle/testdata/testapp/a_subdirectory/hi.jpg create mode 100644 bundle/testdata/testapp/unused.txt rename examples/{ => bitcoin}/bitcoin.star (67%) create mode 100644 examples/bitcoin/icon.png rename examples/{ => clock}/clock.star (100%) rename examples/{ => font-preview}/font-preview.star (100%) rename examples/{ => hello_world}/hello_world.star (100%) rename examples/{ => humanize}/humanize.star (100%) create mode 100644 examples/life/gosper_glider.txt rename examples/{ => life}/life.star (62%) rename examples/{ => qrcode}/qrcode.star (100%) rename examples/{ => quadrants}/quadrants.star (100%) rename examples/{ => schema_hello_world}/schema_hello_world.star (100%) rename examples/{ => sunrise}/sunrise.star (100%) create mode 100644 runtime/file.go create mode 100644 runtime/file_test.go delete mode 100644 server/loader/script_js.go rename server/{watcher/fs.go => watcher.go} (63%) delete mode 100644 server/watcher/fs_js.go delete mode 100644 server/watcher/watcher.go create mode 100644 starlarkutil/onexit.go create mode 100644 tools/singlefilefs.go diff --git a/.gitignore b/.gitignore index 672c066f1f..659fc01ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ .DS_Store # Rendered Apps -examples/*.webp -examples/*.gif +examples/**/*.webp +examples/**/*.gif # Pixlet Binary pixlet diff --git a/README.md b/README.md index d998a22ec9..0fade81e60 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ def main(): Render and serve it with: ```console -curl https://raw.githubusercontent.com/tidbyt/pixlet/main/examples/hello_world.star | \ +curl https://raw.githubusercontent.com/tidbyt/pixlet/main/examples/hello_world/hello_world.star | \ pixlet serve /dev/stdin ``` @@ -135,7 +135,7 @@ to show the Bitcoin tracker on your Tidbyt: ```console # render the bitcoin example -pixlet render examples/bitcoin.star +pixlet render examples/bitcoin/bitcoin.star # login to your Tidbyt account pixlet login @@ -144,7 +144,7 @@ pixlet login pixlet devices # push to your favorite Tidbyt -pixlet push examples/bitcoin.webp +pixlet push examples/bitcoin/bitcoin.webp ``` To get the ID for a device, run `pixlet devices`. Alternatively, you can @@ -158,8 +158,8 @@ If all goes well, you should see the Bitcoin tracker appear on your Tidbyt: Pushing an applet to your Tidbyt without an installation ID simply displays your applet one time. If you would like your applet to continously display as part of the rotation, add an installation ID to the push command: ```console -pixlet render examples/bitcoin.star -pixlet push --installation-id examples/bitcoin.webp +pixlet render examples/bitcoin/bitcoin.star +pixlet push --installation-id examples/bitcoin/bitcoin.webp ``` For example, if we set the `installationID` to "Bitcoin", it would appear in the mobile app as follows: diff --git a/bundle/bundle.go b/bundle/bundle.go index 5c4c4c211b..fe777de1ee 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -7,10 +7,14 @@ import ( "compress/gzip" "fmt" "io" + "io/fs" "os" "path/filepath" + "github.com/nlepage/go-tarfs" + "tidbyt.dev/pixlet/manifest" + "tidbyt.dev/pixlet/runtime" ) const ( @@ -26,16 +30,12 @@ const ( // AppBundle represents the unpacked bundle in our system. type AppBundle struct { - Source []byte Manifest *manifest.Manifest + Source fs.FS } -// InitFromPath translates a directory containing an app manifest and source -// into an AppBundle. -func InitFromPath(dir string) (*AppBundle, error) { - // Load manifest - path := filepath.Join(dir, manifest.ManifestFileName) - m, err := os.Open(path) +func fromFS(fs fs.FS) (*AppBundle, error) { + m, err := fs.Open(manifest.ManifestFileName) if err != nil { return nil, fmt.Errorf("could not open manifest: %w", err) } @@ -46,80 +46,39 @@ func InitFromPath(dir string) (*AppBundle, error) { return nil, fmt.Errorf("could not load manifest: %w", err) } - // Load source - path = filepath.Join(dir, man.FileName) - s, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("could not open app source: %w", err) - } - defer s.Close() - - src, err := io.ReadAll(s) - if err != nil { - return nil, fmt.Errorf("could not read app source: %w", err) - } - // Create app bundle struct return &AppBundle{ Manifest: man, - Source: src, + Source: fs, }, nil } +// InitFromPath translates a directory containing an app manifest and source +// into an AppBundle. +func InitFromPath(dir string) (*AppBundle, error) { + return fromFS(os.DirFS(dir)) +} + // LoadBundle loads a compressed archive into an AppBundle. func LoadBundle(in io.Reader) (*AppBundle, error) { gzr, err := gzip.NewReader(in) if err != nil { - return nil, fmt.Errorf("could not create gzip reader: %w", err) + return nil, fmt.Errorf("creating gzip reader: %w", err) } defer gzr.Close() - tr := tar.NewReader(gzr) - ab := &AppBundle{} - - for { - header, err := tr.Next() - - switch { - case err == io.EOF: - // If there are no more files in the bundle, validate and return it. - if ab.Manifest == nil { - return nil, fmt.Errorf("could not find manifest in archive") - } - if ab.Source == nil { - return nil, fmt.Errorf("could not find source in archive") - } - return ab, nil - case err != nil: - // If there is an error, return immediately. - return nil, fmt.Errorf("could not read archive: %w", err) - case header == nil: - // If for some reason we end up with a blank header, continue to the - // next one. - continue - case header.Name == AppSourceName: - // Load the app source. - buff := make([]byte, header.Size) - _, err := io.ReadFull(tr, buff) - if err != nil { - return nil, fmt.Errorf("could not read source from archive: %w", err) - } - ab.Source = buff - case header.Name == manifest.ManifestFileName: - // Load the app manifest. - buff := make([]byte, header.Size) - _, err := io.ReadFull(tr, buff) - if err != nil { - return nil, fmt.Errorf("could not read manifest from archive: %w", err) - } + // read the entire tarball into memory so that we can seek + // around it, and so that the underlying reader can be closed. + var b bytes.Buffer + io.Copy(&b, gzr) - man, err := manifest.LoadManifest(bytes.NewReader(buff)) - if err != nil { - return nil, fmt.Errorf("could not load manifest: %w", err) - } - ab.Manifest = man - } + r := bytes.NewReader(b.Bytes()) + fs, err := tarfs.New(r) + if err != nil { + return nil, fmt.Errorf("creating tarfs: %w", err) } + + return fromFS(fs) } // WriteBundleToPath is a helper to be able to write the bundle to a provided @@ -137,6 +96,16 @@ func (b *AppBundle) WriteBundleToPath(dir string) error { // WriteBundle writes a compressed archive to the provided writer. func (ab *AppBundle) WriteBundle(out io.Writer) error { + // we don't want to naively write the entire source FS to the tarball, + // since it could contain a lot of extraneous files. instead, run the + // applet and interrogate it for the files it needs to include in the + // bundle. + app, err := runtime.NewAppletFromFS(ab.Manifest.ID, ab.Source, runtime.WithPrintDisabled()) + if err != nil { + return fmt.Errorf("loading applet for bundling: %w", err) + } + bundleFiles := app.PathsForBundle() + // Setup writers. gzw := gzip.NewWriter(out) defer gzw.Close() @@ -146,7 +115,7 @@ func (ab *AppBundle) WriteBundle(out io.Writer) error { // Write manifest. buff := &bytes.Buffer{} - err := ab.Manifest.WriteManifest(buff) + err = ab.Manifest.WriteManifest(buff) if err != nil { return fmt.Errorf("could not write manifest to buffer: %w", err) } @@ -166,19 +135,37 @@ func (ab *AppBundle) WriteBundle(out io.Writer) error { return fmt.Errorf("could not write manifest to archive: %w", err) } - // Write source. - hdr = &tar.Header{ - Name: AppSourceName, - Mode: 0600, - Size: int64(len(ab.Source)), - } - err = tw.WriteHeader(hdr) - if err != nil { - return fmt.Errorf("could not write source header: %w", err) - } - _, err = tw.Write(ab.Source) - if err != nil { - return fmt.Errorf("could not write source to archive: %w", err) + // write sources. + for _, path := range bundleFiles { + stat, err := fs.Stat(ab.Source, path) + if err != nil { + return fmt.Errorf("could not stat %s: %w", path, err) + } + + hdr, err := tar.FileInfoHeader(stat, "") + if err != nil { + return fmt.Errorf("creating header for %s: %w", path, err) + } + hdr.Name = filepath.ToSlash(path) + + err = tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("writing header for %s: %w", path, err) + } + + if !stat.IsDir() { + file, err := ab.Source.Open(path) + if err != nil { + return fmt.Errorf("opening file %s: %w", path, err) + } + + written, err := io.Copy(tw, file) + if err != nil { + return fmt.Errorf("writing file %s: %w", path, err) + } else if written != stat.Size() { + return fmt.Errorf("did not write entire file %s: %w", path, err) + } + } } return nil diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 77e9ffbd37..307a0eb6b1 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -14,7 +14,7 @@ func TestBundleWriteAndLoad(t *testing.T) { ab, err := bundle.InitFromPath("testdata/testapp") assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) // Create a temp directory. dir, err := os.MkdirTemp("", "") @@ -32,7 +32,23 @@ func TestBundleWriteAndLoad(t *testing.T) { newBun, err := bundle.LoadBundle(f) assert.NoError(t, err) assert.Equal(t, "test-app", newBun.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) + + // Ensure the loaded bundle contains the files we expect. + filesExpected := []string{ + "manifest.yaml", + "test_app.star", + "test.txt", + "a_subdirectory/hi.jpg", + } + for _, file := range filesExpected { + _, err := newBun.Source.Open(file) + assert.NoError(t, err) + } + + // Ensure the loaded bundle does not contain any extra files. + _, err = newBun.Source.Open("unused.txt") + assert.ErrorIs(t, err, os.ErrNotExist) } func TestLoadBundle(t *testing.T) { f, err := os.Open("testdata/bundle.tar.gz") @@ -41,7 +57,7 @@ func TestLoadBundle(t *testing.T) { ab, err := bundle.LoadBundle(f) assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) } func TestLoadBundleExcessData(t *testing.T) { f, err := os.Open("testdata/excess-files.tar.gz") @@ -51,5 +67,5 @@ func TestLoadBundleExcessData(t *testing.T) { ab, err := bundle.LoadBundle(f) assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) } diff --git a/bundle/testdata/testapp/a_subdirectory/hi.jpg b/bundle/testdata/testapp/a_subdirectory/hi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8da8481f2fa0c1e5e9d8cb7438c20ef59c174dd2 GIT binary patch literal 32762 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 SEv$p6`Gq^zj` literal 0 HcmV?d00001 diff --git a/bundle/testdata/testapp/manifest.yaml b/bundle/testdata/testapp/manifest.yaml index f2b10a8a60..630cebcd79 100644 --- a/bundle/testdata/testapp/manifest.yaml +++ b/bundle/testdata/testapp/manifest.yaml @@ -4,5 +4,3 @@ name: Test App summary: For Testing desc: It's an app for testing. author: Test Dev -fileName: test_app.star -packageName: testapp diff --git a/bundle/testdata/testapp/test_app.star b/bundle/testdata/testapp/test_app.star index fc8ae9d840..cee0a6c5bc 100644 --- a/bundle/testdata/testapp/test_app.star +++ b/bundle/testdata/testapp/test_app.star @@ -5,11 +5,17 @@ Description: It's an app for testing. Author: Test Dev """ +load("a_subdirectory/hi.jpg", hi_jpeg = "file") load("render.star", "render") load("schema.star", "schema") +load("test.txt", test_txt = "file") DEFAULT_WHO = "world" +TEST_TXT_CONTENT = test_txt.readall() + +HI_JPEG_BYTES = hi_jpeg.readall("rb") + def main(config): who = config.str("who", DEFAULT_WHO) message = "Hello, {}!".format(who) diff --git a/bundle/testdata/testapp/unused.txt b/bundle/testdata/testapp/unused.txt new file mode 100644 index 0000000000..c3e8caa0b7 --- /dev/null +++ b/bundle/testdata/testapp/unused.txt @@ -0,0 +1 @@ +this file is not used in the app \ No newline at end of file diff --git a/cmd/community/manifestprompt.go b/cmd/community/manifestprompt.go index b2ac75eb47..40c1c73e3c 100644 --- a/cmd/community/manifestprompt.go +++ b/cmd/community/manifestprompt.go @@ -51,12 +51,10 @@ func ManifestPrompt() (*manifest.Manifest, error) { } return &manifest.Manifest{ - ID: manifest.GenerateID(name), - Name: name, - Summary: summary, - Desc: desc, - Author: author, - FileName: manifest.GenerateFileName(name), - PackageName: manifest.GeneratePackageName(name), + ID: manifest.GenerateID(name), + Name: name, + Summary: summary, + Desc: desc, + Author: author, }, nil } diff --git a/cmd/community/validatemanifest.go b/cmd/community/validatemanifest.go index 35c0f90df3..ef475658a4 100644 --- a/cmd/community/validatemanifest.go +++ b/cmd/community/validatemanifest.go @@ -12,7 +12,6 @@ import ( var ValidateManifestAppFileName string func init() { - ValidateManifestCmd.Flags().StringVarP(&ValidateManifestAppFileName, "app-file-name", "a", "", "ensures the app file name is the same as the manifest") } var ValidateManifestCmd = &cobra.Command{ @@ -47,9 +46,5 @@ func ValidateManifest(cmd *cobra.Command, args []string) error { return fmt.Errorf("couldn't validate manifest: %w", err) } - if ValidateManifestAppFileName != "" && m.FileName != ValidateManifestAppFileName { - return fmt.Errorf("app name doesn't match: %s != %s", ValidateManifestAppFileName, m.FileName) - } - return nil } diff --git a/cmd/render.go b/cmd/render.go index d5746ea5ec..d64327c78c 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -4,8 +4,11 @@ import ( "context" "fmt" "image" + "io/fs" "os" + "path/filepath" "strings" + "testing/fstest" "time" "github.com/spf13/cobra" @@ -68,23 +71,49 @@ func init() { } var RenderCmd = &cobra.Command{ - Use: "render [script] [=value>]...", - Short: "Run a Pixlet script with provided config parameters", + Use: "render [path] [=value>]...", + Short: "Run a Pixlet app with provided config parameters", Args: cobra.MinimumNArgs(1), RunE: render, + Long: `Render a Pixlet app with provided config parameters. + +The path argument should be the path to the Pixlet app to run. The +app can be a single file with the .star extension, or a directory +containing multiple Starlark files and resources. + `, } func render(cmd *cobra.Command, args []string) error { - script := args[0] + path := args[0] - globals.Width = width - globals.Height = height + // check if path exists, and whether it is a directory or a file + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("failed to stat %s: %w", path, err) + } + + var fs fs.FS + var outPath string + if info.IsDir() { + fs = os.DirFS(path) + outPath = filepath.Join(path, filepath.Base(path)) + } else { + if !strings.HasSuffix(path, ".star") { + return fmt.Errorf("script file must have suffix .star: %s", path) + } + + src, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", path, err) + } + + fs = fstest.MapFS{ + filepath.Base(path): {Data: src}, + } - if !strings.HasSuffix(script, ".star") { - return fmt.Errorf("script file must have suffix .star: %s", script) + outPath = strings.TrimSuffix(path, ".star") } - outPath := strings.TrimSuffix(script, ".star") if renderGif { outPath += ".gif" } else { @@ -94,6 +123,9 @@ func render(cmd *cobra.Command, args []string) error { outPath = output } + globals.Width = width + globals.Height = height + config := map[string]string{} for _, param := range args[1:] { split := strings.Split(param, "=") @@ -103,11 +135,6 @@ func render(cmd *cobra.Command, args []string) error { config[split[0]] = strings.Join(split[1:], "=") } - src, err := os.ReadFile(script) - if err != nil { - return fmt.Errorf("failed to read file %s: %w", script, err) - } - // Remove the print function from the starlark thread if the silent flag is // passed. var opts []runtime.AppletOption @@ -128,7 +155,7 @@ func render(cmd *cobra.Command, args []string) error { runtime.InitHTTP(cache) runtime.InitCache(cache) - applet, err := runtime.NewApplet(script, src, opts...) + applet, err := runtime.NewAppletFromFS(filepath.Base(path), fs, opts...) if err != nil { return fmt.Errorf("failed to load applet: %w", err) } diff --git a/cmd/serve.go b/cmd/serve.go index 10baa95127..b2bf0c71eb 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -15,16 +15,21 @@ var ( func init() { ServeCmd.Flags().StringVarP(&host, "host", "i", "127.0.0.1", "Host interface for serving rendered images") ServeCmd.Flags().IntVarP(&port, "port", "p", 8080, "Port for serving rendered images") - ServeCmd.Flags().BoolVarP(&watch, "watch", "w", true, "Reload scripts on change") + ServeCmd.Flags().BoolVarP(&watch, "watch", "w", true, "Reload scripts on change. Does not recurse sub-directories.") ServeCmd.Flags().IntVarP(&maxDuration, "max_duration", "d", 15000, "Maximum allowed animation duration (ms)") ServeCmd.Flags().IntVarP(&timeout, "timeout", "", 30000, "Timeout for execution (ms)") } var ServeCmd = &cobra.Command{ - Use: "serve [script]", + Use: "serve [path]", Short: "Serve a Pixlet app in a web server", Args: cobra.ExactArgs(1), RunE: serve, + Long: `Serve a Pixlet app in a web server. + +The path argument should be the path to the Pixlet program to run. The +program can be a single file with the .star extension, or a directory +containing multiple Starlark files and resources.`, } func serve(cmd *cobra.Command, args []string) error { diff --git a/docs/modules.md b/docs/modules.md index 90a80cef7f..a7d05446fd 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -111,7 +111,7 @@ The `humanize` module has formatters for units to human friendly sizes. Example: -See [examples/humanize.star](../examples/humanize.star) for an example. +See [examples/humanize/humanize.star](../examples/humanize/humanize.star) for an example. ## Pixlet module: XPath @@ -177,7 +177,7 @@ The schema module provides configuration options for your app. See the [schema d Example: -See [examples/schema_hello_world.star](../examples/schema_hello_world.star) for an example. +See [examples/schema_hello_world/schema_hello_world.star](../examples/schema_hello_world/schema_hello_world.star) for an example. ## Pixlet module: Secret @@ -210,7 +210,7 @@ The `sunrise` module calculates sunrise and sunset times for a given set of GPS Example: -See [examples/sunrise.star](../examples/sunrise.star) for an example. +See [examples/sunrise/sunrise.star](../examples/sunrise/sunrise.star) for an example. ## Pixlet module: Random diff --git a/docs/tutorial.md b/docs/tutorial.md index 4375942679..a8ce678e56 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -9,19 +9,19 @@ biased. You should be able to run the Pixlet CLI like so: -`$ pixlet render examples/clock.star` +`$ pixlet render examples/clock/clock.star` This should in turn run `clock.star` and produce a `clock.webp` file. ```console -$ file examples/clock.webp -examples/clock.webp: RIFF (little-endian) data, Web/P image +$ file examples/clock/clock.webp +examples/clock/clock.webp: RIFF (little-endian) data, Web/P image ``` For local development, its often convenient to run pixlet in "serve" mode: -`$ pixlet serve --watch examples/clock.star` +`$ pixlet serve --watch examples/clock/clock.star` Direct your web browser to http://localhost:8080, and your rendered app will appear. diff --git a/examples/bitcoin.star b/examples/bitcoin/bitcoin.star similarity index 67% rename from examples/bitcoin.star rename to examples/bitcoin/bitcoin.star index 19ea6748a1..f24e7ead42 100644 --- a/examples/bitcoin.star +++ b/examples/bitcoin/bitcoin.star @@ -1,15 +1,10 @@ -load("encoding/base64.star", "base64") load("http.star", "http") +load("icon.png", icon = "file") load("render.star", "render") COINDESK_PRICE_URL = "https://api.coindesk.com/v1/bpi/currentprice.json" -BTC_ICON = base64.decode(""" -iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAlklEQVQ4T2NkwAH+H2T/jy7FaP+ -TEZtyDEG4Zi0TTPXXzoDF0A1DMQRsADbN6MZdO4NiENwQbAbERh1lWLzMmgFGo5iFZBDYEFwuwG -sISCPUIKyGgDRjAyBXYXMNIz5XgDQga8TpLboYgux8DO/AwoUuLiEqTLBFMcmxQ7V0gssgklIsL -AYozjsoBoE45OZi5DRBSnkCAMLhlPBiQGHlAAAAAElFTkSuQmCC -""") +BTC_ICON = icon.readall() def main(): rep = http.get(COINDESK_PRICE_URL, ttl_seconds = 240) diff --git a/examples/bitcoin/icon.png b/examples/bitcoin/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8ffd20791539b4a12697dbd6db62f0c6b1178ae1 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2e8X`U{QAr}70DF+z;$*279*E^c= zf3o20A|A&bX}ZEbU$38QIC_EC*^niN!R+jdW3krFNdk8Sa@dZz$)-l^IWvpVZSjoO z6oDH8F?t8GIXslFD6DB~FiB=s2v02LRkjOnFj2@p@=|w~MAMrZp7#e1wd(09YWZw% zH9Wb|d2312X@yBaIy!6`=d3l@8ZDkYOL}7B=vB$YaOmNb4@nM*Pk}CE@O1TaS?83{ F1OUH$O4I-V literal 0 HcmV?d00001 diff --git a/examples/clock.star b/examples/clock/clock.star similarity index 100% rename from examples/clock.star rename to examples/clock/clock.star diff --git a/examples/font-preview.star b/examples/font-preview/font-preview.star similarity index 100% rename from examples/font-preview.star rename to examples/font-preview/font-preview.star diff --git a/examples/hello_world.star b/examples/hello_world/hello_world.star similarity index 100% rename from examples/hello_world.star rename to examples/hello_world/hello_world.star diff --git a/examples/humanize.star b/examples/humanize/humanize.star similarity index 100% rename from examples/humanize.star rename to examples/humanize/humanize.star diff --git a/examples/life/gosper_glider.txt b/examples/life/gosper_glider.txt new file mode 100644 index 0000000000..8ee81f9968 --- /dev/null +++ b/examples/life/gosper_glider.txt @@ -0,0 +1,42 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 \ No newline at end of file diff --git a/examples/life.star b/examples/life/life.star similarity index 62% rename from examples/life.star rename to examples/life/life.star index d84274ad7b..0691ac0cfb 100644 --- a/examples/life.star +++ b/examples/life/life.star @@ -15,6 +15,7 @@ load("cache.star", "cache") load("encoding/base64.star", "base64") +load("gosper_glider.txt", gosper_glider = "file") load("render.star", "render") load("time.star", "time") @@ -476,5 +477,4 @@ def get_seeded_gosper_glider_board(): method and then base64 encoded. This was a trade off in repeatability with efficiency. I originally rendered this by using points mapped to create the Gosper Glider Gun and running through a few iterations until it looked nice. """ - encoded_board = "MCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwxLDEsMSwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwxLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwwLDAsMCwwLDEsMSwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMSwwLDAsMSwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwxLDAsMCwwLDAsMCwwLDAsMCwxLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDA=" - return decode(base64.decode(encoded_board)) + return decode(gosper_glider.readall()) diff --git a/examples/qrcode.star b/examples/qrcode/qrcode.star similarity index 100% rename from examples/qrcode.star rename to examples/qrcode/qrcode.star diff --git a/examples/quadrants.star b/examples/quadrants/quadrants.star similarity index 100% rename from examples/quadrants.star rename to examples/quadrants/quadrants.star diff --git a/examples/schema_hello_world.star b/examples/schema_hello_world/schema_hello_world.star similarity index 100% rename from examples/schema_hello_world.star rename to examples/schema_hello_world/schema_hello_world.star diff --git a/examples/sunrise.star b/examples/sunrise/sunrise.star similarity index 100% rename from examples/sunrise.star rename to examples/sunrise/sunrise.star diff --git a/go.mod b/go.mod index 7b9f152072..485b35137f 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/newm4n/go-dfe v0.0.0-20210113055126-9d5f01722db9 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c + github.com/nlepage/go-tarfs v1.2.1 github.com/nlepage/go-wasm-http-server v1.1.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/qri-io/starlib v0.5.1-0.20220611014110-7fb7ff9ec804 @@ -100,11 +101,10 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sys v0.18.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index cb7658d840..af16291738 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= @@ -23,12 +21,8 @@ github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5z github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0= -github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xmlquery v1.4.0 h1:xg2HkfcRK2TeTbdb0m1jxCYnvsPaGY/oeZWTGqX/0hA= github.com/antchfx/xmlquery v1.4.0/go.mod h1:Ax2aeaeDjfIw3CwXKDQ0GkwZ6QlxoChlIBP+mGnDFjI= -github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= -github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc= github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -59,7 +53,6 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= @@ -101,16 +94,14 @@ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gitsight/go-vcsurl v1.0.1 h1:wkijKsbVg9R2IBP97U7wOANeIW9WJJKkBwS9XqllzWo= github.com/gitsight/go-vcsurl v1.0.1/go.mod h1:qRFdKDa/0Lh9MT0xE+qQBYZ/01+mY1H40rZUHR24X9U= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -165,8 +156,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY= -github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f h1:WpZiq8iqvGjJ3m3wzAVKL6+0vz7VkE79iSy9GII00II= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= @@ -243,6 +232,8 @@ github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ= github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo= +github.com/nlepage/go-tarfs v1.2.1 h1:o37+JPA+ajllGKSPfy5+YpsNHDjZnAoyfvf5GsUa+Ks= +github.com/nlepage/go-tarfs v1.2.1/go.mod h1:rno18mpMy9aEH1IiJVftFsqPyIpwqSUiAOpJYjlV2NA= github.com/nlepage/go-wasm-http-server v1.1.0 h1:phw2NtSp71m/6NmGjE2veQ41PBPzWFcnE614cKucy5M= github.com/nlepage/go-wasm-http-server v1.1.0/go.mod h1:xpffUeN97vuv8CTlMJ2oC5tPsftfPoG9HkAgI9gkiPI= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -287,16 +278,12 @@ github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgY github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= @@ -362,8 +349,6 @@ github.com/zachomedia/go-bdf v0.0.0-20220611021443-a3af701111be/go.mod h1:FWqHpm go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.starlark.net v0.0.0-20210223155950-e043a3d3c984/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= -go.starlark.net v0.0.0-20240329153429-e6e8e7ce1b7a h1:Oe+v9w90BBIxQZ4U39+axR8KxrBbxqnRudPPcBIlP3o= -go.starlark.net v0.0.0-20240329153429-e6e8e7ce1b7a/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.starlark.net v0.0.0-20240411212711-9b43f0afd521 h1:1Ufp2S2fPpj0RHIQ4rbzpCdPLCPkzdK7BaVFH3nkYBQ= go.starlark.net v0.0.0-20240411212711-9b43f0afd521/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -392,8 +377,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -402,7 +387,6 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -416,8 +400,6 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -463,7 +445,6 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -483,15 +464,13 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -528,7 +507,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index f2f64cfbc0..153d62edba 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,6 @@ var ( ) func init() { - rootCmd.AddCommand(cmd.ServeCmd) rootCmd.AddCommand(cmd.RenderCmd) rootCmd.AddCommand(cmd.PushCmd) rootCmd.AddCommand(cmd.EncryptCmd) diff --git a/main_nonjs.go b/main_nonjs.go index 2f82271f3a..5125a866ac 100644 --- a/main_nonjs.go +++ b/main_nonjs.go @@ -10,4 +10,5 @@ import ( func init() { rootCmd.AddCommand(private.PrivateCmd) rootCmd.AddCommand(cmd.CreateCmd) + rootCmd.AddCommand(cmd.ServeCmd) } diff --git a/manifest/manifest.go b/manifest/manifest.go index bfa0281318..9b32885aa3 100644 --- a/manifest/manifest.go +++ b/manifest/manifest.go @@ -16,21 +16,22 @@ type Manifest struct { // ID is the unique identifier of this app. It has to be globally unique, // which means it cannot conflict with any of our private apps. ID string `json:"id" yaml:"id"` + // Name is the name of the applet. Ex. "Fuzzy Clock" Name string `json:"name" yaml:"name"` + // Summary is the short form of what this applet does. Ex. "Human readable // time". Summary string `json:"summary" yaml:"summary"` + // Desc is the long form of what this applet does. Ex. "Display the time in // a groovy, human-readable way." Desc string `json:"desc" yaml:"desc"` + // Author is the person or organization who contributed this applet. Ex, // "Max Timkovich" Author string `json:"author" yaml:"author"` - // FileName is the name of the starlark source file. - FileName string `json:"fileName" yaml:"fileName"` - // PackageName is the name of the go package where this app lives. - PackageName string `json:"packageName" yaml:"packageName"` + // Source is the starlark source code for this applet using the go `embed` // module. Source []byte `json:"-" yaml:"-"` @@ -100,24 +101,14 @@ func (m Manifest) Validate() error { return err } - err = ValidateFileName(m.FileName) - if err != nil { - return err - } - - err = ValidatePackageName(m.PackageName) - if err != nil { - return err - } - return nil } -// GeneratePackageName creates a suitable go package name from an app name. -func GeneratePackageName(name string) string { - packageName := strings.ReplaceAll(name, "-", "") - packageName = strings.ReplaceAll(packageName, "_", "") - return strings.ToLower(strings.Join(strings.Fields(packageName), "")) +// GenerateDirName creates a suitable directory name from an app name. +func GenerateDirName(name string) string { + dir := strings.ReplaceAll(name, "-", "") + dir = strings.ReplaceAll(dir, "_", "") + return strings.ToLower(strings.Join(strings.Fields(dir), "")) } // GenerateID creates a suitable ID from an app name. diff --git a/manifest/manifest_test.go b/manifest/manifest_test.go index 57d8429d91..a29101046b 100644 --- a/manifest/manifest_test.go +++ b/manifest/manifest_test.go @@ -21,20 +21,16 @@ name: Foo Tracker summary: Track realtime foo desc: The foo tracker provides realtime feeds for foo. author: Tidbyt -fileName: foo_tracker.star -packageName: footracker ` func TestManifest(t *testing.T) { m := manifest.Manifest{ - ID: "foo-tracker", - Name: "Foo Tracker", - Summary: "Track realtime foo", - Desc: "The foo tracker provides realtime feeds for foo.", - Author: "Tidbyt", - FileName: "foo_tracker.star", - PackageName: "footracker", - Source: source, + ID: "foo-tracker", + Name: "Foo Tracker", + Summary: "Track realtime foo", + Desc: "The foo tracker provides realtime feeds for foo.", + Author: "Tidbyt", + Source: source, } expected, err := os.ReadFile("testdata/source.star") @@ -56,20 +52,16 @@ func TestLoadManifest(t *testing.T) { assert.Equal(t, m.Author, "Max Timkovich") assert.Equal(t, m.Summary, "Human readable time") assert.Equal(t, m.Desc, "Display the time in a groovy, human-readable way.") - assert.Equal(t, m.FileName, "fuzzy_clock.star") - assert.Equal(t, m.PackageName, "fuzzyclock") } func TestWriteManifest(t *testing.T) { m := manifest.Manifest{ - ID: "foo-tracker", - Name: "Foo Tracker", - Summary: "Track realtime foo", - Desc: "The foo tracker provides realtime feeds for foo.", - Author: "Tidbyt", - FileName: "foo_tracker.star", - PackageName: "footracker", - Source: source, + ID: "foo-tracker", + Name: "Foo Tracker", + Summary: "Track realtime foo", + Desc: "The foo tracker provides realtime feeds for foo.", + Author: "Tidbyt", + Source: source, } buff := bytes.Buffer{} @@ -96,7 +88,7 @@ func TestGeneratePackageName(t *testing.T) { } for _, tc := range tests { - got := manifest.GeneratePackageName(tc.input) + got := manifest.GenerateDirName(tc.input) assert.Equal(t, tc.want, got) } } diff --git a/manifest/testdata/manifest.yaml b/manifest/testdata/manifest.yaml index 7f3254a77d..6e2f0e2f0c 100644 --- a/manifest/testdata/manifest.yaml +++ b/manifest/testdata/manifest.yaml @@ -4,5 +4,3 @@ name: Fuzzy Clock summary: Human readable time desc: Display the time in a groovy, human-readable way. author: Max Timkovich -fileName: fuzzy_clock.star -packageName: fuzzyclock diff --git a/manifest/validate.go b/manifest/validate.go index 27f2e3650c..fddc036b0d 100644 --- a/manifest/validate.go +++ b/manifest/validate.go @@ -119,49 +119,6 @@ func ValidateAuthor(author string) error { return nil } -func ValidatePackageName(packageName string) error { - if packageName == "" { - return fmt.Errorf("package names cannot be empty") - } - - if packageName != strings.ToLower(packageName) { - return fmt.Errorf("package names should be lower case") - } - - for _, r := range packageName { - if !(unicode.IsLetter(r) || unicode.IsNumber(r)) { - return fmt.Errorf("package names can only contain letters, numbers, or an underscore character") - } - } - return nil -} - -// ValidateFileName ensures the file name appears appropriately for starlark -// source code. -func ValidateFileName(fileName string) error { - if fileName == "" { - return fmt.Errorf("fileName cannot be empty") - } - - if !strings.HasSuffix(fileName, ".star") { - return fmt.Errorf("file names should end in .star: '%s'", fileName) - } - - testName := strings.TrimSuffix(fileName, ".star") - - if testName != strings.ToLower(testName) { - return fmt.Errorf("file names should be lower case") - } - - for _, r := range testName { - if !(unicode.IsLetter(r) || unicode.IsNumber(r) || r == underscore) { - return fmt.Errorf("file names can only contain letters, numbers, or an underscore character") - } - } - - return nil -} - // ValidateID ensures the id will parse when we go to add it to our database // internally. func ValidateID(id string) error { diff --git a/manifest/validate_test.go b/manifest/validate_test.go index f960d4dfa5..e5d5f2648a 100644 --- a/manifest/validate_test.go +++ b/manifest/validate_test.go @@ -111,55 +111,3 @@ func TestValidateID(t *testing.T) { } } - -func TestValidateFileName(t *testing.T) { - type test struct { - input string - shouldErr bool - } - - tests := []test{ - {input: "foo_bar.star", shouldErr: false}, - {input: "foo_bar", shouldErr: true}, - {input: "FooBar.star", shouldErr: true}, - {input: "foo$.star", shouldErr: true}, - {input: "", shouldErr: true}, - } - - for _, tc := range tests { - err := manifest.ValidateFileName(tc.input) - - if tc.shouldErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - } - -} - -func TestValidatePackageName(t *testing.T) { - type test struct { - input string - shouldErr bool - } - - tests := []test{ - {input: "foobar", shouldErr: false}, - {input: "foo_bar", shouldErr: true}, - {input: "FooBar", shouldErr: true}, - {input: "foo$", shouldErr: true}, - {input: "", shouldErr: true}, - } - - for _, tc := range tests { - err := manifest.ValidatePackageName(tc.input) - - if tc.shouldErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - } - -} diff --git a/runtime/applet.go b/runtime/applet.go index c515515f69..c97995d07a 100644 --- a/runtime/applet.go +++ b/runtime/applet.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" "io/fs" - "path/filepath" + "path" "slices" "strings" "testing" @@ -56,6 +56,7 @@ type Applet struct { loader ModuleLoader initializers []ThreadInitializer + loadedPaths map[string]bool globals map[string]starlark.StringDict @@ -119,8 +120,9 @@ func NewApplet(id string, src []byte, opts ...AppletOption) (*Applet, error) { func NewAppletFromFS(id string, fsys fs.FS, opts ...AppletOption) (*Applet, error) { a := &Applet{ - ID: id, - globals: make(map[string]starlark.StringDict), + ID: id, + globals: make(map[string]starlark.StringDict), + loadedPaths: make(map[string]bool), } for _, opt := range opts { @@ -274,6 +276,7 @@ func (a *Applet) Call(ctx context.Context, callable *starlark.Function, args ... }() t := a.newThread(ctx) + defer starlarkutil.RunOnExitFuncs(t) context.AfterFunc(ctx, func() { t.Cancel(context.Cause(ctx).Error()) @@ -296,23 +299,33 @@ func (a *Applet) Call(ctx context.Context, callable *starlark.Function, args ... return resultVal, nil } +// PathsForBundle returns a list of all the paths that have been loaded by the +// applet. This is useful for creating a bundle of the applet. +func (a *Applet) PathsForBundle() []string { + paths := make([]string, 0, len(a.loadedPaths)) + for path := range a.loadedPaths { + paths = append(paths, path) + } + return paths +} + func (a *Applet) load(fsys fs.FS) (err error) { - if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, walkDirErr error) error { + if err := fs.WalkDir(fsys, ".", func(pathToLoad string, d fs.DirEntry, walkDirErr error) error { if walkDirErr != nil { return walkDirErr } - if d.IsDir() || filepath.Dir(path) != "." { + if d.IsDir() || path.Dir(pathToLoad) != "." { // only process files in the root directory return nil } - if !strings.HasSuffix(path, ".star") { + if !strings.HasSuffix(pathToLoad, ".star") { // not a starlark file return nil } - return a.ensureLoaded(fsys, path) + return a.ensureLoaded(fsys, pathToLoad) }); err != nil { return err } @@ -324,7 +337,7 @@ func (a *Applet) load(fsys fs.FS) (err error) { return nil } -func (a *Applet) ensureLoaded(fsys fs.FS, path string, currentlyLoading ...string) (err error) { +func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading ...string) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic while executing %s: %v", a.ID, r) @@ -332,24 +345,28 @@ func (a *Applet) ensureLoaded(fsys fs.FS, path string, currentlyLoading ...strin }() // normalize path so that it can be used as a key - path = filepath.Clean(path) - if _, ok := a.globals[path]; ok { + pathToLoad = path.Clean(pathToLoad) + if _, ok := a.globals[pathToLoad]; ok { // already loaded, good to go return nil } // use the currentlyLoading slice to detect circular dependencies - if slices.Contains(currentlyLoading, path) { - return fmt.Errorf("circular dependency detected: %s -> %s", strings.Join(currentlyLoading, " -> "), path) + if slices.Contains(currentlyLoading, pathToLoad) { + return fmt.Errorf("circular dependency detected: %s -> %s", strings.Join(currentlyLoading, " -> "), pathToLoad) } else { // mark this file as currently loading. if we encounter it again, // we have a circular dependency. - currentlyLoading = append(currentlyLoading, path) + currentlyLoading = append(currentlyLoading, pathToLoad) + + // also mark the file as loaded to keep track of all of the files + // that have been loaded + a.loadedPaths[pathToLoad] = true } - src, err := fs.ReadFile(fsys, path) + src, err := fs.ReadFile(fsys, pathToLoad) if err != nil { - return fmt.Errorf("reading %s: %v", path, err) + return fmt.Errorf("reading %s: %v", pathToLoad, err) } predeclared := starlark.StringDict{ @@ -357,11 +374,12 @@ func (a *Applet) ensureLoaded(fsys fs.FS, path string, currentlyLoading ...strin } thread := a.newThread(context.Background()) + defer starlarkutil.RunOnExitFuncs(thread) // override loader to allow loading starlark files thread.Load = func(thread *starlark.Thread, module string) (starlark.StringDict, error) { // normalize module path - modulePath := filepath.Clean(module) + modulePath := path.Clean(module) // if the module exists on the filesystem, load it if _, err := fs.Stat(fsys, modulePath); err == nil { @@ -382,53 +400,64 @@ func (a *Applet) ensureLoaded(fsys fs.FS, path string, currentlyLoading ...strin return a.loadModule(thread, module) } - globals, err := starlark.ExecFileOptions( - &syntax.FileOptions{ - Set: true, - Recursion: true, - }, - thread, - a.ID, - src, - predeclared, - ) - if err != nil { - return fmt.Errorf("starlark.ExecFile: %v", err) - } - a.globals[path] = globals - - // if the file is in the root directory, check for the main function - // and schema function - mainFun, _ := globals["main"].(*starlark.Function) - if mainFun != nil { - if a.mainFile != "" { - return fmt.Errorf("multiple files with a main() function:\n- %s\n- %s", path, a.mainFile) + switch path.Ext(pathToLoad) { + case ".star": + globals, err := starlark.ExecFileOptions( + &syntax.FileOptions{ + Set: true, + Recursion: true, + }, + thread, + a.ID, + src, + predeclared, + ) + if err != nil { + return fmt.Errorf("starlark.ExecFile: %v", err) } + a.globals[pathToLoad] = globals + + // if the file is in the root directory, check for the main function + // and schema function + mainFun, _ := globals["main"].(*starlark.Function) + if mainFun != nil { + if a.mainFile != "" { + return fmt.Errorf("multiple files with a main() function:\n- %s\n- %s", pathToLoad, a.mainFile) + } - a.mainFile = path - a.mainFun = mainFun - } - - schemaFun, _ := globals[schema.SchemaFunctionName].(*starlark.Function) - if schemaFun != nil { - if a.schemaFile != "" { - return fmt.Errorf("multiple files with a %s() function:\n- %s\n- %s", schema.SchemaFunctionName, path, a.schemaFile) + a.mainFile = pathToLoad + a.mainFun = mainFun } - a.schemaFile = path - schemaVal, err := a.Call(context.Background(), schemaFun) - if err != nil { - return fmt.Errorf("calling schema function for %s: %w", a.ID, err) - } + schemaFun, _ := globals[schema.SchemaFunctionName].(*starlark.Function) + if schemaFun != nil { + if a.schemaFile != "" { + return fmt.Errorf("multiple files with a %s() function:\n- %s\n- %s", schema.SchemaFunctionName, pathToLoad, a.schemaFile) + } + a.schemaFile = pathToLoad - a.schema, err = schema.FromStarlark(schemaVal, globals) - if err != nil { - return fmt.Errorf("parsing schema for %s: %w", a.ID, err) + schemaVal, err := a.Call(context.Background(), schemaFun) + if err != nil { + return fmt.Errorf("calling schema function for %s: %w", a.ID, err) + } + + a.schema, err = schema.FromStarlark(schemaVal, globals) + if err != nil { + return fmt.Errorf("parsing schema for %s: %w", a.ID, err) + } + + a.schemaJSON, err = json.Marshal(a.schema) + if err != nil { + return fmt.Errorf("serializing schema to JSON for %s: %w", a.ID, err) + } } - a.schemaJSON, err = json.Marshal(a.schema) - if err != nil { - return fmt.Errorf("serializing schema to JSON for %s: %w", a.ID, err) + default: + a.globals[pathToLoad] = starlark.StringDict{ + "file": File{ + fsys: fsys, + path: pathToLoad, + }.Struct(), } } diff --git a/runtime/file.go b/runtime/file.go new file mode 100644 index 0000000000..c06a1115b3 --- /dev/null +++ b/runtime/file.go @@ -0,0 +1,112 @@ +package runtime + +import ( + "fmt" + "io" + "io/fs" + + "go.starlark.net/starlark" + "go.starlark.net/starlarkstruct" +) + +type File struct { + fsys fs.FS + path string +} + +func (f File) Struct() *starlarkstruct.Struct { + return starlarkstruct.FromStringDict(starlark.String("File"), starlark.StringDict{ + "path": starlark.String(f.path), + "readall": starlark.NewBuiltin("readall", f.readall), + }) +} + +func (f File) readall(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var mode starlark.String + if err := starlark.UnpackArgs("readall", args, kwargs, "mode?", &mode); err != nil { + return nil, err + } + + r, err := f.reader(string(mode)) + if err != nil { + return nil, err + } + defer r.Close() + + return r.read(thread, nil, nil, nil) +} + +func (f File) reader(mode string) (*Reader, error) { + var binaryMode bool + switch mode { + case "", "r", "rt": + binaryMode = false + + case "rb": + binaryMode = true + + default: + return nil, fmt.Errorf("unsupported mode: %s", mode) + } + + fl, err := f.fsys.Open(f.path) + if err != nil { + return nil, err + } + return &Reader{fl, binaryMode}, nil +} + +type Reader struct { + io.ReadCloser + binaryMode bool +} + +func (r Reader) Struct() *starlarkstruct.Struct { + return starlarkstruct.FromStringDict(starlark.String("Reader"), starlark.StringDict{ + "read": starlark.NewBuiltin("read", r.read), + "close": starlark.NewBuiltin("close", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + return nil, r.Close() + }), + }) +} + +// read reads the contents of the file. The Starlark signature is: +// +// read(size=-1) -> bytes +func (r Reader) read(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + starlarkSize := starlark.MakeInt(-1) + if err := starlark.UnpackArgs("read", args, kwargs, "size?", &starlarkSize); err != nil { + return nil, err + } + + var size int + if err := starlark.AsInt(starlarkSize, &size); err != nil { + return nil, fmt.Errorf("size is not an int") + } + + returnType := func(buf []byte) starlark.Value { + if r.binaryMode { + return starlark.Bytes(buf) + } else { + return starlark.String(buf) + } + } + + if size < 0 { + // read and return all bytes + buf, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + return returnType(buf), nil + } else { + // read and return size bytes + buf := make([]byte, size) + _, err := r.Read(buf) + if err != nil { + return nil, err + } + return returnType(buf), nil + } +} diff --git a/runtime/file_test.go b/runtime/file_test.go new file mode 100644 index 0000000000..4bd2bb6d1e --- /dev/null +++ b/runtime/file_test.go @@ -0,0 +1,39 @@ +package runtime + +import ( + "testing" + "testing/fstest" + + "github.com/stretchr/testify/require" +) + +func TestReadFile(t *testing.T) { + src := ` +load("hello.txt", hello = "file") + +def assert_eq(message, actual, expected): + if not expected == actual: + fail(message, "-", "expected", expected, "actual", actual) + +def test_readall(): + assert_eq("readall", hello.readall(), "hello world") + +def test_readall_binary(): + assert_eq("readall_binary", hello.readall("rb"), b"hello world") + +def main(): + pass + +` + + helloTxt := `hello world` + + vfs := &fstest.MapFS{ + "main.star": {Data: []byte(src)}, + "hello.txt": {Data: []byte(helloTxt)}, + } + + app, err := NewAppletFromFS("test_read_file", vfs) + require.NoError(t, err) + app.RunTests(t) +} diff --git a/server/loader/loader.go b/server/loader/loader.go index 32ae9322b1..103a157e45 100644 --- a/server/loader/loader.go +++ b/server/loader/loader.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/fs" "log" "time" @@ -18,7 +19,7 @@ import ( // Loader is a structure to provide applet loading when a file changes or on // demand. type Loader struct { - filename string + fs fs.FS fileChanges chan bool watch bool applet runtime.Applet @@ -42,7 +43,7 @@ type Update struct { // encoded WebP strings. If watch is enabled, both file changes and on demand // requests will send updates over the updatesChan. func NewLoader( - filename string, + fs fs.FS, watch bool, fileChanges chan bool, updatesChan chan Update, @@ -51,7 +52,7 @@ func NewLoader( ) (*Loader, error) { l := &Loader{ - filename: filename, + fs: fs, fileChanges: fileChanges, watch: watch, applet: runtime.Applet{}, @@ -69,7 +70,7 @@ func NewLoader( runtime.InitCache(cache) if !l.watch { - app, err := loadScript("app-id", l.filename) + app, err := loadScript("app-id", l.fs) l.markInitialLoadComplete() if err != nil { return nil, err @@ -106,7 +107,7 @@ func (l *Loader) Run() error { l.updatesChan <- up l.resultsChan <- up case <-l.fileChanges: - log.Printf("detected updates for %s, reloading\n", l.filename) + log.Println("detected updates, reloading") up := Update{} webp, err := l.loadApplet(config) @@ -156,7 +157,7 @@ func (l *Loader) CallSchemaHandler(ctx context.Context, handlerName, parameter s func (l *Loader) loadApplet(config map[string]string) (string, error) { if l.watch { - app, err := loadScript("app-id", l.filename) + app, err := loadScript("app-id", l.fs) l.markInitialLoadComplete() if err != nil { return "", err diff --git a/server/loader/script.go b/server/loader/script.go index 4a27f3ff97..88cc74b42f 100644 --- a/server/loader/script.go +++ b/server/loader/script.go @@ -1,19 +1,11 @@ -//go:build !js && !wasm - package loader import ( - "fmt" - "os" + "io/fs" "tidbyt.dev/pixlet/runtime" ) -func loadScript(appID string, filename string) (*runtime.Applet, error) { - src, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", filename, err) - } - - return runtime.NewApplet(appID, src) +func loadScript(appID string, fs fs.FS) (*runtime.Applet, error) { + return runtime.NewAppletFromFS(appID, fs) } diff --git a/server/loader/script_js.go b/server/loader/script_js.go deleted file mode 100644 index c07b9b4fa1..0000000000 --- a/server/loader/script_js.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build js && wasm - -package loader - -import ( - "fmt" - "io" - "net/http" - - "tidbyt.dev/pixlet/runtime" -) - -func loadScript(appID string, filename string) (*runtime.Applet, error) { - res, err := http.Get(filename) - if err != nil { - return nil, fmt.Errorf("failed to fetch file %s: %w", filename, err) - } - defer res.Body.Close() - - src, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", filename, err) - } - - return runtime.NewApplet(appID, src) -} diff --git a/server/server.go b/server/server.go index 165228775f..6ff5459674 100644 --- a/server/server.go +++ b/server/server.go @@ -2,35 +2,58 @@ package server import ( "fmt" + "io/fs" + "os" + "path/filepath" + "strings" "golang.org/x/sync/errgroup" "tidbyt.dev/pixlet/server/browser" "tidbyt.dev/pixlet/server/loader" - "tidbyt.dev/pixlet/server/watcher" + "tidbyt.dev/pixlet/tools" ) // Server provides functionality to serve Starlark over HTTP. It has // functionality to watch a file and hot reload the browser on changes. type Server struct { - watcher *watcher.Watcher + watcher *Watcher browser *browser.Browser loader *loader.Loader watch bool } // NewServer creates a new server initialized with the applet. -func NewServer(host string, port int, watch bool, filename string, maxDuration int, timeout int) (*Server, error) { +func NewServer(host string, port int, watch bool, path string, maxDuration int, timeout int) (*Server, error) { fileChanges := make(chan bool, 100) - w := watcher.NewWatcher(filename, fileChanges) + + // check if path exists, and whether it is a directory or a file + info, err := os.Stat(path) + if err != nil { + return nil, fmt.Errorf("failed to stat %s: %w", path, err) + } + + var fs fs.FS + var w *Watcher + if info.IsDir() { + fs = os.DirFS(path) + w = NewWatcher(path, fileChanges) + } else { + if !strings.HasSuffix(path, ".star") { + return nil, fmt.Errorf("script file must have suffix .star: %s", path) + } + + fs = tools.NewSingleFileFS(path) + w = NewWatcher(filepath.Dir(path), fileChanges) + } updatesChan := make(chan loader.Update, 100) - l, err := loader.NewLoader(filename, watch, fileChanges, updatesChan, maxDuration, timeout) + l, err := loader.NewLoader(fs, watch, fileChanges, updatesChan, maxDuration, timeout) if err != nil { return nil, err } addr := fmt.Sprintf("%s:%d", host, port) - b, err := browser.NewBrowser(addr, filename, watch, updatesChan, l) + b, err := browser.NewBrowser(addr, filepath.Base(path), watch, updatesChan, l) if err != nil { return nil, err } diff --git a/server/watcher/fs.go b/server/watcher.go similarity index 63% rename from server/watcher/fs.go rename to server/watcher.go index d7dec016c5..de50bbdb73 100644 --- a/server/watcher/fs.go +++ b/server/watcher.go @@ -1,14 +1,29 @@ -//go:build !js && !wasm - -package watcher +package server import ( "fmt" + "log" "path/filepath" + "strings" "github.com/fsnotify/fsnotify" ) +// Watcher is a structure to watch a file for changes and notify a channel. +type Watcher struct { + path string + fileChanges chan bool +} + +// NewWatcher instantiates a new watcher with the provided filename and changes +// channel. +func NewWatcher(filename string, fileChanges chan bool) *Watcher { + return &Watcher{ + path: filepath.FromSlash(filename), + fileChanges: fileChanges, + } +} + // Run starts the file watcher in a blocking fashion. This watches an entire // directory and only notifies the channel when the specified file is changed. // If there is an error, it's returned. It's up to the caller to respawn the @@ -27,7 +42,7 @@ func (w *Watcher) Run() error { } defer watcher.Close() - watcher.Add(filepath.Dir(w.filename)) + watcher.Add(w.path) for { select { @@ -35,7 +50,8 @@ func (w *Watcher) Run() error { if !ok { return fmt.Errorf("something is weird with the file watcher") } - if event.Name == w.filename && (event.Op&(fsnotify.Write|fsnotify.Create)) != 0 { + log.Println(event.Name) + if strings.HasPrefix(event.Name, w.path) && shouldNotify(event.Op) { w.fileChanges <- true } @@ -47,3 +63,9 @@ func (w *Watcher) Run() error { } } } + +func shouldNotify(op fsnotify.Op) bool { + // notify on all ops except for chmod, since that is discouraged + // in the fsnotify docs. + return op.Has(fsnotify.Write | fsnotify.Create | fsnotify.Remove | fsnotify.Rename) +} diff --git a/server/watcher/fs_js.go b/server/watcher/fs_js.go deleted file mode 100644 index 639c6caaa2..0000000000 --- a/server/watcher/fs_js.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build js && wasm - -package watcher - -import ( - "fmt" -) - -func (w *Watcher) Run() error { - return fmt.Errorf("file watching not supported in WASM") -} diff --git a/server/watcher/watcher.go b/server/watcher/watcher.go deleted file mode 100644 index 0090c41862..0000000000 --- a/server/watcher/watcher.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package watcher provides a simple mechanism to watch a file for changes. -package watcher - -import ( - "path/filepath" -) - -// Watcher is a structure to watch a file for changes and notify a channel. -type Watcher struct { - filename string - fileChanges chan bool -} - -// NewWatcher instantiates a new watcher with the provided filename and changes -// channel. -func NewWatcher(filename string, fileChanges chan bool) *Watcher { - return &Watcher{ - filename: filepath.FromSlash(filename), - fileChanges: fileChanges, - } -} diff --git a/starlarkutil/onexit.go b/starlarkutil/onexit.go new file mode 100644 index 0000000000..d3ee2a6cdc --- /dev/null +++ b/starlarkutil/onexit.go @@ -0,0 +1,27 @@ +package starlarkutil + +import "go.starlark.net/starlark" + +const ( + // ThreadOnExitKey is the key used to store functions that should be called + // when a thread exits. + ThreadOnExitKey = "tidbyt.dev/pixlet/runtime/on_exit" +) + +type threadOnExitFunc func() + +func AddOnExit(thread *starlark.Thread, fn threadOnExitFunc) { + if onExit, ok := thread.Local(ThreadOnExitKey).(*[]threadOnExitFunc); ok { + *onExit = append(*onExit, fn) + } else { + thread.SetLocal(ThreadOnExitKey, &[]threadOnExitFunc{fn}) + } +} + +func RunOnExitFuncs(thread *starlark.Thread) { + if onExit, ok := thread.Local(ThreadOnExitKey).(*[]threadOnExitFunc); ok { + for _, fn := range *onExit { + fn() + } + } +} diff --git a/tools/generator/generator.go b/tools/generator/generator.go index 132e41a3d3..39c3a3add6 100644 --- a/tools/generator/generator.go +++ b/tools/generator/generator.go @@ -83,12 +83,12 @@ func (g *Generator) RemoveApp(app *manifest.Manifest) error { } func (g *Generator) createDir(app *manifest.Manifest) error { - p := path.Join(g.root, appsDir, app.PackageName) + p := path.Join(g.root, appsDir, manifest.GenerateDirName(app.Name)) return os.MkdirAll(p, os.ModePerm) } func (g *Generator) removeDir(app *manifest.Manifest) error { - p := path.Join(g.root, appsDir, app.PackageName) + p := path.Join(g.root, appsDir, manifest.GenerateDirName(app.Name)) return os.RemoveAll(p) } @@ -96,7 +96,7 @@ func (g *Generator) writeManifest(app *manifest.Manifest) error { var p string switch g.appType { case Community, Internal: - p = path.Join(g.root, appsDir, app.PackageName, manifestName) + p = path.Join(g.root, appsDir, manifest.GenerateDirName(app.Name), manifestName) default: p = path.Join(g.root, manifestName) } @@ -111,12 +111,15 @@ func (g *Generator) writeManifest(app *manifest.Manifest) error { } func (g *Generator) generateStarlark(app *manifest.Manifest) (string, error) { + dir := manifest.GenerateDirName(app.Name) + fn := manifest.GenerateFileName(app.Name) + var p string switch g.appType { case Community, Internal: - p = path.Join(g.root, appsDir, app.PackageName, app.FileName) + p = path.Join(g.root, appsDir, dir, fn) default: - p = path.Join(g.root, app.FileName) + p = path.Join(g.root, fn) } file, err := os.Create(p) diff --git a/tools/singlefilefs.go b/tools/singlefilefs.go new file mode 100644 index 0000000000..87ab8e1f19 --- /dev/null +++ b/tools/singlefilefs.go @@ -0,0 +1,27 @@ +package tools + +import ( + "io/fs" + "os" + "path/filepath" +) + +type SingleFileFS struct { + Path string + baseFS fs.FS +} + +func NewSingleFileFS(filePath string) *SingleFileFS { + return &SingleFileFS{ + Path: filePath, + baseFS: os.DirFS(filepath.Dir(filePath)), + } +} + +func (sfs *SingleFileFS) Open(name string) (fs.File, error) { + if name != "." && name != filepath.Base(sfs.Path) { + return nil, fs.ErrNotExist + } + + return sfs.baseFS.Open(name) +} From 648ba9811fb47977cf885d3ee1064476bf7e7654 Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Wed, 24 Apr 2024 12:53:10 -0400 Subject: [PATCH 09/19] Clean up bundle API for loading bundles (#1049) This is mostly important for embedders like the Tidbyt backend. --- bundle/bundle.go | 10 +++++----- bundle/bundle_test.go | 2 +- cmd/private/bundle.go | 2 +- cmd/private/upload.go | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bundle/bundle.go b/bundle/bundle.go index fe777de1ee..74454b8344 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -34,7 +34,7 @@ type AppBundle struct { Source fs.FS } -func fromFS(fs fs.FS) (*AppBundle, error) { +func FromFS(fs fs.FS) (*AppBundle, error) { m, err := fs.Open(manifest.ManifestFileName) if err != nil { return nil, fmt.Errorf("could not open manifest: %w", err) @@ -53,10 +53,10 @@ func fromFS(fs fs.FS) (*AppBundle, error) { }, nil } -// InitFromPath translates a directory containing an app manifest and source +// FromDir translates a directory containing an app manifest and source // into an AppBundle. -func InitFromPath(dir string) (*AppBundle, error) { - return fromFS(os.DirFS(dir)) +func FromDir(dir string) (*AppBundle, error) { + return FromFS(os.DirFS(dir)) } // LoadBundle loads a compressed archive into an AppBundle. @@ -78,7 +78,7 @@ func LoadBundle(in io.Reader) (*AppBundle, error) { return nil, fmt.Errorf("creating tarfs: %w", err) } - return fromFS(fs) + return FromFS(fs) } // WriteBundleToPath is a helper to be able to write the bundle to a provided diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 307a0eb6b1..0f6a28059e 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -11,7 +11,7 @@ import ( func TestBundleWriteAndLoad(t *testing.T) { // Ensure we can load the bundle from an app. - ab, err := bundle.InitFromPath("testdata/testapp") + ab, err := bundle.FromDir("testdata/testapp") assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) assert.NotNil(t, ab.Source) diff --git a/cmd/private/bundle.go b/cmd/private/bundle.go index 7bdc1c6338..634e35e747 100644 --- a/cmd/private/bundle.go +++ b/cmd/private/bundle.go @@ -42,7 +42,7 @@ be a gzip compressed tar file that can be uploaded to Tidbyt for deployment.`, return fmt.Errorf("output must be a directory") } - ab, err := bundle.InitFromPath(bundleInput) + ab, err := bundle.FromDir(bundleInput) if err != nil { return fmt.Errorf("could not init bundle: %w", err) } diff --git a/cmd/private/upload.go b/cmd/private/upload.go index 7a52df06a8..72f68cbc7d 100644 --- a/cmd/private/upload.go +++ b/cmd/private/upload.go @@ -63,7 +63,7 @@ flags.`, // Create bundle buf := &bytes.Buffer{} - ab, err := bundle.InitFromPath(uploadDir) + ab, err := bundle.FromDir(uploadDir) if err != nil { return fmt.Errorf("could not init bundle: %w", err) } From 9ebdaacc9275f47100804b27ecc5ac2d9bcb34c8 Mon Sep 17 00:00:00 2001 From: Tom Curtis <105217023+dinosaursrarr@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:53:11 +0100 Subject: [PATCH 10/19] Add bsoup module (#853) Responding to request on forum: https://discuss.tidbyt.com/t/beautiful-soup-python-supported/5328/2 I can imagine you saying no, because this provides similar functionality to the existing html module. But I agree with the poster that this is a better API and I would prefer to be able to use it. --- docs/modules.md | 1 + go.mod | 1 + go.sum | 1 + runtime/applet.go | 4 ++++ runtime/applet_test.go | 3 +++ 5 files changed, 10 insertions(+) diff --git a/docs/modules.md b/docs/modules.md index a7d05446fd..9aad04657d 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -28,6 +28,7 @@ individual modules, please refer to the Starlib documentation. | Module | Description | | --- | --- | +| [`bsoup.star`](https://github.com/qri-io/starlib/blob/master/bsoup) | Beautiful Soup-like functions for HTML | | [`compress/gzip.star`](https://github.com/qri-io/starlib/blob/master/compress/gzip) | gzip decompressing | | [`compress/zipfile.star`](https://github.com/qri-io/starlib/blob/master/zipfile) | zip decompressing | | [`encoding/base64.star`](https://github.com/qri-io/starlib/tree/master/encoding/base64) | Base 64 encoding and decoding | diff --git a/go.mod b/go.mod index 485b35137f..38925cfdb2 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,7 @@ require ( github.com/cloudflare/circl v1.3.7 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect diff --git a/go.sum b/go.sum index af16291738..0bc7726ec4 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e h1:44fmjqDtdCiUNlSjJVp+w1AOs6na3Y6Ai0aIeseFjkI= github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= diff --git a/runtime/applet.go b/runtime/applet.go index c97995d07a..45f98e74e6 100644 --- a/runtime/applet.go +++ b/runtime/applet.go @@ -13,6 +13,7 @@ import ( starlibgzip "github.com/qri-io/starlib/compress/gzip" starlibbase64 "github.com/qri-io/starlib/encoding/base64" + starlibbsoup "github.com/qri-io/starlib/bsoup" starlibcsv "github.com/qri-io/starlib/encoding/csv" starlibhash "github.com/qri-io/starlib/hash" starlibhtml "github.com/qri-io/starlib/html" @@ -510,6 +511,9 @@ func (a *Applet) loadModule(thread *starlark.Thread, module string) (starlark.St case "xpath.star": return xpath.LoadXPathModule() + case "bsoup.star": + return starlibbsoup.LoadModule() + case "compress/gzip.star": return starlark.StringDict{ starlibgzip.Module.Name: starlibgzip.Module, diff --git a/runtime/applet_test.go b/runtime/applet_test.go index 6ba07d8af6..bcb121f99a 100644 --- a/runtime/applet_test.go +++ b/runtime/applet_test.go @@ -221,6 +221,7 @@ func TestModuleLoading(t *testing.T) { // Our basic set of modules can be imported src := ` load("render.star", "render") +load("bsoup.star", "bsoup") load("encoding/base64.star", "base64") load("encoding/json.star", "json") load("http.star", "http") @@ -245,6 +246,8 @@ def main(): fail("re broken") if time.parse_duration("10s").seconds != 10: fail("time broken") + if bsoup.parseHtml("

foo

").find("h1").get_text() != "foo": + fail("bsoup broken") return render.Root(child=render.Box()) ` app, err := NewApplet("test.star", []byte(src)) From 0ad59b9f01f462c4cdb87723a27f193eb682d3fc Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Wed, 24 Apr 2024 15:17:05 -0400 Subject: [PATCH 11/19] Provide file name in load errors (#1050) When loading a Starlark file fails with an error, provide the name of the file. Since apps can now contain multiple files, this is necessary for the error message to make sense. --- runtime/applet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/applet.go b/runtime/applet.go index 45f98e74e6..2bac67fe01 100644 --- a/runtime/applet.go +++ b/runtime/applet.go @@ -409,7 +409,7 @@ func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading .. Recursion: true, }, thread, - a.ID, + path.Join(a.ID, pathToLoad), src, predeclared, ) From 908343416fe40d12a32817dba3dd36a6b9e45981 Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Wed, 24 Apr 2024 15:34:36 -0400 Subject: [PATCH 12/19] bundle: Add WithoutRuntime write option (#1051) WithoutRuntime is a WriteOption that can be used to write the bundle without using the runtime to determine the files to include in the bundle. Instead, all files in the source FS will be included in the bundle. This is useful when writing a bundle that is known not to contain any unnecessary files, when loading and rewriting a bundle that was already tree-shaken, or when loading the entire runtime is not possible for performance or security reasons. --- bundle/bundle.go | 93 --------------------------- bundle/bundle_test.go | 39 ++++++++++++ bundle/write.go | 142 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 93 deletions(-) create mode 100644 bundle/write.go diff --git a/bundle/bundle.go b/bundle/bundle.go index 74454b8344..e06c130c3d 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -2,19 +2,16 @@ package bundle import ( - "archive/tar" "bytes" "compress/gzip" "fmt" "io" "io/fs" "os" - "path/filepath" "github.com/nlepage/go-tarfs" "tidbyt.dev/pixlet/manifest" - "tidbyt.dev/pixlet/runtime" ) const ( @@ -80,93 +77,3 @@ func LoadBundle(in io.Reader) (*AppBundle, error) { return FromFS(fs) } - -// WriteBundleToPath is a helper to be able to write the bundle to a provided -// directory. -func (b *AppBundle) WriteBundleToPath(dir string) error { - path := filepath.Join(dir, AppBundleName) - f, err := os.Create(path) - if err != nil { - return fmt.Errorf("could not create file for bundle: %w", err) - } - defer f.Close() - - return b.WriteBundle(f) -} - -// WriteBundle writes a compressed archive to the provided writer. -func (ab *AppBundle) WriteBundle(out io.Writer) error { - // we don't want to naively write the entire source FS to the tarball, - // since it could contain a lot of extraneous files. instead, run the - // applet and interrogate it for the files it needs to include in the - // bundle. - app, err := runtime.NewAppletFromFS(ab.Manifest.ID, ab.Source, runtime.WithPrintDisabled()) - if err != nil { - return fmt.Errorf("loading applet for bundling: %w", err) - } - bundleFiles := app.PathsForBundle() - - // Setup writers. - gzw := gzip.NewWriter(out) - defer gzw.Close() - - tw := tar.NewWriter(gzw) - defer tw.Close() - - // Write manifest. - buff := &bytes.Buffer{} - err = ab.Manifest.WriteManifest(buff) - if err != nil { - return fmt.Errorf("could not write manifest to buffer: %w", err) - } - b := buff.Bytes() - - hdr := &tar.Header{ - Name: manifest.ManifestFileName, - Mode: 0600, - Size: int64(len(b)), - } - err = tw.WriteHeader(hdr) - if err != nil { - return fmt.Errorf("could not write manifest header: %w", err) - } - _, err = tw.Write(b) - if err != nil { - return fmt.Errorf("could not write manifest to archive: %w", err) - } - - // write sources. - for _, path := range bundleFiles { - stat, err := fs.Stat(ab.Source, path) - if err != nil { - return fmt.Errorf("could not stat %s: %w", path, err) - } - - hdr, err := tar.FileInfoHeader(stat, "") - if err != nil { - return fmt.Errorf("creating header for %s: %w", path, err) - } - hdr.Name = filepath.ToSlash(path) - - err = tw.WriteHeader(hdr) - if err != nil { - return fmt.Errorf("writing header for %s: %w", path, err) - } - - if !stat.IsDir() { - file, err := ab.Source.Open(path) - if err != nil { - return fmt.Errorf("opening file %s: %w", path, err) - } - - written, err := io.Copy(tw, file) - if err != nil { - return fmt.Errorf("writing file %s: %w", path, err) - } else if written != stat.Size() { - return fmt.Errorf("did not write entire file %s: %w", path, err) - } - } - } - - return nil -} diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 0f6a28059e..0d35a6e97a 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -50,6 +50,45 @@ func TestBundleWriteAndLoad(t *testing.T) { _, err = newBun.Source.Open("unused.txt") assert.ErrorIs(t, err, os.ErrNotExist) } + +func TestBundleWriteAndLoadWithoutRuntime(t *testing.T) { + ab, err := bundle.FromDir("testdata/testapp") + assert.NoError(t, err) + assert.Equal(t, "test-app", ab.Manifest.ID) + assert.NotNil(t, ab.Source) + + // Create a temp directory. + dir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + + // Write bundle to the temp directory, without tree-shaking. + err = ab.WriteBundleToPath(dir, bundle.WithoutRuntime()) + assert.NoError(t, err) + + // Ensure we can load up the bundle just created. + path := filepath.Join(dir, bundle.AppBundleName) + f, err := os.Open(path) + assert.NoError(t, err) + defer f.Close() + newBun, err := bundle.LoadBundle(f) + assert.NoError(t, err) + assert.Equal(t, "test-app", newBun.Manifest.ID) + assert.NotNil(t, ab.Source) + + // Ensure the loaded bundle contains the files we expect. + filesExpected := []string{ + "manifest.yaml", + "test_app.star", + "test.txt", + "a_subdirectory/hi.jpg", + "unused.txt", + } + for _, file := range filesExpected { + _, err := newBun.Source.Open(file) + assert.NoError(t, err) + } +} + func TestLoadBundle(t *testing.T) { f, err := os.Open("testdata/bundle.tar.gz") assert.NoError(t, err) diff --git a/bundle/write.go b/bundle/write.go new file mode 100644 index 0000000000..ae1188b863 --- /dev/null +++ b/bundle/write.go @@ -0,0 +1,142 @@ +// Package bundle provides primitives for bundling apps for portability. +package bundle + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "slices" + + "tidbyt.dev/pixlet/manifest" + "tidbyt.dev/pixlet/runtime" +) + +type WriteOption interface{} + +type withoutRuntimeOption struct{} + +// WithoutRuntime is a WriteOption that can be used to write the bundle without +// using the runtime to determine the files to include in the bundle. Instead, +// all files in the source FS will be included in the bundle. +// +// This is useful when writing a bundle that is known not to contain any +// unnecessary files, when loading and rewriting a bundle that was already +// tree-shaken, or when loading the entire runtime is not possible for +// performance or security reasons. +func WithoutRuntime() WriteOption { + return withoutRuntimeOption{} +} + +// WriteBundleToPath is a helper to be able to write the bundle to a provided +// directory. +func (b *AppBundle) WriteBundleToPath(dir string, opts ...WriteOption) error { + path := filepath.Join(dir, AppBundleName) + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("could not create file for bundle: %w", err) + } + defer f.Close() + + return b.WriteBundle(f, opts...) +} + +// WriteBundle writes a compressed archive to the provided writer. +func (ab *AppBundle) WriteBundle(out io.Writer, opts ...WriteOption) error { + var bundleFiles []string + + if slices.Contains(opts, WithoutRuntime()) { + // we can't use the runtime to determine the files to include in the + // bundle, so we'll just include everything in the source FS. + err := fs.WalkDir(ab.Source, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("walking directory: %w", err) + } + if !d.IsDir() { + bundleFiles = append(bundleFiles, path) + } + return nil + }) + if err != nil { + return fmt.Errorf("walking source FS: %w", err) + } + } else { + // we don't want to naively write the entire source FS to the tarball, + // since it could contain a lot of extraneous files. instead, run the + // applet and interrogate it for the files it needs to include in the + // bundle. + app, err := runtime.NewAppletFromFS(ab.Manifest.ID, ab.Source, runtime.WithPrintDisabled()) + if err != nil { + return fmt.Errorf("loading applet for bundling: %w", err) + } + bundleFiles = app.PathsForBundle() + } + + // Setup writers. + gzw := gzip.NewWriter(out) + defer gzw.Close() + + tw := tar.NewWriter(gzw) + defer tw.Close() + + // Write manifest. + buff := &bytes.Buffer{} + err := ab.Manifest.WriteManifest(buff) + if err != nil { + return fmt.Errorf("could not write manifest to buffer: %w", err) + } + b := buff.Bytes() + + hdr := &tar.Header{ + Name: manifest.ManifestFileName, + Mode: 0600, + Size: int64(len(b)), + } + err = tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("could not write manifest header: %w", err) + } + _, err = tw.Write(b) + if err != nil { + return fmt.Errorf("could not write manifest to archive: %w", err) + } + + // write sources. + for _, path := range bundleFiles { + stat, err := fs.Stat(ab.Source, path) + if err != nil { + return fmt.Errorf("could not stat %s: %w", path, err) + } + + hdr, err := tar.FileInfoHeader(stat, "") + if err != nil { + return fmt.Errorf("creating header for %s: %w", path, err) + } + hdr.Name = filepath.ToSlash(path) + + err = tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("writing header for %s: %w", path, err) + } + + if !stat.IsDir() { + file, err := ab.Source.Open(path) + if err != nil { + return fmt.Errorf("opening file %s: %w", path, err) + } + + written, err := io.Copy(tw, file) + if err != nil { + return fmt.Errorf("writing file %s: %w", path, err) + } else if written != stat.Size() { + return fmt.Errorf("did not write entire file %s: %w", path, err) + } + } + } + + return nil +} From 452de9a4dd7c5cfc649f3e572db6cd19265577a8 Mon Sep 17 00:00:00 2001 From: Tom Curtis <105217023+dinosaursrarr@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:11:23 +0100 Subject: [PATCH 13/19] Remove TTL header from cache key (#1048) See [discussion on Discord](https://discord.com/channels/928484660785336380/928485908842426389/1231791730513412136). Multiple times, app developers have wanted to cache a piece of content until a particular time, rather than for a particular period. That requires setting ttl_seconds to a variable amount, calculated as the time remaining. Currently, the cache client won't support that. It generates the cache key from the entire request, which includes the TTL seconds in a header. This change removes that header before generating the key. That seems sensible because the key should be about the content and how it would be fetched, but the TTL is about what to do with the response. As a transitionary step, this writes to new TTL-less keys, but continues to look up keys with TTLs. Once all the old cache entries have expired, then we can stop looking up the old keys. --- runtime/httpcache.go | 6 ++++++ runtime/testdata/httpcache.star | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/runtime/httpcache.go b/runtime/httpcache.go index 6c77cef996..40502058ab 100644 --- a/runtime/httpcache.go +++ b/runtime/httpcache.go @@ -23,6 +23,7 @@ const ( HTTPTimeout = 5 * time.Second MaxResponseBytes = 20 * 1024 * 1024 // 20MB HTTPCachePrefix = "httpcache" + TTLHeader = "X-Tidbyt-Cache-Seconds" ) // Status codes that are cacheable as defined here: @@ -106,10 +107,15 @@ func (c *cacheClient) RoundTrip(req *http.Request) (*http.Response, error) { } func cacheKey(req *http.Request) (string, error) { + ttl := req.Header.Get(TTLHeader) + req.Header.Del(TTLHeader) r, err := httputil.DumpRequest(req, true) if err != nil { return "", fmt.Errorf("%s: %w", "failed to serialize request", err) } + if ttl != "" { + req.Header.Set(TTLHeader, ttl) + } h := sha256.Sum256(r) key := hex.EncodeToString(h[:]) diff --git a/runtime/testdata/httpcache.star b/runtime/testdata/httpcache.star index 0a1cbe18a5..5bb5bc8c86 100644 --- a/runtime/testdata/httpcache.star +++ b/runtime/testdata/httpcache.star @@ -18,7 +18,7 @@ def main(config): resp = http.get( url = "https://example.com", - ttl_seconds = 60, + ttl_seconds = 3, ) assert.eq(resp.headers.get("Tidbyt-Cache-Status"), "HIT") @@ -32,7 +32,7 @@ def main(config): url = "https://example.com", ttl_seconds = 60, ) - assert.eq(resp.headers.get("Tidbyt-Cache-Status"), "MISS") + assert.eq(resp.headers.get("Tidbyt-Cache-Status"), "HIT") resp = http.post( url = "https://example.com", From 56519a47f2f23a18b0f48411f1556cf7a1f909a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:55:02 -0400 Subject: [PATCH 14/19] chore(deps): update actions/checkout action to v4 (#886) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/main.yml | 6 +++--- .github/workflows/pull-request.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8c8d0fbfc7..5d5bdf24b7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f632823a4..af3a605fde 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 @@ -56,7 +56,7 @@ jobs: mingw-w64-x86_64-toolchain - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Linux dependencies if: matrix.os == 'ubuntu-22.04' @@ -151,7 +151,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: "0" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 0a971b1493..fd9629d78e 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -9,7 +9,7 @@ jobs: lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 @@ -54,7 +54,7 @@ jobs: mingw-w64-x86_64-toolchain - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Linux dependencies if: matrix.os == 'ubuntu-22.04' From 93e1b9e821118935c6e50b915e8acc203ea85565 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:55:12 -0400 Subject: [PATCH 15/19] chore(deps): update actions/setup-node action to v4 (#946) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- .github/workflows/pull-request.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af3a605fde..6707580a27 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: go-version: "1.22.2" - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "16" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index fd9629d78e..26374ffd50 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -37,7 +37,7 @@ jobs: go-version: "1.22.2" - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "16" From a225de90eadd6bddb13530ab4124d4697c06fe3f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:55:27 -0400 Subject: [PATCH 16/19] chore(deps): update goreleaser/goreleaser-action action to v5 (#892) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6707580a27..561f11bf0e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -167,7 +167,7 @@ jobs: path: build - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser-pro version: v1.12.3 From 66521fc4ac5de81f82f0fe4d699499e52e7575d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:03:46 +0000 Subject: [PATCH 17/19] fix(deps): update github.com/google/pprof digest to a892ee0 (#1052) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 38925cfdb2..6ccccc8e20 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/gitsight/go-vcsurl v1.0.1 github.com/go-git/go-git/v5 v5.12.0 github.com/go-playground/validator/v10 v10.15.1 - github.com/google/pprof v0.0.0-20240416155748-26353dc0451f + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 github.com/google/tink/go v1.7.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 diff --git a/go.sum b/go.sum index 0bc7726ec4..5db58c01df 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f h1:WpZiq8iqvGjJ3m3wzAVKL6+0vz7VkE79iSy9GII00II= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= From ff7ac7c6ac580502931037623c9cc45076d6acf8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:04:23 +0000 Subject: [PATCH 18/19] fix(deps): update react-router monorepo to v6.23.0 (#1053) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 52 +++++++++++++++++++++++------------------------ package.json | 4 ++-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9772b8ebac..0606583615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,8 +33,8 @@ "react-dom": "17.0.2", "react-easy-crop": "4.7.4", "react-redux": "8.1.3", - "react-router": "6.22.3", - "react-router-dom": "6.22.3", + "react-router": "6.23.0", + "react-router-dom": "6.23.0", "react-simple-oauth2-login": "0.5.4" }, "devDependencies": { @@ -2825,9 +2825,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.0.tgz", + "integrity": "sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==", "engines": { "node": ">=14.0.0" } @@ -7247,11 +7247,11 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.0.tgz", + "integrity": "sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==", "dependencies": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.0" }, "engines": { "node": ">=14.0.0" @@ -7261,12 +7261,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz", + "integrity": "sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==", "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "@remix-run/router": "1.16.0", + "react-router": "6.23.0" }, "engines": { "node": ">=14.0.0" @@ -10510,9 +10510,9 @@ } }, "@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==" + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.0.tgz", + "integrity": "sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==" }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "6.5.1", @@ -13801,20 +13801,20 @@ } }, "react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.0.tgz", + "integrity": "sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==", "requires": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.0" } }, "react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz", + "integrity": "sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==", "requires": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "@remix-run/router": "1.16.0", + "react-router": "6.23.0" } }, "react-simple-oauth2-login": { diff --git a/package.json b/package.json index 03a34beb12..58ab54e08a 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "react-dom": "17.0.2", "react-easy-crop": "4.7.4", "react-redux": "8.1.3", - "react-router": "6.22.3", - "react-router-dom": "6.22.3", + "react-router": "6.23.0", + "react-router-dom": "6.23.0", "react-simple-oauth2-login": "0.5.4" } } From 186eba99a1da83bdbdb3715e862092df40a9ab86 Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Thu, 25 Apr 2024 17:17:34 -0400 Subject: [PATCH 19/19] schema: Remove `dive` tag on SchemaField.Visibility (#1058) This tag only makes sense on slices and maps. It causes a panic in `go-playground/validator` v10.19.0. --- schema/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/schema.go b/schema/schema.go index 63527edf30..122cd46c2d 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -42,7 +42,7 @@ type SchemaField struct { Visibility *SchemaVisibility `json:"visibility,omitempty" validate:"omitempty,dive"` Default string `json:"default,omitempty" validate:"required_for=dropdown onoff radio"` - Options []SchemaOption `json:"options,omitempty" validate:"required_for=dropdown radio,dive"` + Options []SchemaOption `json:"options,omitempty" validate:"required_for=dropdown radio"` Palette []string `json:"palette,omitempty"` Source string `json:"source,omitempty" validate:"required_for=generated"`