From 5f8e0610618a75d07f39ffed4f0a7a1e2195bd6b Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 21 Jun 2023 08:50:01 -0500 Subject: [PATCH] Update release process (#192) --- .github/workflows/publish.yml | 27 ++++++++++++++++ .github/workflows/test.yml | 2 +- apps/platform/Dockerfile | 2 +- apps/platform/nodemon.json | 2 +- apps/platform/package-lock.json | 4 +-- apps/platform/package.json | 11 +++++-- apps/platform/src/api.ts | 23 ++++++++++++-- apps/platform/src/app.ts | 42 ++++++++++++++++--------- apps/platform/src/boot.ts | 5 +++ apps/platform/src/config/controllers.ts | 26 +++++++-------- apps/platform/src/config/database.ts | 6 ++-- apps/platform/src/config/env.ts | 1 + apps/platform/src/config/queue.ts | 8 ----- apps/platform/src/core/validate.ts | 2 +- apps/platform/src/index.ts | 7 +++-- apps/platform/src/providers/Provider.ts | 2 +- apps/platform/src/worker.ts | 10 ++++-- apps/ui/package-lock.json | 4 +-- apps/ui/package.json | 9 ++++-- package-lock.json | 18 ++++++----- package.json | 3 +- tsconfig.base.json | 3 +- 22 files changed, 147 insertions(+), 70 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 apps/platform/src/boot.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..4f2fe8fa --- /dev/null +++ b/.github/workflows/publish.yml @@ -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}} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 48b625ac..70020682 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,7 @@ jobs: run: npm install && npm install --save-dev - name: 'Run Jest Tests' - run: npm run test -- --scope=platform + run: npm run test -- --scope=@parcelvoy/platform env: NODE_ENV: test APP_SECRET: testing diff --git a/apps/platform/Dockerfile b/apps/platform/Dockerfile index 45b3f5c9..bc9aaa43 100644 --- a/apps/platform/Dockerfile +++ b/apps/platform/Dockerfile @@ -24,4 +24,4 @@ USER node WORKDIR /usr/src/app COPY --chown=node:node --from=build /usr/src/app ./ EXPOSE 3001 -CMD ["dumb-init", "node", "index.js"] +CMD ["dumb-init", "node", "boot.js"] diff --git a/apps/platform/nodemon.json b/apps/platform/nodemon.json index e21234b5..8d6d599e 100644 --- a/apps/platform/nodemon.json +++ b/apps/platform/nodemon.json @@ -1,6 +1,6 @@ { "ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"], "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" } \ No newline at end of file diff --git a/apps/platform/package-lock.json b/apps/platform/package-lock.json index 2ebceb38..d6e37c46 100644 --- a/apps/platform/package-lock.json +++ b/apps/platform/package-lock.json @@ -1,11 +1,11 @@ { - "name": "platform", + "name": "@parcelvoy/platform", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "platform", + "name": "@parcelvoy/platform", "version": "0.1.0", "dependencies": { "@aws-sdk/client-s3": "^3.171.0", diff --git a/apps/platform/package.json b/apps/platform/package.json index 7b23f882..3f7fd3ee 100644 --- a/apps/platform/package.json +++ b/apps/platform/package.json @@ -1,6 +1,9 @@ { - "name": "platform", + "name": "@parcelvoy/platform", "version": "0.1.0", + "repository":"https://github.com/parcelvoy/platform", + "main": "build/index.js", + "types": "build/index.d.ts", "dependencies": { "@aws-sdk/client-s3": "^3.171.0", "@aws-sdk/client-ses": "^3.121.0", @@ -56,7 +59,8 @@ "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: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": { "@types/busboy": "^1.5.0", @@ -83,5 +87,6 @@ "ts-jest": "^28.0.7", "ts-node": "^10.8.2", "typescript": "^4.9.3" - } + }, + "files": [ "/build", "/db" ] } diff --git a/apps/platform/src/api.ts b/apps/platform/src/api.ts index f0d4d7d4..d1a1f542 100644 --- a/apps/platform/src/api.ts +++ b/apps/platform/src/api.ts @@ -2,11 +2,15 @@ import Koa from 'koa' import koaBody from 'koa-body' import cors from '@koa/cors' import serve from 'koa-static' -import controllers from './config/controllers' +import controllers, { register } from './config/controllers' import { RequestError } from './core/errors' import { logger } from './config/logger' +import Router from '@koa/router' export default class Api extends Koa { + router = new Router({ prefix: '/api' }) + controllers?: Record + constructor( public app: import('./app').default, ) { @@ -52,6 +56,21 @@ export default class Api extends Koa { 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()) } } diff --git a/apps/platform/src/app.ts b/apps/platform/src/app.ts index 67d386f5..444b74a7 100644 --- a/apps/platform/src/app.ts +++ b/apps/platform/src/app.ts @@ -22,7 +22,7 @@ export default class App { return App.$main } - static async init(env: Env): Promise { + static async init(this: T, env: Env): Promise> { logger.info('parcelvoy initializing') @@ -42,15 +42,20 @@ export default class App { const auth = loadAuth(env.auth) // Setup app - App.$main = new App(env, + const app = new this(env, database, queue, auth, storage, error, - ) + ) as any - return App.$main + return this.setMain(app) + } + + static setMain(this: T, app: InstanceType) { + this.$main = app + return app } uuid = uuid() @@ -59,8 +64,7 @@ export default class App { rateLimiter: RateLimiter #registered: { [key: string | number]: unknown } - // eslint-disable-next-line no-useless-constructor - private constructor( + constructor( public env: Env, public db: Database, public queue: Queue, @@ -73,23 +77,31 @@ export default class App { this.unhandledErrorListener() } - async start() { + start() { const runners = this.env.runners if (runners.includes('api')) { - this.api = new Api(this) - const server = this.api?.listen(this.env.port) - server.keepAliveTimeout = 65000 - server.requestTimeout = 0 - logger.info('parcelvoy:api ready') + this.startApi() } if (runners.includes('worker')) { - this.worker = new Worker(this) - this.worker?.run() - logger.info('parcelvoy:worker ready') + this.startWorker() } 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() { await this.worker?.close() await this.db.destroy() diff --git a/apps/platform/src/boot.ts b/apps/platform/src/boot.ts new file mode 100644 index 00000000..8c69f639 --- /dev/null +++ b/apps/platform/src/boot.ts @@ -0,0 +1,5 @@ +import App from './app' +import env from './config/env' + +export default App.init(env()) + .then(app => app.start()) diff --git a/apps/platform/src/config/controllers.ts b/apps/platform/src/config/controllers.ts index 5da1979a..30f9c709 100644 --- a/apps/platform/src/config/controllers.ts +++ b/apps/platform/src/config/controllers.ts @@ -20,28 +20,25 @@ import ProjectAdminController from '../projects/ProjectAdminController' import ProjectApiKeyController from '../projects/ProjectApiKeyController' import AdminController from '../auth/AdminController' 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) { parent.use(router.routes(), router.allowedMethods()) } return parent } -export default (api: import('../api').default) => { +export default (app: App) => { - // Register the three main levels of routers - const root = register(new Router({ prefix: '/api' }), - adminRouter(), - clientRouter(), - publicRouter(), - ) - - api.use(root.routes()) - .use(root.allowedMethods()) + const routers: Record = { + admin: adminRouter(), + client: clientRouter(), + public: publicRouter(), + } // 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() ui.get('/(.*)', async (ctx, next) => { try { @@ -50,9 +47,10 @@ export default (api: import('../api').default) => { return next() } }) - api.use(ui.routes()) - .use(ui.allowedMethods()) + routers.ui = ui } + + return routers } /** diff --git a/apps/platform/src/config/database.ts b/apps/platform/src/config/database.ts index 35cce26a..2e50fbac 100644 --- a/apps/platform/src/config/database.ts +++ b/apps/platform/src/config/database.ts @@ -1,4 +1,5 @@ import knex, { Knex as Database } from 'knex' +import path from 'path' import { removeKey } from '../utilities' import { logger } from './logger' @@ -10,6 +11,7 @@ export interface DatabaseConfig { user: string password: string database?: string + migrationPaths: string[] } export type Query = (builder: Database.QueryBuilder) => Database.QueryBuilder @@ -23,7 +25,7 @@ knex.QueryBuilder.extend('when', function( }) const connect = (config: DatabaseConfig, withDB = true) => { - let connection = config + let connection = removeKey('migrationPaths', config) if (!withDB) { connection = removeKey('database', connection) } @@ -45,7 +47,7 @@ const connect = (config: DatabaseConfig, withDB = true) => { const migrate = async (config: DatabaseConfig, db: Database, fresh = false) => { if (fresh) await db.raw(`CREATE DATABASE ${config.database}`) return db.migrate.latest({ - directory: './db/migrations', + directory: [path.resolve(__dirname, '../../db/migrations'), ...config.migrationPaths], tableName: 'migrations', loadExtensions: ['.js', '.ts'], }) diff --git a/apps/platform/src/config/env.ts b/apps/platform/src/config/env.ts index b3615e2b..6a3b40b1 100644 --- a/apps/platform/src/config/env.ts +++ b/apps/platform/src/config/env.ts @@ -55,6 +55,7 @@ export default (type?: EnvType): Env => { password: process.env.DB_PASSWORD!, port: parseInt(process.env.DB_PORT!), database: process.env.DB_DATABASE!, + migrationPaths: process.env.DB_MIGRATION_PATHS?.split(',') ?? [], }, redis: { host: process.env.REDIS_HOST!, diff --git a/apps/platform/src/config/queue.ts b/apps/platform/src/config/queue.ts index 6ff0df8c..e16b6b9b 100644 --- a/apps/platform/src/config/queue.ts +++ b/apps/platform/src/config/queue.ts @@ -21,8 +21,6 @@ import UserAliasJob from '../users/UserAliasJob' import UserSchemaSyncJob from '../schema/UserSchemaSyncJob' import UserDeviceJob from '../users/UserDeviceJob' -export type Queues = Record - export const loadJobs = (queue: Queue) => { queue.register(CampaignGenerateListJob) queue.register(CampaignInteractJob) @@ -49,9 +47,3 @@ export const loadJobs = (queue: Queue) => { export default (config: QueueConfig) => { return new Queue(config) } - -export const loadWorker = (config: QueueConfig) => { - const queue = new Queue(config) - loadJobs(queue) - return queue -} diff --git a/apps/platform/src/core/validate.ts b/apps/platform/src/core/validate.ts index b7f9b4a4..1ebf7ba5 100644 --- a/apps/platform/src/core/validate.ts +++ b/apps/platform/src/core/validate.ts @@ -12,7 +12,7 @@ addErrors(validator) export { JSONSchemaType, IsValidSchema, validator } -export const validate = (schema: JSONSchemaType, data: any): T => { +export function validate(schema: JSONSchemaType, data: any): T { const validate = validator.getSchema(schema.$id!) || validator.compile(schema) if (validate(data)) { diff --git a/apps/platform/src/index.ts b/apps/platform/src/index.ts index 8c69f639..abbc3547 100644 --- a/apps/platform/src/index.ts +++ b/apps/platform/src/index.ts @@ -1,5 +1,8 @@ +import Api from './api' import App from './app' import env from './config/env' +import Model from './core/Model' +import Worker from './worker' +import Job from './queue/Job' -export default App.init(env()) - .then(app => app.start()) +export { App, env, Model, Api, Worker, Job } diff --git a/apps/platform/src/providers/Provider.ts b/apps/platform/src/providers/Provider.ts index d43a01eb..c9c182b2 100644 --- a/apps/platform/src/providers/Provider.ts +++ b/apps/platform/src/providers/Provider.ts @@ -18,7 +18,7 @@ export interface ProviderSetupMeta { value: string | number } -export const ProviderSchema = (id: string, data: JSONSchemaType): JSONSchemaType => { +export function ProviderSchema<_ extends ExternalProviderParams, D>(id: string, data: JSONSchemaType): any { return { $id: id, type: 'object', diff --git a/apps/platform/src/worker.ts b/apps/platform/src/worker.ts index fdf89e2b..4b7ef3be 100644 --- a/apps/platform/src/worker.ts +++ b/apps/platform/src/worker.ts @@ -1,15 +1,17 @@ -import { loadWorker } from './config/queue' +import { loadJobs } from './config/queue' import scheduler, { Scheduler } from './config/scheduler' import Queue from './queue' export default class Worker { worker: Queue scheduler: Scheduler + constructor( public app: import('./app').default, ) { - this.worker = loadWorker(app.env.queue) + this.worker = new Queue(app.env.queue) this.scheduler = scheduler(app) + this.loadJobs() } run() { @@ -20,4 +22,8 @@ export default class Worker { await this.worker.close() await this.scheduler.close() } + + loadJobs() { + loadJobs(this.worker) + } } diff --git a/apps/ui/package-lock.json b/apps/ui/package-lock.json index 7e41e96a..40f63aeb 100644 --- a/apps/ui/package-lock.json +++ b/apps/ui/package-lock.json @@ -1,11 +1,11 @@ { - "name": "ui", + "name": "@parcelvoy/ui", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ui", + "name": "@parcelvoy/ui", "version": "0.1.0", "dependencies": { "@fontsource/inter": "^4.5.14", diff --git a/apps/ui/package.json b/apps/ui/package.json index 775d70b2..4202cc41 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -1,6 +1,7 @@ { - "name": "ui", + "name": "@parcelvoy/ui", "version": "0.1.0", + "repository":"https://github.com/parcelvoy/platform", "dependencies": { "@fontsource/inter": "^4.5.14", "@headlessui/react": "1.7.13", @@ -45,7 +46,8 @@ "eject": "react-scripts eject", "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: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": { "production": [ @@ -68,5 +70,6 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.31.11", "typescript": "^4.9.3" - } + }, + "files": [ "/build" ] } diff --git a/package-lock.json b/package-lock.json index 93ddcab7..589625a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ } }, "apps/platform": { + "name": "@parcelvoy/platform", "version": "0.1.0", "dependencies": { "@aws-sdk/client-s3": "^3.171.0", @@ -137,6 +138,7 @@ } }, "apps/ui": { + "name": "@parcelvoy/ui", "version": "0.1.0", "dependencies": { "@fontsource/inter": "^4.5.14", @@ -6829,6 +6831,14 @@ "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": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", @@ -23044,10 +23054,6 @@ "node": ">=4" } }, - "node_modules/platform": { - "resolved": "apps/platform", - "link": true - }, "node_modules/postcss": { "version": "8.4.22", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.22.tgz", @@ -29409,10 +29415,6 @@ "node": ">=0.8.0" } }, - "node_modules/ui": { - "resolved": "apps/ui", - "link": true - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index e758e603..2a5f67a3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test": "lerna run test", "docker:build": "lerna run docker:build", "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" } } diff --git a/tsconfig.base.json b/tsconfig.base.json index 940a0083..6cb17754 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -12,7 +12,8 @@ "pretty": true, "sourceMap": true, "allowJs": true, - "noEmit": false + "noEmit": false, + "declaration": true } } \ No newline at end of file