diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 6ff2a9065da65..56d1485709dbd 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -15,6 +15,7 @@ "test:coverage": "c8 ava --concurrency 1 --serial", "postinstall": "prisma generate", "data-migration": "node --loader ts-node/esm/transpile-only.mjs ./src/data/index.ts", + "make-action": "node --loader ts-node/esm/transpile-only.mjs ./src/core/bootstrap/bin/index.ts create", "predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run" }, "dependencies": { diff --git a/packages/backend/server/src/app.module.ts b/packages/backend/server/src/app.module.ts index 3948a0b282c04..1e952d8739138 100644 --- a/packages/backend/server/src/app.module.ts +++ b/packages/backend/server/src/app.module.ts @@ -7,6 +7,7 @@ import { get } from 'lodash-es'; import { AppController } from './app.controller'; import { AuthModule } from './core/auth'; +import { BootstrapModule } from './core/bootstrap'; import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config'; import { DocModule } from './core/doc'; import { FeatureModule } from './core/features'; @@ -129,6 +130,7 @@ function buildAppModule() { // graphql server only .useIf( config => config.flavor.graphql, + BootstrapModule, ServerConfigModule, GqlModule, StorageModule, diff --git a/packages/backend/server/src/core/bootstrap/actions/1716659387824-upgrade-prompts.ts b/packages/backend/server/src/core/bootstrap/actions/1716659387824-upgrade-prompts.ts new file mode 100644 index 0000000000000..244fb8e612043 --- /dev/null +++ b/packages/backend/server/src/core/bootstrap/actions/1716659387824-upgrade-prompts.ts @@ -0,0 +1,10 @@ +import { PrismaClient } from '@prisma/client'; + +import { refreshPrompts } from './utils/prompts'; + +export class UpgradePrompts1716659387824 { + // do the action + static async run(db: PrismaClient) { + await refreshPrompts(db); + } +} diff --git a/packages/backend/server/src/data/migrations/utils/prompts.ts b/packages/backend/server/src/core/bootstrap/actions/utils/prompts.ts similarity index 100% rename from packages/backend/server/src/data/migrations/utils/prompts.ts rename to packages/backend/server/src/core/bootstrap/actions/utils/prompts.ts diff --git a/packages/backend/server/src/core/bootstrap/bin/app.ts b/packages/backend/server/src/core/bootstrap/bin/app.ts new file mode 100644 index 0000000000000..a92a55831761c --- /dev/null +++ b/packages/backend/server/src/core/bootstrap/bin/app.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; + +import { AppModule as BusinessAppModule } from '../../../app.module'; +import { ConfigModule } from '../../../fundamentals/config'; +import { CreateCommand, NameQuestion } from './create'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + doc: { + manager: { + enableUpdateAutoMerging: false, + }, + }, + metrics: { + enabled: false, + customerIo: {}, + }, + }), + BusinessAppModule, + ], + providers: [NameQuestion, CreateCommand], +}) +export class CliAppModule {} diff --git a/packages/backend/server/src/core/bootstrap/bin/create.ts b/packages/backend/server/src/core/bootstrap/bin/create.ts new file mode 100644 index 0000000000000..06027b06d7521 --- /dev/null +++ b/packages/backend/server/src/core/bootstrap/bin/create.ts @@ -0,0 +1,71 @@ +import { writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { Logger } from '@nestjs/common'; +import { camelCase, kebabCase, upperFirst } from 'lodash-es'; +import { + Command, + CommandRunner, + InquirerService, + Question, + QuestionSet, +} from 'nest-commander'; + +@QuestionSet({ name: 'name-questions' }) +export class NameQuestion { + @Question({ + name: 'name', + message: 'Name of the data action script:', + }) + parseName(val: string) { + return val.trim(); + } +} + +@Command({ + name: 'create', + arguments: '[name]', + description: 'create a bootstrap action script', +}) +export class CreateCommand extends CommandRunner { + logger = new Logger(CreateCommand.name); + constructor(private readonly inquirer: InquirerService) { + super(); + } + + override async run(inputs: string[]): Promise { + let name = inputs[0]; + + if (!name) { + name = ( + await this.inquirer.ask<{ name: string }>('name-questions', undefined) + ).name; + } + + const timestamp = Date.now(); + const content = this.createScript(upperFirst(camelCase(name)) + timestamp); + const fileName = `${timestamp}-${kebabCase(name)}.ts`; + const filePath = join( + fileURLToPath(import.meta.url), + '../../actions', + fileName + ); + + this.logger.log(`Creating ${fileName}...`); + writeFileSync(filePath, content); + this.logger.log('Action file created at', filePath); + this.logger.log('Done'); + } + + private createScript(name: string) { + return ` +import { PrismaClient } from '@prisma/client'; + +export class ${name} { + // do the action + static async run(db: PrismaClient) {} +} +`; + } +} diff --git a/packages/backend/server/src/core/bootstrap/bin/index.ts b/packages/backend/server/src/core/bootstrap/bin/index.ts new file mode 100644 index 0000000000000..b77e40104cc22 --- /dev/null +++ b/packages/backend/server/src/core/bootstrap/bin/index.ts @@ -0,0 +1,15 @@ +import '../../../prelude'; + +import { Logger } from '@nestjs/common'; +import { CommandFactory } from 'nest-commander'; + +async function bootstrap() { + const { CliAppModule } = await import('./app'); + await CommandFactory.run(CliAppModule, new Logger()).catch(e => { + console.error(e); + process.exit(1); + }); + process.exit(0); +} + +await bootstrap(); diff --git a/packages/backend/server/src/core/bootstrap/index.ts b/packages/backend/server/src/core/bootstrap/index.ts new file mode 100644 index 0000000000000..3e2c35febc925 --- /dev/null +++ b/packages/backend/server/src/core/bootstrap/index.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { BootstrapService } from './service'; + +@Module({ + providers: [BootstrapService], + exports: [BootstrapService], +}) +export class BootstrapModule {} diff --git a/packages/backend/server/src/core/bootstrap/service.ts b/packages/backend/server/src/core/bootstrap/service.ts new file mode 100644 index 0000000000000..c361bdf4853cd --- /dev/null +++ b/packages/backend/server/src/core/bootstrap/service.ts @@ -0,0 +1,72 @@ +import { readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { PrismaClient } from '@prisma/client'; + +interface Action { + file: string; + name: string; + run: (db: PrismaClient, injector: ModuleRef) => Promise; +} + +@Injectable() +export class BootstrapService implements OnModuleInit { + private readonly logger = new Logger(BootstrapService.name); + + constructor( + private readonly db: PrismaClient, + private readonly injector: ModuleRef + ) {} + + async onModuleInit() { + const actions = await this.collectActions(); + + for (const action of actions) { + await this.runAction(action); + } + } + + private async collectActions(): Promise { + const folder = join(fileURLToPath(import.meta.url), '../actions'); + + const actionFiles = readdirSync(folder) + .filter(desc => + desc.endsWith(import.meta.url.endsWith('.ts') ? '.ts' : '.js') + ) + .map(desc => join(folder, desc)); + + actionFiles.sort((a, b) => a.localeCompare(b)); + + const actions: Action[] = ( + await Promise.all( + actionFiles.map(async file => { + const path = pathToFileURL(file); + return import(path.href).then(mod => { + const { name, run } = mod[Object.keys(mod)[0]]; + if (!name || !run || typeof run !== 'function') { + this.logger.warn(`Uncompleted action: ${path.pathname}`); + return null; + } + return { file, name, run }; + }); + }) + ) + ).filter((v): v is Action => !!v); + + return actions; + } + + private async runAction(action: Action) { + this.logger.log(`Running ${action.name}...`); + + try { + await action.run(this.db, this.injector); + } catch (e) { + this.logger.error(`Failed to run action ${action.name}: `, e); + process.exit(1); + } + } +} diff --git a/packages/backend/server/src/data/migrations/1712068777394-prompts.ts b/packages/backend/server/src/data/migrations/1712068777394-prompts.ts deleted file mode 100644 index e6b5ecc71fa93..0000000000000 --- a/packages/backend/server/src/data/migrations/1712068777394-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class Prompts1712068777394 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1713185798895-refresh-prompt.ts b/packages/backend/server/src/data/migrations/1713185798895-refresh-prompt.ts deleted file mode 100644 index 82b3525b14d99..0000000000000 --- a/packages/backend/server/src/data/migrations/1713185798895-refresh-prompt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class RefreshPrompt1713185798895 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1713522040090-update-prompt.ts b/packages/backend/server/src/data/migrations/1713522040090-update-prompt.ts deleted file mode 100644 index 07ae8fc2d3a14..0000000000000 --- a/packages/backend/server/src/data/migrations/1713522040090-update-prompt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompt1713522040090 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1713777617122-update-prompts.ts b/packages/backend/server/src/data/migrations/1713777617122-update-prompts.ts deleted file mode 100644 index e659be58a3b6a..0000000000000 --- a/packages/backend/server/src/data/migrations/1713777617122-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1713777617122 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1713864641056-update-prompt.ts b/packages/backend/server/src/data/migrations/1713864641056-update-prompt.ts deleted file mode 100644 index 7fc7af0e0ac5f..0000000000000 --- a/packages/backend/server/src/data/migrations/1713864641056-update-prompt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompt1713864641056 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1714021969665-update-prompts.ts b/packages/backend/server/src/data/migrations/1714021969665-update-prompts.ts deleted file mode 100644 index 5b9ead1df2e04..0000000000000 --- a/packages/backend/server/src/data/migrations/1714021969665-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1714021969665 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1714386922280-update-prompts.ts b/packages/backend/server/src/data/migrations/1714386922280-update-prompts.ts deleted file mode 100644 index d1a47fca39e05..0000000000000 --- a/packages/backend/server/src/data/migrations/1714386922280-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1714386922280 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1714454280973-update-prompts.ts b/packages/backend/server/src/data/migrations/1714454280973-update-prompts.ts deleted file mode 100644 index 5e1b8a2c439ac..0000000000000 --- a/packages/backend/server/src/data/migrations/1714454280973-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1714454280973 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1714982671938-update-prompts.ts b/packages/backend/server/src/data/migrations/1714982671938-update-prompts.ts deleted file mode 100644 index e0279c5b985ce..0000000000000 --- a/packages/backend/server/src/data/migrations/1714982671938-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1714982671938 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1714992100105-update-prompts.ts b/packages/backend/server/src/data/migrations/1714992100105-update-prompts.ts deleted file mode 100644 index 33dc62ec2f759..0000000000000 --- a/packages/backend/server/src/data/migrations/1714992100105-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1714992100105 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1714998654392-update-prompts.ts b/packages/backend/server/src/data/migrations/1714998654392-update-prompts.ts deleted file mode 100644 index 6b345e1e77258..0000000000000 --- a/packages/backend/server/src/data/migrations/1714998654392-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1714998654392 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1715149980782-add-make-it-real-with-text-prompt.ts b/packages/backend/server/src/data/migrations/1715149980782-add-make-it-real-with-text-prompt.ts deleted file mode 100644 index cd14728abded0..0000000000000 --- a/packages/backend/server/src/data/migrations/1715149980782-add-make-it-real-with-text-prompt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class AddMakeItRealWithTextPrompt1715149980782 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1715672224087-update-prompts.ts b/packages/backend/server/src/data/migrations/1715672224087-update-prompts.ts deleted file mode 100644 index 01530068ade68..0000000000000 --- a/packages/backend/server/src/data/migrations/1715672224087-update-prompts.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1715672224087 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(db: PrismaClient) { - await db.aiPrompt.updateMany({ - where: { - model: 'gpt-4o', - }, - data: { - model: 'gpt-4-vision-preview', - }, - }); - } -} diff --git a/packages/backend/server/src/data/migrations/1715936358947-update-prompts.ts b/packages/backend/server/src/data/migrations/1715936358947-update-prompts.ts deleted file mode 100644 index c7a18b2388dd7..0000000000000 --- a/packages/backend/server/src/data/migrations/1715936358947-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1715936358947 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/src/data/migrations/1716451792364-update-prompts.ts b/packages/backend/server/src/data/migrations/1716451792364-update-prompts.ts deleted file mode 100644 index 1a07b7e02d942..0000000000000 --- a/packages/backend/server/src/data/migrations/1716451792364-update-prompts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -import { refreshPrompts } from './utils/prompts'; - -export class UpdatePrompts1716451792364 { - // do the migration - static async up(db: PrismaClient) { - await refreshPrompts(db); - } - - // revert the migration - static async down(_db: PrismaClient) {} -} diff --git a/packages/backend/server/tests/copilot.e2e.ts b/packages/backend/server/tests/copilot.e2e.ts index 22ce793c5c020..80d766b574d92 100644 --- a/packages/backend/server/tests/copilot.e2e.ts +++ b/packages/backend/server/tests/copilot.e2e.ts @@ -8,8 +8,8 @@ import ava from 'ava'; import Sinon from 'sinon'; import { AuthService } from '../src/core/auth'; +import { prompts } from '../src/core/bootstrap/actions/utils/prompts'; import { WorkspaceModule } from '../src/core/workspaces'; -import { prompts } from '../src/data/migrations/utils/prompts'; import { ConfigModule } from '../src/fundamentals/config'; import { CopilotModule } from '../src/plugins/copilot'; import { PromptService } from '../src/plugins/copilot/prompt'; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/prompt.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/prompt.ts index 493767f4e6548..c7c042fbdd294 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/prompt.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/prompt.ts @@ -1,4 +1,4 @@ -// manually synced with packages/backend/server/src/data/migrations/utils/prompts.ts +// manually synced with packages/backend/server/src/core/bootstrap/actions/utils/prompts.ts // todo: automate this export const promptKeys = [ 'debug:chat:gpt4',