Update release process (#192)

This commit is contained in:
Chris Anderson 2023-06-21 08:50:01 -05:00 committed by GitHub
parent 9d17439447
commit 5f8e061061
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 147 additions and 70 deletions

27
.github/workflows/publish.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: Publish Packages
on:
push:
tags:
- "v*.*.*"
jobs:
build-package:
name: "Publish to GitHub Packages"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
if: github.repository_owner == 'parcelvoy'
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
registry-url: https://npm.pkg.github.com/
- run: npm ci
- run: echo "registry=https://npm.pkg.github.com/@parcelvoy" >> .npmrc
- run: npm run package:publish --tag=$(echo ${GITHUB_REF_NAME:1})
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

View file

@ -64,7 +64,7 @@ jobs:
run: npm install && npm install --save-dev run: npm install && npm install --save-dev
- name: 'Run Jest Tests' - name: 'Run Jest Tests'
run: npm run test -- --scope=platform run: npm run test -- --scope=@parcelvoy/platform
env: env:
NODE_ENV: test NODE_ENV: test
APP_SECRET: testing APP_SECRET: testing

View file

@ -24,4 +24,4 @@ USER node
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY --chown=node:node --from=build /usr/src/app ./ COPY --chown=node:node --from=build /usr/src/app ./
EXPOSE 3001 EXPOSE 3001
CMD ["dumb-init", "node", "index.js"] CMD ["dumb-init", "node", "boot.js"]

View file

@ -1,6 +1,6 @@
{ {
"ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"], "ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"],
"watch": ["src", ".env"], "watch": ["src", ".env"],
"exec": "TZ=utc ts-node --transpile-only ./src/index.ts", "exec": "TZ=utc ts-node --transpile-only ./src/boot.ts",
"ext": "ts,js,json" "ext": "ts,js,json"
} }

View file

@ -1,11 +1,11 @@
{ {
"name": "platform", "name": "@parcelvoy/platform",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "platform", "name": "@parcelvoy/platform",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.171.0", "@aws-sdk/client-s3": "^3.171.0",

View file

@ -1,6 +1,9 @@
{ {
"name": "platform", "name": "@parcelvoy/platform",
"version": "0.1.0", "version": "0.1.0",
"repository":"https://github.com/parcelvoy/platform",
"main": "build/index.js",
"types": "build/index.d.ts",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.171.0", "@aws-sdk/client-s3": "^3.171.0",
"@aws-sdk/client-ses": "^3.121.0", "@aws-sdk/client-ses": "^3.121.0",
@ -56,7 +59,8 @@
"test": "jest --forceExit --runInBand --testTimeout 10000", "test": "jest --forceExit --runInBand --testTimeout 10000",
"docker:build": "docker buildx build -f ./Dockerfile -t ghcr.io/parcelvoy/api:latest -t ghcr.io/parcelvoy/api:$npm_config_tag ../../", "docker:build": "docker buildx build -f ./Dockerfile -t ghcr.io/parcelvoy/api:latest -t ghcr.io/parcelvoy/api:$npm_config_tag ../../",
"docker:build:push": "npm run docker:build -- --push", "docker:build:push": "npm run docker:build -- --push",
"migration:create": "node ./scripts/create-migration.mjs" "migration:create": "node ./scripts/create-migration.mjs",
"package:publish": "npm run build && npm version $npm_config_tag --no-git-tag-version && npm pack && npm publish --tag=latest"
}, },
"devDependencies": { "devDependencies": {
"@types/busboy": "^1.5.0", "@types/busboy": "^1.5.0",
@ -83,5 +87,6 @@
"ts-jest": "^28.0.7", "ts-jest": "^28.0.7",
"ts-node": "^10.8.2", "ts-node": "^10.8.2",
"typescript": "^4.9.3" "typescript": "^4.9.3"
} },
"files": [ "/build", "/db" ]
} }

View file

@ -2,11 +2,15 @@ import Koa from 'koa'
import koaBody from 'koa-body' import koaBody from 'koa-body'
import cors from '@koa/cors' import cors from '@koa/cors'
import serve from 'koa-static' import serve from 'koa-static'
import controllers from './config/controllers' import controllers, { register } from './config/controllers'
import { RequestError } from './core/errors' import { RequestError } from './core/errors'
import { logger } from './config/logger' import { logger } from './config/logger'
import Router from '@koa/router'
export default class Api extends Koa { export default class Api extends Koa {
router = new Router({ prefix: '/api' })
controllers?: Record<string, Router>
constructor( constructor(
public app: import('./app').default, public app: import('./app').default,
) { ) {
@ -52,6 +56,21 @@ export default class Api extends Koa {
defer: !app.env.mono, defer: !app.env.mono,
})) }))
controllers(this) this.registerControllers()
}
getControllers() {
return controllers(this.app)
}
registerControllers() {
this.controllers = this.getControllers()
this.register(...Object.values(this.controllers))
}
register(...routers: Router[]) {
const root = register(this.router, ...routers)
this.use(root.routes())
.use(root.allowedMethods())
} }
} }

View file

@ -22,7 +22,7 @@ export default class App {
return App.$main return App.$main
} }
static async init(env: Env): Promise<App> { static async init<T extends typeof App>(this: T, env: Env): Promise<InstanceType<T>> {
logger.info('parcelvoy initializing') logger.info('parcelvoy initializing')
@ -42,15 +42,20 @@ export default class App {
const auth = loadAuth(env.auth) const auth = loadAuth(env.auth)
// Setup app // Setup app
App.$main = new App(env, const app = new this(env,
database, database,
queue, queue,
auth, auth,
storage, storage,
error, error,
) ) as any
return App.$main return this.setMain(app)
}
static setMain<T extends typeof App>(this: T, app: InstanceType<T>) {
this.$main = app
return app
} }
uuid = uuid() uuid = uuid()
@ -59,8 +64,7 @@ export default class App {
rateLimiter: RateLimiter rateLimiter: RateLimiter
#registered: { [key: string | number]: unknown } #registered: { [key: string | number]: unknown }
// eslint-disable-next-line no-useless-constructor constructor(
private constructor(
public env: Env, public env: Env,
public db: Database, public db: Database,
public queue: Queue, public queue: Queue,
@ -73,23 +77,31 @@ export default class App {
this.unhandledErrorListener() this.unhandledErrorListener()
} }
async start() { start() {
const runners = this.env.runners const runners = this.env.runners
if (runners.includes('api')) { if (runners.includes('api')) {
this.api = new Api(this) this.startApi()
const server = this.api?.listen(this.env.port)
server.keepAliveTimeout = 65000
server.requestTimeout = 0
logger.info('parcelvoy:api ready')
} }
if (runners.includes('worker')) { if (runners.includes('worker')) {
this.worker = new Worker(this) this.startWorker()
this.worker?.run()
logger.info('parcelvoy:worker ready')
} }
return this return this
} }
startApi(api?: Api) {
this.api = api ?? new Api(this)
const server = this.api?.listen(this.env.port)
server.keepAliveTimeout = 65000
server.requestTimeout = 0
logger.info('parcelvoy:api ready')
}
startWorker(worker?: Worker) {
this.worker = worker ?? new Worker(this)
this.worker?.run()
logger.info('parcelvoy:worker ready')
}
async close() { async close() {
await this.worker?.close() await this.worker?.close()
await this.db.destroy() await this.db.destroy()

View file

@ -0,0 +1,5 @@
import App from './app'
import env from './config/env'
export default App.init(env())
.then(app => app.start())

View file

@ -20,28 +20,25 @@ import ProjectAdminController from '../projects/ProjectAdminController'
import ProjectApiKeyController from '../projects/ProjectApiKeyController' import ProjectApiKeyController from '../projects/ProjectApiKeyController'
import AdminController from '../auth/AdminController' import AdminController from '../auth/AdminController'
import OrganizationController from '../organizations/OrganizationController' import OrganizationController from '../organizations/OrganizationController'
import App from '../app'
const register = (parent: Router, ...routers: Router[]) => { export const register = (parent: Router, ...routers: Router[]) => {
for (const router of routers) { for (const router of routers) {
parent.use(router.routes(), router.allowedMethods()) parent.use(router.routes(), router.allowedMethods())
} }
return parent return parent
} }
export default (api: import('../api').default) => { export default (app: App) => {
// Register the three main levels of routers const routers: Record<string, Router> = {
const root = register(new Router({ prefix: '/api' }), admin: adminRouter(),
adminRouter(), client: clientRouter(),
clientRouter(), public: publicRouter(),
publicRouter(), }
)
api.use(root.routes())
.use(root.allowedMethods())
// If we are running in mono mode, we need to also serve the UI // If we are running in mono mode, we need to also serve the UI
if (api.app.env.mono) { if (app.env.mono) {
const ui = new Router() const ui = new Router()
ui.get('/(.*)', async (ctx, next) => { ui.get('/(.*)', async (ctx, next) => {
try { try {
@ -50,9 +47,10 @@ export default (api: import('../api').default) => {
return next() return next()
} }
}) })
api.use(ui.routes()) routers.ui = ui
.use(ui.allowedMethods())
} }
return routers
} }
/** /**

View file

@ -1,4 +1,5 @@
import knex, { Knex as Database } from 'knex' import knex, { Knex as Database } from 'knex'
import path from 'path'
import { removeKey } from '../utilities' import { removeKey } from '../utilities'
import { logger } from './logger' import { logger } from './logger'
@ -10,6 +11,7 @@ export interface DatabaseConfig {
user: string user: string
password: string password: string
database?: string database?: string
migrationPaths: string[]
} }
export type Query = (builder: Database.QueryBuilder<any>) => Database.QueryBuilder<any> export type Query = (builder: Database.QueryBuilder<any>) => Database.QueryBuilder<any>
@ -23,7 +25,7 @@ knex.QueryBuilder.extend('when', function(
}) })
const connect = (config: DatabaseConfig, withDB = true) => { const connect = (config: DatabaseConfig, withDB = true) => {
let connection = config let connection = removeKey('migrationPaths', config)
if (!withDB) { if (!withDB) {
connection = removeKey('database', connection) connection = removeKey('database', connection)
} }
@ -45,7 +47,7 @@ const connect = (config: DatabaseConfig, withDB = true) => {
const migrate = async (config: DatabaseConfig, db: Database, fresh = false) => { const migrate = async (config: DatabaseConfig, db: Database, fresh = false) => {
if (fresh) await db.raw(`CREATE DATABASE ${config.database}`) if (fresh) await db.raw(`CREATE DATABASE ${config.database}`)
return db.migrate.latest({ return db.migrate.latest({
directory: './db/migrations', directory: [path.resolve(__dirname, '../../db/migrations'), ...config.migrationPaths],
tableName: 'migrations', tableName: 'migrations',
loadExtensions: ['.js', '.ts'], loadExtensions: ['.js', '.ts'],
}) })

View file

@ -55,6 +55,7 @@ export default (type?: EnvType): Env => {
password: process.env.DB_PASSWORD!, password: process.env.DB_PASSWORD!,
port: parseInt(process.env.DB_PORT!), port: parseInt(process.env.DB_PORT!),
database: process.env.DB_DATABASE!, database: process.env.DB_DATABASE!,
migrationPaths: process.env.DB_MIGRATION_PATHS?.split(',') ?? [],
}, },
redis: { redis: {
host: process.env.REDIS_HOST!, host: process.env.REDIS_HOST!,

View file

@ -21,8 +21,6 @@ import UserAliasJob from '../users/UserAliasJob'
import UserSchemaSyncJob from '../schema/UserSchemaSyncJob' import UserSchemaSyncJob from '../schema/UserSchemaSyncJob'
import UserDeviceJob from '../users/UserDeviceJob' import UserDeviceJob from '../users/UserDeviceJob'
export type Queues = Record<number, Queue>
export const loadJobs = (queue: Queue) => { export const loadJobs = (queue: Queue) => {
queue.register(CampaignGenerateListJob) queue.register(CampaignGenerateListJob)
queue.register(CampaignInteractJob) queue.register(CampaignInteractJob)
@ -49,9 +47,3 @@ export const loadJobs = (queue: Queue) => {
export default (config: QueueConfig) => { export default (config: QueueConfig) => {
return new Queue(config) return new Queue(config)
} }
export const loadWorker = (config: QueueConfig) => {
const queue = new Queue(config)
loadJobs(queue)
return queue
}

View file

@ -12,7 +12,7 @@ addErrors(validator)
export { JSONSchemaType, IsValidSchema, validator } export { JSONSchemaType, IsValidSchema, validator }
export const validate = <T>(schema: JSONSchemaType<T>, data: any): T => { export function validate<T>(schema: JSONSchemaType<T>, data: any): T {
const validate = validator.getSchema<T>(schema.$id!) const validate = validator.getSchema<T>(schema.$id!)
|| validator.compile(schema) || validator.compile(schema)
if (validate(data)) { if (validate(data)) {

View file

@ -1,5 +1,8 @@
import Api from './api'
import App from './app' import App from './app'
import env from './config/env' import env from './config/env'
import Model from './core/Model'
import Worker from './worker'
import Job from './queue/Job'
export default App.init(env()) export { App, env, Model, Api, Worker, Job }
.then(app => app.start())

View file

@ -18,7 +18,7 @@ export interface ProviderSetupMeta {
value: string | number value: string | number
} }
export const ProviderSchema = <T extends ExternalProviderParams, D>(id: string, data: JSONSchemaType<D>): JSONSchemaType<T> => { export function ProviderSchema<_ extends ExternalProviderParams, D>(id: string, data: JSONSchemaType<D>): any {
return { return {
$id: id, $id: id,
type: 'object', type: 'object',

View file

@ -1,15 +1,17 @@
import { loadWorker } from './config/queue' import { loadJobs } from './config/queue'
import scheduler, { Scheduler } from './config/scheduler' import scheduler, { Scheduler } from './config/scheduler'
import Queue from './queue' import Queue from './queue'
export default class Worker { export default class Worker {
worker: Queue worker: Queue
scheduler: Scheduler scheduler: Scheduler
constructor( constructor(
public app: import('./app').default, public app: import('./app').default,
) { ) {
this.worker = loadWorker(app.env.queue) this.worker = new Queue(app.env.queue)
this.scheduler = scheduler(app) this.scheduler = scheduler(app)
this.loadJobs()
} }
run() { run() {
@ -20,4 +22,8 @@ export default class Worker {
await this.worker.close() await this.worker.close()
await this.scheduler.close() await this.scheduler.close()
} }
loadJobs() {
loadJobs(this.worker)
}
} }

View file

@ -1,11 +1,11 @@
{ {
"name": "ui", "name": "@parcelvoy/ui",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ui", "name": "@parcelvoy/ui",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@fontsource/inter": "^4.5.14", "@fontsource/inter": "^4.5.14",

View file

@ -1,6 +1,7 @@
{ {
"name": "ui", "name": "@parcelvoy/ui",
"version": "0.1.0", "version": "0.1.0",
"repository":"https://github.com/parcelvoy/platform",
"dependencies": { "dependencies": {
"@fontsource/inter": "^4.5.14", "@fontsource/inter": "^4.5.14",
"@headlessui/react": "1.7.13", "@headlessui/react": "1.7.13",
@ -45,7 +46,8 @@
"eject": "react-scripts eject", "eject": "react-scripts eject",
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"", "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"docker:build": "docker buildx build -f ./Dockerfile -t ghcr.io/parcelvoy/ui:latest -t ghcr.io/parcelvoy/ui:$npm_config_tag ../../", "docker:build": "docker buildx build -f ./Dockerfile -t ghcr.io/parcelvoy/ui:latest -t ghcr.io/parcelvoy/ui:$npm_config_tag ../../",
"docker:build:push": "npm run docker:build -- --push" "docker:build:push": "npm run docker:build -- --push",
"package:publish": "npm run build && npm version $npm_config_tag --no-git-tag-version && npm pack && npm publish --tag=latest"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@ -68,5 +70,6 @@
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.31.11",
"typescript": "^4.9.3" "typescript": "^4.9.3"
} },
"files": [ "/build" ]
} }

18
package-lock.json generated
View file

@ -13,6 +13,7 @@
} }
}, },
"apps/platform": { "apps/platform": {
"name": "@parcelvoy/platform",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.171.0", "@aws-sdk/client-s3": "^3.171.0",
@ -137,6 +138,7 @@
} }
}, },
"apps/ui": { "apps/ui": {
"name": "@parcelvoy/ui",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@fontsource/inter": "^4.5.14", "@fontsource/inter": "^4.5.14",
@ -6829,6 +6831,14 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/@parcelvoy/platform": {
"resolved": "apps/platform",
"link": true
},
"node_modules/@parcelvoy/ui": {
"resolved": "apps/ui",
"link": true
},
"node_modules/@parse/node-apn": { "node_modules/@parse/node-apn": {
"version": "5.1.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz",
@ -23044,10 +23054,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/platform": {
"resolved": "apps/platform",
"link": true
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.22", "version": "8.4.22",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.22.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.22.tgz",
@ -29409,10 +29415,6 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/ui": {
"resolved": "apps/ui",
"link": true
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View file

@ -14,6 +14,7 @@
"test": "lerna run test", "test": "lerna run test",
"docker:build": "lerna run docker:build", "docker:build": "lerna run docker:build",
"docker:build:push": "lerna run docker:build:push", "docker:build:push": "lerna run docker:build:push",
"analyze:ui:bundle": "lerna run build && npx source-map-explorer './apps/ui/build/static/js/*.js'" "analyze:ui:bundle": "lerna run build && npx source-map-explorer './apps/ui/build/static/js/*.js'",
"package:publish": "lerna run package:publish"
} }
} }

View file

@ -12,7 +12,8 @@
"pretty": true, "pretty": true,
"sourceMap": true, "sourceMap": true,
"allowJs": true, "allowJs": true,
"noEmit": false "noEmit": false,
"declaration": true
} }
} }