From 73dc08d39000d42d01cc90961e1fd43840c33151 Mon Sep 17 00:00:00 2001 From: Arthur Fontaine <0arthur.fontaine@gmail.com> Date: Fri, 17 Nov 2023 02:49:53 +0100 Subject: [PATCH 1/3] chore(Alakazam): create nuxt-ngrok module to open a tunnel in dev environment --- apps/alakazam/package.json | 2 + apps/alakazam/src/modules/ngrok.ts | 47 +++++++ pnpm-lock.yaml | 210 ++++++++++++++++++++++++++++- 3 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 apps/alakazam/src/modules/ngrok.ts diff --git a/apps/alakazam/package.json b/apps/alakazam/package.json index 0fc83ad..6362ca1 100644 --- a/apps/alakazam/package.json +++ b/apps/alakazam/package.json @@ -19,9 +19,11 @@ "@octokit/rest": "^20.0.2", "@pandacss/dev": "^0.14.0", "@whitebird/ui": "workspace:*", + "consola": "^3.2.3", "edgedb": "^1.4.1", "eslint-plugin-vue": "^9.17.0", "glob": "^10.3.4", + "ngrok": "4.3.3", "npm-run-all": "^4.1.5", "nuxt": "^3.7.1", "nuxt-auth-utils": "^0.0.5", diff --git a/apps/alakazam/src/modules/ngrok.ts b/apps/alakazam/src/modules/ngrok.ts new file mode 100644 index 0000000..2a97aef --- /dev/null +++ b/apps/alakazam/src/modules/ngrok.ts @@ -0,0 +1,47 @@ +import { defineNuxtModule } from '@nuxt/kit' +import { colors } from 'consola/utils' +import ngrok from 'ngrok' + +const CONFIG_KEY = 'ngrok' + +export default defineNuxtModule({ + meta: { + name: 'nuxt-ngrok', + configKey: CONFIG_KEY, + }, + async setup(resolvedOptions: Partial, nuxt) { + if (nuxt.options.dev === false) { + return + } + + const ngrokOptions: ngrok.Ngrok.Options = { + addr: nuxt.options.devServer.port, + ...resolvedOptions, + } + const url = await ngrok.connect(ngrokOptions) + + nuxt.options.runtimeConfig[CONFIG_KEY] = { + url, + } + + nuxt.addHooks({ + 'devtools:initialized'() { + console.log(String.prototype.concat( + colors.blue(` ➜ Tunnel: `), + colors.underline(colors.cyan(url)), + )) + }, + 'app:resolve'() { + if (!ngrokOptions.auth) { + console.warn( + `ngrok tunnel is exposed to the public without password protection! Consider setting the ${colors.bold(`${CONFIG_KEY}.auth`)} option.`, + ) + } + }, + 'close'() { + ngrok.disconnect() + ngrok.kill() + } + }) + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef6c337..32f58d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: '@whitebird/ui': specifier: workspace:* version: link:../whitebird-ui + consola: + specifier: ^3.2.3 + version: 3.2.3 edgedb: specifier: ^1.4.1 version: 1.4.1 @@ -83,6 +86,9 @@ importers: glob: specifier: ^10.3.4 version: 10.3.7 + ngrok: + specifier: 4.3.3 + version: 4.3.3 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -984,7 +990,7 @@ packages: astro: ^2.5.0 dependencies: '@astrojs/prism': 2.1.2 - astro: 2.9.6(@types/node@18.18.0) + astro: 2.9.6(@types/node@20.4.7) github-slugger: 1.5.0 import-meta-resolve: 2.2.2 rehype-raw: 6.1.1 @@ -5706,6 +5712,11 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true + /@sindresorhus/is@4.6.0: + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + dev: true + /@storybook/addon-actions@7.5.1(@types/react-dom@18.2.7)(@types/react@18.2.22)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GieD3ru6EslKvwol1cE4lvszQCLB/AkQdnLofnqy1nnYso+hRxmPAw9/O+pWfpUBFdjXsQ7GX09+wEUpOJzepw==} peerDependencies: @@ -6701,6 +6712,13 @@ packages: dependencies: tslib: 2.6.2 + /@szmarczak/http-timer@4.0.6: + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + dependencies: + defer-to-connect: 2.0.1 + dev: true + /@testing-library/dom@9.3.3: resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==} engines: {node: '>=14'} @@ -6806,6 +6824,15 @@ packages: '@types/connect': 3.4.37 '@types/node': 18.18.0 + /@types/cacheable-request@6.0.3: + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + dependencies: + '@types/http-cache-semantics': 4.0.4 + '@types/keyv': 3.1.4 + '@types/node': 18.18.0 + '@types/responselike': 1.0.3 + dev: true + /@types/chai-subset@1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: @@ -6914,6 +6941,10 @@ packages: dependencies: '@types/unist': 2.0.8 + /@types/http-cache-semantics@4.0.4: + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + dev: true + /@types/http-errors@2.0.3: resolution: {integrity: sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==} @@ -6963,6 +6994,12 @@ packages: /@types/json5@0.0.30: resolution: {integrity: sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==} + /@types/keyv@3.1.4: + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + dependencies: + '@types/node': 18.18.0 + dev: true + /@types/lodash@4.14.199: resolution: {integrity: sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==} @@ -7024,6 +7061,10 @@ packages: /@types/node@20.4.7: resolution: {integrity: sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==} + /@types/node@8.10.66: + resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} + dev: true + /@types/normalize-package-data@2.4.2: resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} @@ -7061,6 +7102,12 @@ packages: /@types/resolve@1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + /@types/responselike@1.0.3: + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + dependencies: + '@types/node': 18.18.0 + dev: true + /@types/scheduler@0.16.4: resolution: {integrity: sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==} @@ -7106,6 +7153,14 @@ packages: '@types/yargs-parser': 21.0.1 dev: true + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + requiresBuild: true + dependencies: + '@types/node': 18.18.0 + dev: true + optional: true + /@typescript-eslint/eslint-plugin@6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.46.0)(typescript@5.2.2): resolution: {integrity: sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8920,6 +8975,7 @@ packages: - sugarss - supports-color - terser + dev: false /astro@2.9.6(@types/node@20.4.7): resolution: {integrity: sha512-yvbZQ6YOWYLejyQ4nAcgZLDYQ34enQJ5/LWlXKtUzXzpsUHB75vJMj+XmEq5Q2eVlZOlQrp0lU+nhSRGaOsOUQ==} @@ -8998,7 +9054,6 @@ packages: - sugarss - supports-color - terser - dev: true /async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} @@ -9416,6 +9471,24 @@ packages: unique-filename: 3.0.0 dev: true + /cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + dev: true + + /cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.1.1 + keyv: 4.5.3 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + dev: true + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -9676,6 +9749,12 @@ packages: shallow-clone: 3.0.1 dev: true + /clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + dependencies: + mimic-response: 1.0.1 + dev: true + /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -10301,6 +10380,11 @@ packages: dependencies: clone: 1.0.4 + /defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + dev: true + /define-data-property@1.1.0: resolution: {integrity: sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==} engines: {node: '>= 0.4'} @@ -11726,6 +11810,20 @@ packages: - supports-color dev: true + /extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.4 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + dev: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -12071,6 +12169,13 @@ packages: engines: {node: '>=8'} dev: true + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: true + /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -12318,6 +12423,23 @@ packages: dependencies: get-intrinsic: 1.2.1 + /got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + dev: true + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -12543,6 +12665,12 @@ packages: lru-cache: 10.0.1 dev: true + /hpagent@0.1.2: + resolution: {integrity: sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==} + requiresBuild: true + dev: true + optional: true + /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true @@ -12615,6 +12743,14 @@ packages: engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} dev: true + /http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + dev: true + /https-proxy-agent@2.2.4: resolution: {integrity: sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==} engines: {node: '>= 4.5.0'} @@ -13714,6 +13850,10 @@ packages: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true + /lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: true + /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true @@ -13815,6 +13955,11 @@ packages: get-func-name: 2.0.0 dev: true + /lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + dev: true + /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} @@ -14451,6 +14596,11 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + /mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + dev: true + /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -14752,6 +14902,24 @@ packages: dependencies: typescript: 5.2.2 + /ngrok@4.3.3: + resolution: {integrity: sha512-a2KApnkiG5urRxBPdDf76nNBQTnNNWXU0nXw0SsqsPI+Kmt2lGf9TdVYpYrHMnC+T9KhcNSWjCpWqBgC6QcFvw==} + engines: {node: '>=10.19.0 <14 || >=14.2'} + hasBin: true + requiresBuild: true + dependencies: + '@types/node': 8.10.66 + extract-zip: 2.0.1 + got: 11.8.6 + lodash.clonedeep: 4.5.0 + uuid: 8.3.2 + yaml: 1.10.2 + optionalDependencies: + hpagent: 0.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} dev: true @@ -14988,6 +15156,11 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + /normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + dev: true + /npm-bundled@3.0.0: resolution: {integrity: sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -15443,6 +15616,11 @@ packages: - debug dev: true + /p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + dev: true + /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -16435,6 +16613,11 @@ packages: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: true + /radix3@1.1.0: resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==} dev: true @@ -16936,6 +17119,10 @@ packages: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: false + /resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -16971,6 +17158,12 @@ packages: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + /responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + dependencies: + lowercase-keys: 2.0.0 + dev: true + /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -18950,6 +19143,11 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: true + /uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -19310,7 +19508,6 @@ packages: rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 - dev: true /vitefu@0.2.4(vite@4.4.9): resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} @@ -19331,7 +19528,7 @@ packages: vite: optional: true dependencies: - vite: 4.5.0(@types/node@18.18.0) + vite: 4.5.0(@types/node@20.4.7) /vitest@0.34.5: resolution: {integrity: sha512-CPI68mmnr2DThSB3frSuE5RLm9wo5wU4fbDrDwWQQB1CWgq9jQVoQwnQSzYAjdoBOPoH2UtXpOgHVge/uScfZg==} @@ -19895,6 +20092,11 @@ packages: yaml: 2.3.2 dev: true + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + /yaml@2.3.2: resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} engines: {node: '>= 14'} From 8c7d5c14722cc5488b68602adb29894398a4e54b Mon Sep 17 00:00:00 2001 From: Arthur Fontaine <0arthur.fontaine@gmail.com> Date: Fri, 17 Nov 2023 03:10:43 +0100 Subject: [PATCH 2/3] feat(Alakazam): add webhook to the project's github repository when change the repo --- apps/alakazam/nuxt.config.ts | 12 ++- .../composables/use-github-repositories.ts | 0 .../server/api/project/edit-project.post.ts | 39 +++++++-- .../server/handlers/project/edit-project.ts | 45 ++++++++--- .../repository/add-webhook-to-repository.ts | 80 +++++++++++++++++++ 5 files changed, 158 insertions(+), 18 deletions(-) rename apps/alakazam/src/{shared => features/project}/composables/use-github-repositories.ts (100%) create mode 100644 apps/alakazam/src/server/handlers/repository/add-webhook-to-repository.ts diff --git a/apps/alakazam/nuxt.config.ts b/apps/alakazam/nuxt.config.ts index 1ba96dd..ca8d5d1 100644 --- a/apps/alakazam/nuxt.config.ts +++ b/apps/alakazam/nuxt.config.ts @@ -60,7 +60,13 @@ export default defineNuxtConfig({ github: { clientId: process.env.NUXT_OAUTH_GITHUB_CLIENT_ID, clientSecret: process.env.NUXT_OAUTH_GITHUB_CLIENT_SECRET, - } - } - } + }, + }, + github: { + webhookSecret: process.env.NUXT_GITHUB_WEBHOOK_SECRET, + webhookUrl: process.env.NODE_ENV === 'development' + ? undefined + : process.env.NUXT_GITHUB_WEBHOOK_URL, + }, + }, }) diff --git a/apps/alakazam/src/shared/composables/use-github-repositories.ts b/apps/alakazam/src/features/project/composables/use-github-repositories.ts similarity index 100% rename from apps/alakazam/src/shared/composables/use-github-repositories.ts rename to apps/alakazam/src/features/project/composables/use-github-repositories.ts diff --git a/apps/alakazam/src/server/api/project/edit-project.post.ts b/apps/alakazam/src/server/api/project/edit-project.post.ts index 10365d5..c449b2a 100644 --- a/apps/alakazam/src/server/api/project/edit-project.post.ts +++ b/apps/alakazam/src/server/api/project/edit-project.post.ts @@ -11,14 +11,41 @@ const EditProjectSchema = valibot.object({ export default defineEventHandler(async (event) => { const session = await getUserSession(event) + const configuration = useRuntimeConfig() + const body = await readBody(event) const parsedBody = valibot.parse(EditProjectSchema, body) - return await editProject({ - userId: session.user.id, - projectId: parsedBody.project.id, - projectName: parsedBody.project.name, - repositoryUrl: parsedBody.project.repositoryUrl, - }) + const url = new URL( + configuration.github.webhookUrl !== undefined && configuration.github.webhookUrl !== '' + ? configuration.github.webhookUrl + : configuration.ngrok.url + ) + const webhookUrl = `${url.origin}/api/handle-github-webhook` + + let editProjectParameters: Parameters[0] = { + user: { id: session.user.id }, + project: { + id: parsedBody.project.id, + name: parsedBody.project.name, + }, + } + + if (parsedBody.project.repositoryUrl) { + editProjectParameters = { + ...editProjectParameters, + repository: { + url: parsedBody.project.repositoryUrl, + }, + webhook: { + url: webhookUrl, + secret: configuration.github.webhookSecret, + events: ['push'], + }, + githubAccessToken: session.user.token, + } + } + + return await editProject(editProjectParameters) }) diff --git a/apps/alakazam/src/server/handlers/project/edit-project.ts b/apps/alakazam/src/server/handlers/project/edit-project.ts index eea2bd8..bf10196 100644 --- a/apps/alakazam/src/server/handlers/project/edit-project.ts +++ b/apps/alakazam/src/server/handlers/project/edit-project.ts @@ -1,21 +1,35 @@ +import { addWebhookToRepository } from "../repository/add-webhook-to-repository" import { getProject } from "./get-project" export const editProject = async ( - { userId, projectId, projectName, repositoryUrl }: - { userId: string, projectId: string, projectName?: string, repositoryUrl?: string }, + { user, project, repository, webhook, githubAccessToken }: { + user: { id: string } + project: { id: string, name?: string, repositoryUrl?: string } + repository?: { url: string } + } & ( + { + webhook?: never + githubAccessToken?: never + } + | { + webhook?: Parameters[1]['webhook'] + githubAccessToken: string + repository: { url: string } + } + ), ) => { - if (await getProject({ userId, projectId }) === null) { + if (await getProject({ userId: user.id, projectId: project.id }) === null) { throw new Error('User is not a member of the organization') } - return database + let updateDatabasePromise = database .update( database.Project, (databaseProject) => ({ - filter: database.op(databaseProject.id, '=', database.uuid(projectId)), + filter: database.op(databaseProject.id, '=', database.uuid(project.id)), set: { - ...(projectName !== undefined ? { name: projectName } : {}), - ...(repositoryUrl !== undefined ? { + ...project.name !== undefined && { name: project.name }, + ...repository?.url !== undefined && { sources: { '+=': database.insert( database.ProjectSource, @@ -23,15 +37,28 @@ export const editProject = async ( githubRepository: database.insert( database.GitHubRepository, { - url: repositoryUrl, + url: repository.url, }, ), }, ) } - } : {}), + }, }, }), ) .run(database.client) + + if (webhook !== undefined && repository !== undefined) { + updateDatabasePromise = updateDatabasePromise.then(async (d) => { + await addWebhookToRepository(githubAccessToken, { + repository: { url: repository.url }, + webhook, + }) + + return d + }) + } + + return updateDatabasePromise } diff --git a/apps/alakazam/src/server/handlers/repository/add-webhook-to-repository.ts b/apps/alakazam/src/server/handlers/repository/add-webhook-to-repository.ts new file mode 100644 index 0000000..0fbd040 --- /dev/null +++ b/apps/alakazam/src/server/handlers/repository/add-webhook-to-repository.ts @@ -0,0 +1,80 @@ +import { Octokit } from "@octokit/rest"; + +export const addWebhookToRepository = async ( + githubAccessToken: string, + options: { + repository: ( + |{ + owner: string + name: string + } + |{ + url: string + } + ), + webhook: { + url: string + secret: string + events: string[], + comment?: string + } + } +) => { + const octokit = new Octokit({ + auth: githubAccessToken, + }) + + if ('url' in options.repository) { + const { owner, name } = /^https:\/\/github.com\/(?[\w-]+)\/(?[\w-]+)$/.exec(options.repository.url)?.groups ?? {} + + if (owner === undefined || name === undefined) { + throw new Error('Invalid repository URL') + } + + options.repository = { + owner, + name, + } + } + + const repo = await octokit.repos.get({ + owner: options.repository.owner, + repo: options.repository.name, + }) + + if (repo.data.permissions === undefined || repo.data.permissions.admin !== true) { + throw new Error('User does not have admin permissions on this repository') + } + + const webhookUrlQuery = options.webhook.events.reduce( + (query, event) => { + query.append('webhook_events', event) + return query + }, + new URLSearchParams({ + ...(options.webhook.comment ? + { webhook_events: options.webhook.comment } : + {}) + }) + ) + + const webhookUrl = new URL(options.webhook.url) + webhookUrl.search = webhookUrlQuery.toString() + + octokit.repos.createWebhook({ + owner: options.repository.owner, + repo: options.repository.name, + name: 'web', + active: true, + events: options.webhook.events, + config: { + url: webhookUrl.toString(), + content_type: 'json', + secret: options.webhook.secret, + insecure_ssl: process.env.NODE_ENV === 'development' ? Number(true) : Number(false), + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + }, + }) +} From 579fc9eca6f2aefb60c4b55f492f658e32b23e56 Mon Sep 17 00:00:00 2001 From: Arthur Fontaine <0arthur.fontaine@gmail.com> Date: Fri, 17 Nov 2023 21:59:39 +0100 Subject: [PATCH 3/3] feat(Alakazam): create /api/handle-github-component route without implement it --- .../handle-github-webhook.post.ts | 37 +++++++++++++++++++ .../component-generation/update-components.ts | 9 +++++ 2 files changed, 46 insertions(+) create mode 100644 apps/alakazam/src/server/api/github-webhook/handle-github-webhook.post.ts create mode 100644 apps/alakazam/src/server/handlers/component-generation/update-components.ts diff --git a/apps/alakazam/src/server/api/github-webhook/handle-github-webhook.post.ts b/apps/alakazam/src/server/api/github-webhook/handle-github-webhook.post.ts new file mode 100644 index 0000000..0d701c2 --- /dev/null +++ b/apps/alakazam/src/server/api/github-webhook/handle-github-webhook.post.ts @@ -0,0 +1,37 @@ +import * as valibot from "valibot" + +import { updateComponents } from "~/server/handlers/component-generation/update-components" + +export default defineEventHandler(async (event) => { + const githubEventName = event.headers.get("X-GitHub-Event") + + switch (githubEventName) { + case "push": { + const body = await readBody(event) + const parsedBody = valibot.parse(valibot.object({ + repository: valibot.object({ + id: valibot.string(), + full_name: valibot.string(), + html_url: valibot.string(), + }), + }), body) + + return await updateComponents({ + repository: { + id: parsedBody.repository.id, + name: parsedBody.repository.full_name, + url: parsedBody.repository.html_url, + }, + }) + } + case "ping": { + return "pong" + } + default: { + throw createError({ + statusCode: 400, + statusMessage: `Invalid GitHub event: ${githubEventName}` + }) + } + } +}) diff --git a/apps/alakazam/src/server/handlers/component-generation/update-components.ts b/apps/alakazam/src/server/handlers/component-generation/update-components.ts new file mode 100644 index 0000000..2c6219e --- /dev/null +++ b/apps/alakazam/src/server/handlers/component-generation/update-components.ts @@ -0,0 +1,9 @@ +export const updateComponents = async (params: { + repository: { + id: string + name: string + url: string + } +}) => { + // TODO: Implement +}