Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - Migrate backend to Deno #1395

Open
wants to merge 56 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d990dd0
Make backend tests pass
snowteamer May 10, 2022
b702711
Merge master into add-deno-version-without-pogo
snowteamer Aug 27, 2022
ad2c7e8
Use own Pogo fork
snowteamer Aug 27, 2022
3110298
Drop Nodejs backend
snowteamer Aug 28, 2022
35fb008
Ignore backend/ in .flowconfig
snowteamer Aug 28, 2022
bd31982
Install Deno in Travis CI
snowteamer Aug 28, 2022
bd116c5
Restore shared/types.js
snowteamer Aug 28, 2022
e226467
Make grunt deno task non-blocking
snowteamer Aug 28, 2022
5713753
Fix route handler for cached assets
snowteamer Aug 29, 2022
6f5bbae
Update Flow declarations and ignore shared files
snowteamer Aug 29, 2022
4a37434
Fix dangling process issues
snowteamer Aug 29, 2022
c2a6664
Fix spurious empty lines in backend output
snowteamer Aug 30, 2022
7b82ac8
Restore pubsub pingInterval and cleanup output
snowteamer Aug 30, 2022
5a92b17
Restore lost backend logging
snowteamer Aug 31, 2022
a408411
Refactor backend/database.ts using .push()
snowteamer Sep 1, 2022
13382ac
Drop --no-check flag in deno:start
snowteamer Sep 1, 2022
786a4f0
Fix 'npm backend' script
snowteamer Sep 1, 2022
6f6bb96
Make grunt pin run esbuild first (@SebinSong)
snowteamer Sep 10, 2022
71b74d6
Migrate backend tests to Deno
snowteamer Sep 11, 2022
23272a2
Fix test:unit task
snowteamer Sep 11, 2022
b20992c
Exit early when running in CI
snowteamer Sep 11, 2022
80573a7
Make backend and shared pass deno check
snowteamer Sep 14, 2022
747091b
Merge branch 'master' of https://github.com/okTurtles/group-income in…
snowteamer Sep 18, 2022
4800658
Adding typescript linting
snowteamer Sep 20, 2022
1af98c9
Adding typescript linting
snowteamer Sep 20, 2022
67cff98
Add exec:ts task (deno check) in the build
snowteamer Sep 21, 2022
58f7b2c
Merge branch 'add-deno-version-without-pogo' of https://github.com/sn…
snowteamer Oct 8, 2022
7a2c8c7
Fix Flow errors
snowteamer Oct 9, 2022
4f0e68c
Merge branch 'master' of https://github.com/okTurtles/group-income in…
snowteamer Oct 9, 2022
f9228d3
Merge branch master into add-deno-version-without-pogo
snowteamer Oct 12, 2022
6f9a3c9
Migrate i18n validation tests to Deno
snowteamer Oct 17, 2022
bd7a1a1
Migrate disallow-vhtml-directive tests to Deno
snowteamer Oct 17, 2022
3e7ca71
Fix lint errors
snowteamer Oct 18, 2022
c6e2601
Restore Flow typings of a few more files
snowteamer Oct 20, 2022
f1d87f1
Bump contracts version to 0.0.8
snowteamer Oct 20, 2022
b937be3
Merge master into add-deno-version-without-pogo
snowteamer Oct 20, 2022
ea78079
Get rid of 'curl | sh' in travis.yml
snowteamer Oct 20, 2022
207a435
Migrate giLodash tests to Deno
snowteamer Oct 26, 2022
a17ba6a
Migrate time.js tests to Deno
snowteamer Oct 26, 2022
c23f017
Use a single .eslintrc.json
snowteamer Oct 26, 2022
b761ffe
Ignore test/contracts in .gitignore
snowteamer Oct 26, 2022
bec5000
Migrate remaining unit tests to Deno
snowteamer Oct 26, 2022
a8f890f
Drop Mocha
snowteamer Oct 28, 2022
29370e3
Update Deno and import maps
snowteamer Oct 28, 2022
500b0c3
Merge master into add-deno-version-without-pogo
snowteamer Oct 28, 2022
e612402
Merge origin into add-deno-version-without-pogo
snowteamer Oct 29, 2022
ebc3168
Fix app sometimes not loading on port 3000
snowteamer Oct 29, 2022
21f7240
Merge master into add-deno-version-without-pogo
snowteamer Oct 29, 2022
50c6796
Fix exec:ts error in giLodash.js
snowteamer Oct 30, 2022
3f6d704
Cleanup test/contracts
snowteamer Nov 5, 2022
2ab156d
Merge branch master into add-deno-version-without-pogo
snowteamer Jan 9, 2023
cbfeb7d
Merge master into add-deno-version-without-pogo
snowteamer Jan 10, 2023
01c0c52
Rename import-map.json to import_map.json
snowteamer Jan 10, 2023
e0dc0aa
Use some pinned URLs in import map
snowteamer Jan 10, 2023
7ac3edd
Merge master into add-deno-version-without-pogo
snowteamer Jan 24, 2023
55f5047
Merge branch 'master' of https://github.com/okTurtles/group-income in…
snowteamer Jan 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .flowconfig
Expand Up @@ -7,6 +7,10 @@
# - https://flowtype.org/docs/objects.html
# - https://flowtype.org/docs/functions.html
.*/Gruntfile.js
# Backend files use TypeScript instead of Flow since they run using Deno.
.*/backend/.*
# Shared files must be imported by Deno backend files, so they better not contain Flowtype annotations.
.*/shared/.*
.*/dist/.*
.*/contracts/.*
.*/frontend/assets/.*
Expand All @@ -23,6 +27,9 @@
.*/test/.*
.*/test/frontend.js
.*.test.js
# Not using Flow because backend tests are importing it.
.*/frontend/controller/namespace.js
.*/frontend/controller/utils/misc.js

[libs]
./shared/declarations.js
Expand Down
8 changes: 8 additions & 0 deletions .travis.yml
Expand Up @@ -9,6 +9,14 @@ env:
branches:
only:
- master # https://github.com/okTurtles/group-income/issues/58
before_install:
# https://blog.travis-ci.com/2021-02-01-rundeno
- pwd
- curl -fsSL https://deno.land/x/install/install.sh | sh
snowteamer marked this conversation as resolved.
Show resolved Hide resolved
- ls -l $HOME/.deno
- export DENO_INSTALL="$HOME/.deno"
- export PATH="$DENO_INSTALL/bin:$PATH"
- deno run https://deno.land/std/examples/welcome.ts
cache:
# Caches $HOME/.npm when npm ci is default script command
# Caches node_modules in all other cases
Expand Down
189 changes: 125 additions & 64 deletions Gruntfile.js
@@ -1,11 +1,13 @@
'use strict'

if (process.env['CI']) process.exit(1)

// =======================
// Entry point.
//
// Ensures:
//
// - Babel support is available on the backend, in Mocha tests, etc.
// - Babel support is available in Mocha tests, etc.
// - Environment variables are set to different values depending
// on whether we're in a production environment or otherwise.
//
Expand All @@ -14,7 +16,7 @@
const util = require('util')
const chalk = require('chalk')
const crypto = require('crypto')
const { exec, fork } = require('child_process')
const { exec, spawn } = require('child_process')
const execP = util.promisify(exec)
const { copyFile, readFile } = require('fs/promises')
const fs = require('fs')
Expand Down Expand Up @@ -66,7 +68,7 @@ const {
EXPOSE_SBP = ''
} = process.env

const backendIndex = './backend/index.js'
const backendIndex = './backend/index.ts'
const distAssets = 'dist/assets'
const distCSS = 'dist/assets/css'
const distDir = 'dist'
Expand Down Expand Up @@ -226,6 +228,34 @@ module.exports = (grunt) => {
entryPoints: ['./frontend/controller/serviceworkers/primary.js']
}
}
esbuildOptionBags.testCommons = {
...esbuildOptionBags.default,
bundle: true,
entryPoints: ['./frontend/common/common.js'],
external: ['dompurify', 'vue'],
outdir: './test/common',
splitting: false
}
esbuildOptionBags.testContracts = {
...esbuildOptionBags.default,
bundle: true,
entryPoints: [
`${contractsDir}/group.js`,
`${contractsDir}/chatroom.js`,
`${contractsDir}/identity.js`,
`${contractsDir}/mailbox.js`,
// `${contractsDir}/misc/flowTyper.js`,
`${contractsDir}/shared/voting/proposals.js`,
`${contractsDir}/shared/payments/index.js`,
`${contractsDir}/shared/constants.js`,
`${contractsDir}/shared/functions.js`,
`${contractsDir}/shared/giLodash.js`
],
external: ['dompurify', 'vue'],
outdir: './test/contracts',
splitting: false
}

esbuildOptionBags.contracts = {
...pick(clone(esbuildOptionBags.default), [
'define', 'bundle', 'watch', 'incremental'
Expand Down Expand Up @@ -360,14 +390,14 @@ module.exports = (grunt) => {
flow: '"./node_modules/.bin/flow" --quiet || echo The Flow check failed!',
puglint: '"./node_modules/.bin/pug-lint-vue" frontend/views',
stylelint: 'node ./node_modules/stylelint/bin/stylelint.js --cache "frontend/assets/style/**/*.{css,sass,scss}" "frontend/views/**/*.vue"',
// Test files:
// - anything in the `/test` folder, e.g. integration tests;
// - anything that ends with `.test.js`, e.g. unit tests for SBP domains kept in the domain folder.
// Test anything that ends with `.test.js`, e.g. unit tests for SBP domains kept in the domain folder.
// The `--require` flag ensures custom Babel support in our test files.
test: {
cmd: 'node --experimental-fetch node_modules/mocha/bin/mocha --require ./scripts/mocha-helper.js --exit -R spec --bail "./{test/,!(node_modules|ignored|dist|historical|test)/**/}*.test.js"',
options: { env: process.env }
},
// Test anything in /test that ends with `.test.ts`.
testWithDeno: 'deno test --allow-env --allow-net --allow-read --allow-write --importmap=import-map.json --no-check ./test/*.ts',
chelDeployAll: 'find contracts -iname "*.manifest.json" | xargs -r ./node_modules/.bin/chel deploy ./data'
}
})
Expand All @@ -378,53 +408,6 @@ module.exports = (grunt) => {

let child = null

// Useful helper task for `grunt test`.
grunt.registerTask('backend:launch', '[internal]', function () {
const done = this.async()
grunt.log.writeln('backend: launching...')
// Provides Babel support for the backend files.
require('@babel/register')
require(backendIndex).then(done).catch(done)
})

// Used with `grunt dev` only, makes it possible to restart just the server when
// backend or shared files are modified.
grunt.registerTask('backend:relaunch', '[internal]', function () {
const done = this.async() // Tell Grunt we're async.
const fork2 = function () {
grunt.log.writeln('backend: forking...')
child = fork(backendIndex, process.argv, {
env: process.env,
execArgv: ['--require', '@babel/register']
})
child.on('error', (err) => {
if (err) {
console.error('error starting or sending message to child:', err)
process.exit(1)
}
})
child.on('exit', (c) => {
if (c !== 0) {
grunt.log.error(`child exited with error code: ${c}`.bold)
// ^C can cause c to be null, which is an OK error.
process.exit(c || 0)
}
})
done()
}
if (child) {
grunt.log.writeln('Killing child!')
// Wait for successful shutdown to avoid EADDRINUSE errors.
child.on('message', () => {
child = null
fork2()
})
child.send({ shutdown: 1 })
} else {
fork2()
}
})

grunt.registerTask('build', function () {
const esbuild = this.flags.watch ? 'esbuild:watch' : 'esbuild'

Expand Down Expand Up @@ -455,8 +438,8 @@ module.exports = (grunt) => {
cypress[command](options).then(r => done(r.totalFailed === 0)).catch(done)
})

grunt.registerTask('pin', async function (version) {
if (typeof version !== 'string') throw new Error('usage: grunt pin:<version>')
grunt.registerTask('_pin', async function (version) {
// a task internally executed in 'pin' task below
const done = this.async()
const dirPath = `contracts/${version}`

Expand All @@ -481,9 +464,64 @@ module.exports = (grunt) => {
done()
})

grunt.registerTask('pin', function (version) {
if (typeof version !== 'string') throw new Error('usage: grunt pin:<version>')

grunt.task.run('build')
grunt.task.run(`_pin:${version}`)
})

grunt.registerTask('default', ['dev'])

grunt.registerTask('deno:start', function () {
const done = this.async() // Tell Grunt we're async.
child = spawn(
'deno',
['run', '--allow-env', '--allow-net', '--allow-read', '--allow-write', '--import-map=import-map.json', backendIndex],
{
stdio: 'inherit'
}
)
child.on('error', (err) => {
if (err) {
console.error('Error starting or sending message to child:', err)
process.exit(1)
}
})
child.on('exit', (c) => {
// ^C can cause c to be null, which is an OK error.
if (c === null) {
grunt.log.writeln('Backend process exited with null code.')
} else if (c !== 0) {
grunt.log.error(`Backend process exited with error code: ${c}`.bold)
process.exit(c)
} else {
grunt.log.writeln('Backend process exited normally.')
}
})
child.on('close', (code) => {
console.log(`Backend process closed with code ${code}`)
})
child.on('spawn', () => {
grunt.log.writeln('Backend process spawned.')
done()
})
})

grunt.registerTask('deno:stop', function () {
if (child) {
const killed = child.kill()
if (killed) {
grunt.log.writeln('Deno backend stopped.')
child = null
} else {
grunt.log.error('Failed to quit dangling child!')
}
}
})

// TODO: add 'deploy' as per https://github.com/okTurtles/group-income/issues/10
grunt.registerTask('dev', ['checkDependencies', 'exec:chelDeployAll', 'build:watch', 'backend:relaunch', 'keepalive'])
grunt.registerTask('dev', ['checkDependencies', 'exec:chelDeployAll', 'build:watch', 'deno:start', 'keepalive'])
grunt.registerTask('dist', ['build'])

// --------------------
Expand Down Expand Up @@ -518,6 +556,12 @@ module.exports = (grunt) => {
const buildContractsSlim = createEsbuildTask({
...esbuildOptionBags.contractsSlim, plugins: defaultPlugins
})
const buildTestCommons = createEsbuildTask({
...esbuildOptionBags.testCommons, plugins: defaultPlugins
})
const buildTestContracts = createEsbuildTask({
...esbuildOptionBags.testContracts, plugins: defaultPlugins
})

// first we build the contracts since genManifestsAndDeploy depends on that
// and then we build the main bundle since it depends on manifests.json
Expand All @@ -528,6 +572,7 @@ module.exports = (grunt) => {
.then(() => {
return Promise.all([buildMain.run(), buildServiceWorkers.run()])
})
.then(() => Promise.all([buildTestContracts.run(), buildTestCommons.run()]))
.catch(error => {
grunt.log.error(error.message)
process.exit(1)
Expand All @@ -547,7 +592,7 @@ module.exports = (grunt) => {

;[
[['Gruntfile.js'], [eslint]],
[['backend/**/*.js', 'shared/**/*.js'], [eslint, 'backend:relaunch']],
[['backend/**/*.ts', 'shared/**/*.ts'], [eslint, 'deno:stop', 'deno:start']],
[['frontend/**/*.html'], ['copy']],
[['frontend/**/*.js'], [eslint]],
[['frontend/assets/{fonts,images}/**/*'], ['copy']],
Expand Down Expand Up @@ -620,6 +665,19 @@ module.exports = (grunt) => {
done()
})

// Stops the Flowtype server.
grunt.registerTask('flow:stop', function () {
const done = this.async()
exec('./node_modules/.bin/flow stop', (err, stdout, stderr) => {
if (!err) {
grunt.log.writeln('Flowtype server stopped')
} else {
grunt.log.error('Could not stop Flowtype server:', err.message)
}
done(err)
})
})

// eslint-disable-next-line no-unused-vars
let killKeepAlive = null
grunt.registerTask('keepalive', function () {
Expand All @@ -628,27 +686,30 @@ module.exports = (grunt) => {
killKeepAlive = this.async()
})

grunt.registerTask('test', ['build', 'exec:chelDeployAll', 'backend:launch', 'exec:test', 'cypress'])
grunt.registerTask('test:unit', ['backend:launch', 'exec:test'])
grunt.registerTask('test', ['build', 'exec:chelDeployAll', 'deno:start', 'exec:test', 'exec:testWithDeno', 'cypress', 'deno:stop', 'flow:stop'])
grunt.registerTask('test:unit', ['deno:start', 'exec:test', 'exec:testWithDeno', 'deno:stop'])

// -------------------------------------------------------------------------
// Process event handlers
// -------------------------------------------------------------------------

process.on('exit', () => {
process.on('exit', (code, signal) => {
console.log('[node] Exiting with code:', code, 'signal:', signal)
// Note: 'beforeExit' doesn't work.
// In cases where 'watch' fails while child (server) is still running
// we will exit and child will continue running in the background.
// This can happen, for example, when running two GIS instances via
// the PORT_SHIFT envar. If grunt-contrib-watch livereload process
// cannot bind to the port for some reason, then the parent process
// will exit leaving a dangling child server process.
if (child) {
if (child && !child.killed) {
grunt.log.writeln('Quitting dangling child!')
child.send({ shutdown: 2 })
child.kill()
}
// Stops the Flowtype server.
exec('./node_modules/.bin/flow stop')
// Make sure to stop the Flowtype server in case `flow:stop` wasn't called.
exec('./node_modules/.bin/flow stop', () => {
grunt.log.writeln('Flowtype server stopped in process exit handler')
})
snowteamer marked this conversation as resolved.
Show resolved Hide resolved
})

process.on('uncaughtException', (err) => {
Expand Down
20 changes: 20 additions & 0 deletions backend/.eslintrc.json
@@ -0,0 +1,20 @@
{
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"plugins": [
"@typescript-eslint",
"import"
],
"rules": {
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"require-await": "error",
"quote-props": "off",
"dot-notation": "off",
"import/extensions": [
2,
"ignorePackages"
]
}
}