mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-21 21:49:51 +01:00
feat(apps): transpile via django (#18201)
This commit is contained in:
parent
2ce7ea6b0c
commit
06e993818d
@ -34,3 +34,5 @@
|
||||
!share/GeoLite2-City.mmdb
|
||||
!hogvm/python
|
||||
!unit.json
|
||||
!plugin-transpiler/src
|
||||
!plugin-transpiler/*.*
|
||||
|
18
.github/actions/run-backend-tests/action.yml
vendored
18
.github/actions/run-backend-tests/action.yml
vendored
@ -62,6 +62,24 @@ runs:
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.x.x
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
|
||||
- name: Install plugin-transpiler
|
||||
shell: bash
|
||||
run: |
|
||||
cd plugin-transpiler
|
||||
pnpm install
|
||||
pnpm run build
|
||||
|
||||
- uses: syphar/restore-virtualenv@v1
|
||||
id: cache-backend-tests
|
||||
with:
|
||||
|
1
.github/workflows/ci-backend.yml
vendored
1
.github/workflows/ci-backend.yml
vendored
@ -68,6 +68,7 @@ jobs:
|
||||
- mypy.ini
|
||||
- pytest.ini
|
||||
- frontend/src/queries/schema.json # Used for generating schema.py
|
||||
- plugin-transpiler/src # Used for transpiling plugins
|
||||
# Make sure we run if someone is explicitly change the workflow
|
||||
- .github/workflows/ci-backend.yml
|
||||
- .github/actions/run-backend-tests/action.yml
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -55,3 +55,4 @@ gen/
|
||||
upgrade/
|
||||
hogvm/typescript/dist
|
||||
.wokeignore
|
||||
plugin-transpiler/dist
|
||||
|
3
plugin-transpiler/README.md
Normal file
3
plugin-transpiler/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
## Plugin Transpiler
|
||||
|
||||
This project transpiles frontend plugins and site apps.
|
13
plugin-transpiler/build.mjs
Executable file
13
plugin-transpiler/build.mjs
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
import * as esbuild from 'esbuild'
|
||||
;(async function build() {
|
||||
let result = await esbuild.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
bundle: true,
|
||||
outdir: 'dist',
|
||||
})
|
||||
if (!result.errors.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Build succeeded')
|
||||
}
|
||||
})()
|
21
plugin-transpiler/package.json
Normal file
21
plugin-transpiler/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "plugin-transpiler",
|
||||
"version": "1.0.0",
|
||||
"description": "Transpiles site apps TSX to browser JS via stdin/stdout",
|
||||
"main": "transpile.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc -b && node build.mjs",
|
||||
"start:dist": "node dist/index.js",
|
||||
"start": "npm run build && npm run start:dist"
|
||||
},
|
||||
"author": "PostHog Inc.",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/standalone": "^7.23.2",
|
||||
"@types/babel__standalone": "^7.1.6",
|
||||
"@types/node": "^20.8.9",
|
||||
"esbuild": "^0.19.5",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
340
plugin-transpiler/pnpm-lock.yaml
Normal file
340
plugin-transpiler/pnpm-lock.yaml
Normal file
@ -0,0 +1,340 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
devDependencies:
|
||||
'@babel/standalone':
|
||||
specifier: ^7.23.2
|
||||
version: 7.23.2
|
||||
'@types/babel__standalone':
|
||||
specifier: ^7.1.6
|
||||
version: 7.1.6
|
||||
'@types/node':
|
||||
specifier: ^20.8.9
|
||||
version: 20.8.9
|
||||
esbuild:
|
||||
specifier: ^0.19.5
|
||||
version: 0.19.5
|
||||
typescript:
|
||||
specifier: ^5.2.2
|
||||
version: 5.2.2
|
||||
|
||||
packages:
|
||||
|
||||
/@babel/helper-string-parser@7.22.5:
|
||||
resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/helper-validator-identifier@7.22.20:
|
||||
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.23.0:
|
||||
resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.23.0
|
||||
dev: true
|
||||
|
||||
/@babel/standalone@7.23.2:
|
||||
resolution: {integrity: sha512-VJNw7OS26JvB6rE9XpbT6uQeQIEBWU5eeHGS4VR/+/4ZoKdLBXLcy66ZVJ/9IBkK1RMp8B0cohvhzdKWtJAGmg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/types@7.23.0:
|
||||
resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.22.5
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@esbuild/android-arm64@0.19.5:
|
||||
resolution: {integrity: sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/android-arm@0.19.5:
|
||||
resolution: {integrity: sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/android-x64@0.19.5:
|
||||
resolution: {integrity: sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/darwin-arm64@0.19.5:
|
||||
resolution: {integrity: sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/darwin-x64@0.19.5:
|
||||
resolution: {integrity: sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/freebsd-arm64@0.19.5:
|
||||
resolution: {integrity: sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/freebsd-x64@0.19.5:
|
||||
resolution: {integrity: sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-arm64@0.19.5:
|
||||
resolution: {integrity: sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-arm@0.19.5:
|
||||
resolution: {integrity: sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-ia32@0.19.5:
|
||||
resolution: {integrity: sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-loong64@0.19.5:
|
||||
resolution: {integrity: sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-mips64el@0.19.5:
|
||||
resolution: {integrity: sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-ppc64@0.19.5:
|
||||
resolution: {integrity: sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-riscv64@0.19.5:
|
||||
resolution: {integrity: sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-s390x@0.19.5:
|
||||
resolution: {integrity: sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-x64@0.19.5:
|
||||
resolution: {integrity: sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/netbsd-x64@0.19.5:
|
||||
resolution: {integrity: sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/openbsd-x64@0.19.5:
|
||||
resolution: {integrity: sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/sunos-x64@0.19.5:
|
||||
resolution: {integrity: sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/win32-arm64@0.19.5:
|
||||
resolution: {integrity: sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/win32-ia32@0.19.5:
|
||||
resolution: {integrity: sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/win32-x64@0.19.5:
|
||||
resolution: {integrity: sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@types/babel__core@7.20.3:
|
||||
resolution: {integrity: sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.0
|
||||
'@babel/types': 7.23.0
|
||||
'@types/babel__generator': 7.6.6
|
||||
'@types/babel__template': 7.4.3
|
||||
'@types/babel__traverse': 7.20.3
|
||||
dev: true
|
||||
|
||||
/@types/babel__generator@7.6.6:
|
||||
resolution: {integrity: sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.0
|
||||
dev: true
|
||||
|
||||
/@types/babel__standalone@7.1.6:
|
||||
resolution: {integrity: sha512-JRVq2H02irW6GDxkHepsdZVjor87SZo4eDsxoBtSbswf7HWtVYNxCmJDBkJbFNFWGOuzsxATZV8xZ2PI9sBiIw==}
|
||||
dependencies:
|
||||
'@types/babel__core': 7.20.3
|
||||
dev: true
|
||||
|
||||
/@types/babel__template@7.4.3:
|
||||
resolution: {integrity: sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.0
|
||||
'@babel/types': 7.23.0
|
||||
dev: true
|
||||
|
||||
/@types/babel__traverse@7.20.3:
|
||||
resolution: {integrity: sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.0
|
||||
dev: true
|
||||
|
||||
/@types/node@20.8.9:
|
||||
resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/esbuild@0.19.5:
|
||||
resolution: {integrity: sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
'@esbuild/android-arm': 0.19.5
|
||||
'@esbuild/android-arm64': 0.19.5
|
||||
'@esbuild/android-x64': 0.19.5
|
||||
'@esbuild/darwin-arm64': 0.19.5
|
||||
'@esbuild/darwin-x64': 0.19.5
|
||||
'@esbuild/freebsd-arm64': 0.19.5
|
||||
'@esbuild/freebsd-x64': 0.19.5
|
||||
'@esbuild/linux-arm': 0.19.5
|
||||
'@esbuild/linux-arm64': 0.19.5
|
||||
'@esbuild/linux-ia32': 0.19.5
|
||||
'@esbuild/linux-loong64': 0.19.5
|
||||
'@esbuild/linux-mips64el': 0.19.5
|
||||
'@esbuild/linux-ppc64': 0.19.5
|
||||
'@esbuild/linux-riscv64': 0.19.5
|
||||
'@esbuild/linux-s390x': 0.19.5
|
||||
'@esbuild/linux-x64': 0.19.5
|
||||
'@esbuild/netbsd-x64': 0.19.5
|
||||
'@esbuild/openbsd-x64': 0.19.5
|
||||
'@esbuild/sunos-x64': 0.19.5
|
||||
'@esbuild/win32-arm64': 0.19.5
|
||||
'@esbuild/win32-ia32': 0.19.5
|
||||
'@esbuild/win32-x64': 0.19.5
|
||||
dev: true
|
||||
|
||||
/to-fast-properties@2.0.0:
|
||||
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/typescript@5.2.2:
|
||||
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
dev: true
|
46
plugin-transpiler/src/index.ts
Normal file
46
plugin-transpiler/src/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { transform } from '@babel/standalone'
|
||||
import { presets } from './presets'
|
||||
|
||||
process.stdin.setEncoding('utf8')
|
||||
|
||||
let type: 'site' | 'frontend' = 'site'
|
||||
|
||||
for (let i = 2; i < process.argv.length; i++) {
|
||||
const arg = process.argv[i]
|
||||
if (arg === '--type' && process.argv[i + 1]) {
|
||||
type = process.argv[++i] as any
|
||||
if (type !== 'site' && type !== 'frontend') {
|
||||
console.error(`Unknown app type: ${type}`)
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
console.error(`Unknown argument: ${arg}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
const { wrapper, ...options } = presets[type]
|
||||
|
||||
let code = ''
|
||||
process.stdin.on('readable', () => {
|
||||
let chunk: string | Buffer
|
||||
while ((chunk = process.stdin.read())) {
|
||||
code += chunk
|
||||
}
|
||||
})
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
let output = transform(code, options).code
|
||||
if (output) {
|
||||
if (wrapper) {
|
||||
output = wrapper(output)
|
||||
}
|
||||
process.stdout.write(output, 'utf8')
|
||||
} else {
|
||||
throw new Error('Could not transpile code')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
25
plugin-transpiler/src/presets.ts
Normal file
25
plugin-transpiler/src/presets.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export const presets = {
|
||||
site: {
|
||||
envName: 'production',
|
||||
code: true,
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
filename: 'site.ts',
|
||||
presets: [['typescript', { isTSX: false, allExtensions: true }], 'env'],
|
||||
wrapper: (code: string): string => `(function () {let exports={};${code};return exports;})`,
|
||||
},
|
||||
frontend: {
|
||||
envName: 'production',
|
||||
code: true,
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
filename: 'frontend.tsx',
|
||||
plugins: ['transform-react-jsx'],
|
||||
presets: [
|
||||
['typescript', { isTSX: true, allExtensions: true }],
|
||||
['env', { targets: { esmodules: false } }],
|
||||
],
|
||||
wrapper: (code: string): string =>
|
||||
`"use strict";\nexport function getFrontendApp (require) { let exports = {}; ${code}; return exports; }`,
|
||||
},
|
||||
}
|
20
plugin-transpiler/tsconfig.json
Normal file
20
plugin-transpiler/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020", // Target a recent version of ECMAScript.
|
||||
"module": "CommonJS", // Use CommonJS module system for Node.js.
|
||||
"outDir": "./dist", // Output directory for the compiled JavaScript files.
|
||||
"strict": true, // Enable all strict type-checking options.
|
||||
"esModuleInterop": true, // Allows default imports from modules with no default export.
|
||||
"skipLibCheck": true, // Skip type checking of declaration files.
|
||||
"forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
|
||||
"resolveJsonModule": true, // Allow importing .json files as modules.
|
||||
"moduleResolution": "node" // Use Node.js-style module resolution.
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts", // Include all TypeScript files in the 'src' directory and its subdirectories.
|
||||
"src/**/*.tsx" // Include all TSX files, if you have any.
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules" // Exclude node_modules from the compilation.
|
||||
]
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional, Set, cast
|
||||
import subprocess
|
||||
from typing import Any, Dict, List, Optional, Set, cast, Literal
|
||||
|
||||
import requests
|
||||
from dateutil.relativedelta import relativedelta
|
||||
@ -180,6 +182,24 @@ def _fix_formdata_config_json(request: request.Request, validated_data: dict):
|
||||
validated_data["config"] = json.loads(request.POST["config"])
|
||||
|
||||
|
||||
def transpile(input_string: str, type: Literal["site", "frontend"] = "site") -> Optional[str]:
|
||||
from posthog.settings.base_variables import BASE_DIR
|
||||
|
||||
transpiler_path = os.path.join(BASE_DIR, "plugin-transpiler/dist/index.js")
|
||||
if type not in ["site", "frontend"]:
|
||||
raise Exception('Invalid type. Must be "site" or "frontend".')
|
||||
|
||||
process = subprocess.Popen(
|
||||
["node", transpiler_path, "--type", type], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = process.communicate(input=input_string.encode())
|
||||
|
||||
if process.returncode != 0:
|
||||
error = stderr.decode()
|
||||
raise Exception(error)
|
||||
return stdout.decode()
|
||||
|
||||
|
||||
class PlainRenderer(renderers.BaseRenderer):
|
||||
format = "txt"
|
||||
|
||||
@ -355,25 +375,47 @@ class PluginViewSet(StructuredViewSetMixin, viewsets.ModelViewSet):
|
||||
plugin = self.get_plugin_with_permissions(reason="source editing")
|
||||
sources: Dict[str, PluginSourceFile] = {}
|
||||
performed_changes = False
|
||||
for source in PluginSourceFile.objects.filter(plugin=plugin):
|
||||
sources[source.filename] = source
|
||||
for key, value in request.data.items():
|
||||
for plugin_source_file in PluginSourceFile.objects.filter(plugin=plugin):
|
||||
sources[plugin_source_file.filename] = plugin_source_file
|
||||
for key, source in request.data.items():
|
||||
transpiled = None
|
||||
error = None
|
||||
status = None
|
||||
try:
|
||||
if key == "site.ts":
|
||||
transpiled = transpile(source, type="site")
|
||||
status = PluginSourceFile.Status.TRANSPILED
|
||||
elif key == "frontend.tsx":
|
||||
transpiled = transpile(source, type="frontend")
|
||||
status = PluginSourceFile.Status.TRANSPILED
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
status = PluginSourceFile.Status.ERROR
|
||||
|
||||
if key not in sources:
|
||||
performed_changes = True
|
||||
sources[key], created = PluginSourceFile.objects.update_or_create(
|
||||
plugin=plugin, filename=key, defaults={"source": value}
|
||||
plugin=plugin,
|
||||
filename=key,
|
||||
defaults={
|
||||
"source": source,
|
||||
"transpiled": transpiled,
|
||||
"status": status,
|
||||
"error": error,
|
||||
},
|
||||
)
|
||||
elif sources[key].source != value:
|
||||
elif sources[key].source != source or sources[key].transpiled != transpiled or sources[key].error != error:
|
||||
performed_changes = True
|
||||
if value is None:
|
||||
if source is None:
|
||||
sources[key].delete()
|
||||
del sources[key]
|
||||
else:
|
||||
sources[key].source = value
|
||||
sources[key].status = None
|
||||
sources[key].transpiled = None
|
||||
sources[key].error = None
|
||||
sources[key].source = source
|
||||
sources[key].transpiled = transpiled
|
||||
sources[key].status = status
|
||||
sources[key].error = error
|
||||
sources[key].save()
|
||||
|
||||
response: Dict[str, str] = {}
|
||||
for _, source in sources.items():
|
||||
response[source.filename] = source.source
|
||||
|
@ -37,6 +37,8 @@ def mocked_plugin_reload(*args, **kwargs):
|
||||
@mock.patch("posthog.models.plugin.reload_plugins_on_workers", side_effect=mocked_plugin_reload)
|
||||
@mock.patch("requests.get", side_effect=mocked_plugin_requests_get)
|
||||
class TestPluginAPI(APIBaseTest, QueryMatchingTest):
|
||||
maxDiff = None
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super().setUpTestData()
|
||||
@ -703,88 +705,122 @@ class TestPluginAPI(APIBaseTest, QueryMatchingTest):
|
||||
self.assertEqual(response.json(), {"plugin.json": '{"name":"my plugin"}'})
|
||||
self.assertEqual(mock_reload.call_count, 3)
|
||||
|
||||
def test_create_plugin_frontend_source(self, mock_get, mock_reload):
|
||||
self.assertEqual(mock_reload.call_count, 0)
|
||||
def test_transpile_plugin_frontend_source(self, mock_get, mock_reload):
|
||||
# Setup
|
||||
assert mock_reload.call_count == 0
|
||||
response = self.client.post(
|
||||
"/api/organizations/@current/plugins/",
|
||||
{"plugin_type": "source", "name": "myplugin"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
assert response.status_code == 201
|
||||
id = response.json()["id"]
|
||||
self.assertEqual(
|
||||
response.json(),
|
||||
{
|
||||
"id": id,
|
||||
"plugin_type": "source",
|
||||
"name": "myplugin",
|
||||
"description": None,
|
||||
"url": None,
|
||||
"config_schema": {},
|
||||
"tag": None,
|
||||
"icon": None,
|
||||
"latest_tag": None,
|
||||
"is_global": False,
|
||||
"organization_id": response.json()["organization_id"],
|
||||
"organization_name": self.CONFIG_ORGANIZATION_NAME,
|
||||
"capabilities": {},
|
||||
"metrics": {},
|
||||
"public_jobs": {},
|
||||
},
|
||||
)
|
||||
self.assertEqual(Plugin.objects.count(), 1)
|
||||
self.assertEqual(mock_reload.call_count, 0)
|
||||
assert response.json() == {
|
||||
"id": id,
|
||||
"plugin_type": "source",
|
||||
"name": "myplugin",
|
||||
"description": None,
|
||||
"url": None,
|
||||
"config_schema": {},
|
||||
"tag": None,
|
||||
"icon": None,
|
||||
"latest_tag": None,
|
||||
"is_global": False,
|
||||
"organization_id": response.json()["organization_id"],
|
||||
"organization_name": self.CONFIG_ORGANIZATION_NAME,
|
||||
"capabilities": {},
|
||||
"metrics": {},
|
||||
"public_jobs": {},
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
assert Plugin.objects.count() == 1
|
||||
assert mock_reload.call_count == 0
|
||||
|
||||
# Add first source file, frontend.tsx
|
||||
self.client.patch(
|
||||
f"/api/organizations/@current/plugins/{id}/update_source",
|
||||
{"frontend.tsx": "export const scene = {}"},
|
||||
)
|
||||
assert Plugin.objects.count() == 1
|
||||
assert PluginSourceFile.objects.count() == 1
|
||||
assert mock_reload.call_count == 1
|
||||
|
||||
self.assertEqual(Plugin.objects.count(), 1)
|
||||
self.assertEqual(PluginSourceFile.objects.count(), 1)
|
||||
self.assertEqual(mock_reload.call_count, 1)
|
||||
|
||||
# Fetch transpiled source via API call
|
||||
plugin = Plugin.objects.get(pk=id)
|
||||
plugin_config = PluginConfig.objects.create(plugin=plugin, team=self.team, enabled=True, order=1)
|
||||
|
||||
# no frontend, since no pluginserver transpiles the code
|
||||
response = self.client.get(f"/api/plugin_config/{plugin_config.id}/frontend")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.content,
|
||||
b'export function getFrontendApp () { return {"transpiling": true} }',
|
||||
response.content.decode("utf-8"),
|
||||
'"use strict";\nexport function getFrontendApp (require) { let exports = {}; '
|
||||
'"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.scene = void 0;\n'
|
||||
"var scene = exports.scene = {};" # this is it
|
||||
"; return exports; }",
|
||||
)
|
||||
|
||||
# mock the plugin server's transpilation
|
||||
# Check in the database
|
||||
plugin_source = PluginSourceFile.objects.get(plugin_id=id)
|
||||
self.assertEqual(plugin_source.status, None)
|
||||
self.assertEqual(plugin_source.transpiled, None)
|
||||
plugin_source.status = PluginSourceFile.Status.TRANSPILED
|
||||
plugin_source.transpiled = "'random transpiled frontend'"
|
||||
plugin_source.save()
|
||||
assert plugin_source.source == "export const scene = {}"
|
||||
assert plugin_source.error is None
|
||||
assert plugin_source.transpiled == response.content.decode("utf-8")
|
||||
assert plugin_source.status == PluginSourceFile.Status.TRANSPILED
|
||||
|
||||
# Can get the transpiled frontend
|
||||
response = self.client.get(f"/api/plugin_config/{plugin_config.id}/frontend")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b"'random transpiled frontend'")
|
||||
|
||||
# Update the source frontend
|
||||
# Updates work
|
||||
self.client.patch(
|
||||
f"/api/organizations/@current/plugins/{id}/update_source",
|
||||
{"frontend.tsx": "export const scene = { name: 'new' }"},
|
||||
)
|
||||
|
||||
# It will clear the transpiled frontend
|
||||
plugin_source = PluginSourceFile.objects.get(plugin_id=id)
|
||||
self.assertEqual(plugin_source.source, "export const scene = { name: 'new' }")
|
||||
self.assertEqual(plugin_source.transpiled, None)
|
||||
|
||||
# And reply that it's transpiling
|
||||
response = self.client.get(f"/api/plugin_config/{plugin_config.id}/frontend")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.content,
|
||||
b'export function getFrontendApp () { return {"transpiling": true} }',
|
||||
assert plugin_source.source == "export const scene = { name: 'new' }"
|
||||
assert plugin_source.error is None
|
||||
assert (
|
||||
plugin_source.transpiled
|
||||
== (
|
||||
'"use strict";\nexport function getFrontendApp (require) { let exports = {}; "use strict";\n\n'
|
||||
'Object.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.scene = void 0;\n'
|
||||
"var scene = exports.scene = {\n name: 'new'\n};" # this is it
|
||||
"; return exports; }"
|
||||
)
|
||||
)
|
||||
assert plugin_source.status == PluginSourceFile.Status.TRANSPILED
|
||||
|
||||
# Errors as well
|
||||
self.client.patch(
|
||||
f"/api/organizations/@current/plugins/{id}/update_source",
|
||||
{"frontend.tsx": "export const scene = { nam broken code foobar"},
|
||||
)
|
||||
plugin_source = PluginSourceFile.objects.get(plugin_id=id)
|
||||
assert plugin_source.source == "export const scene = { nam broken code foobar"
|
||||
assert plugin_source.transpiled is None
|
||||
assert plugin_source.status == PluginSourceFile.Status.ERROR
|
||||
assert (
|
||||
plugin_source.error
|
||||
== '/frontend.tsx: Unexpected token, expected "," (1:27)\n\n> 1 | export const scene = { nam broken code foobar\n | ^\n'
|
||||
)
|
||||
|
||||
# Deletes work
|
||||
self.client.patch(
|
||||
f"/api/organizations/@current/plugins/{id}/update_source",
|
||||
{"frontend.tsx": None},
|
||||
)
|
||||
try:
|
||||
PluginSourceFile.objects.get(plugin_id=id)
|
||||
assert False, "Should have thrown DoesNotExist"
|
||||
except PluginSourceFile.DoesNotExist:
|
||||
assert True
|
||||
|
||||
# Check that the syntax for "site.ts" is slightly different
|
||||
self.client.patch(
|
||||
f"/api/organizations/@current/plugins/{id}/update_source",
|
||||
{"site.ts": "console.log('hello')"},
|
||||
)
|
||||
plugin_source = PluginSourceFile.objects.get(plugin_id=id)
|
||||
assert plugin_source.source == "console.log('hello')"
|
||||
assert plugin_source.error is None
|
||||
assert (
|
||||
plugin_source.transpiled
|
||||
== "(function () {let exports={};\"use strict\";\n\nconsole.log('hello');;return exports;})"
|
||||
)
|
||||
assert plugin_source.status == PluginSourceFile.Status.TRANSPILED
|
||||
|
||||
def test_plugin_repository(self, mock_get, mock_reload):
|
||||
response = self.client.get("/api/organizations/@current/plugins/repository/")
|
||||
|
@ -26,7 +26,7 @@ WORKDIR /code
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && pnpm --version && \
|
||||
RUN corepack enable && \
|
||||
mkdir /tmp/pnpm-store && \
|
||||
pnpm install --frozen-lockfile --store-dir /tmp/pnpm-store --prod && \
|
||||
rm -rf /tmp/pnpm-store
|
||||
@ -37,6 +37,22 @@ COPY babel.config.js tsconfig.json webpack.config.js ./
|
||||
RUN pnpm build
|
||||
|
||||
|
||||
#
|
||||
# ---------------------------------------------------------
|
||||
#
|
||||
FROM node:18.12.1-bullseye-slim AS plugin-transpiler-build
|
||||
WORKDIR /code
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY plugin-transpiler/ plugin-transpiler/
|
||||
WORKDIR /code/plugin-transpiler
|
||||
RUN corepack enable && \
|
||||
mkdir /tmp/pnpm-store && \
|
||||
pnpm install --frozen-lockfile --store-dir /tmp/pnpm-store && \
|
||||
pnpm build && \
|
||||
rm -rf /tmp/pnpm-store
|
||||
|
||||
|
||||
#
|
||||
# ---------------------------------------------------------
|
||||
#
|
||||
|
16
unit.json
16
unit.json
@ -14,27 +14,23 @@
|
||||
"metrics": [
|
||||
{
|
||||
"match": {
|
||||
"uri": [
|
||||
"/metrics"
|
||||
]
|
||||
"uri": ["/metrics"]
|
||||
},
|
||||
"action": {
|
||||
"pass": "applications/metrics"
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"status": [
|
||||
{
|
||||
"match": {
|
||||
"uri": [
|
||||
"/status"
|
||||
]
|
||||
"uri": ["/status"]
|
||||
},
|
||||
"action": {
|
||||
"proxy": "http://unix:/var/run/control.unit.sock"
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
"applications": {
|
||||
"posthog": {
|
||||
@ -52,6 +48,6 @@
|
||||
"path": ".",
|
||||
"module": "unit_metrics",
|
||||
"user": "nobody"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user