From d76a1f24e5437c3d483d73b1498c6d6da320d631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 26 Aug 2024 14:29:11 +0200 Subject: [PATCH 1/2] Provide a ServiceManager to shell scripts --- docs/docs/authentication/session-tokens.md | 12 +++---- docs/docs/cli/shell-scripts.md | 6 ++-- package-lock.json | 1 + .../revoking-sessions.feature.ts | 20 ++++++------ packages/cli/package.json | 1 + .../src/generate/specs/script/test-foo-bar.ts | 4 ++- .../src/generate/templates/script/script.ts | 4 ++- packages/cli/src/run/run-script.spec.ts | 32 +++++++++++++++++++ packages/cli/src/run/run-script.ts | 13 ++++++-- packages/examples/src/scripts/revoke-token.ts | 6 ++-- 10 files changed, 73 insertions(+), 26 deletions(-) diff --git a/docs/docs/authentication/session-tokens.md b/docs/docs/authentication/session-tokens.md index 2f8d0d9e62..904dcdded8 100644 --- a/docs/docs/authentication/session-tokens.md +++ b/docs/docs/authentication/session-tokens.md @@ -641,7 +641,7 @@ npx foal g script revoke-session Open `scripts/revoke-session.ts` and update its content. ```typescript -import { createService, readSession, Store } from '@foal/core'; +import { readSession, ServiceManager, Store } from '@foal/core'; import { dataSource } from '../db'; @@ -653,10 +653,10 @@ export const schema = { required: [ 'token' ] } -export async function main({ token }: { token: string }) { +export async function main({ token }: { token: string }, services: ServiceManager) { await dataSource.initialize(); - const store = createService(Store); + const store = services.get(Store); await store.boot(); const session = await readSession(store, token); @@ -687,14 +687,14 @@ npx foal g script revoke-all-sessions Open `scripts/revoke-all-sessions.ts` and update its content. ```typescript -import { createService, Store } from '@foal/core'; +import { ServiceManager, Store } from '@foal/core'; import { dataSource } from '../db'; -export async function main() { +export async function main(args: any, services: ServiceManager) { await dataSource.initialize(); - const store = createService(Store); + const store = services.get(Store); await store.boot(); await store.clear(); } diff --git a/docs/docs/cli/shell-scripts.md b/docs/docs/cli/shell-scripts.md index e139eef109..2ea9904374 100644 --- a/docs/docs/cli/shell-scripts.md +++ b/docs/docs/cli/shell-scripts.md @@ -17,19 +17,19 @@ Remove the content of `src/scripts/display-users.ts` and replace it with the cod ```typescript // 3p -import { createService } from '@foal/core'; +import { ServiceManager } from '@foal/core'; // App import { dataSource } from '../db'; import { User } from '../app/entities'; import { Logger } from '../app/services'; -export async function main() { +export async function main(args: any, services: ServiceManager) { await dataSource.initialize(); try { const users = await User.find(); - const logger = createService(Logger); + const logger = services.get(Logger); logger.log(users); } finally { dataSource.destroy(); diff --git a/package-lock.json b/package-lock.json index ab6a115ab8..6ed03b284d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15251,6 +15251,7 @@ "foal": "lib/index.js" }, "devDependencies": { + "@foal/core": "^4.5.0", "@types/mocha": "10.0.7", "@types/node": "20.14.8", "copyfiles": "~2.4.1", diff --git a/packages/acceptance-tests/src/docs/authentication/session-tokens/revoking-sessions.feature.ts b/packages/acceptance-tests/src/docs/authentication/session-tokens/revoking-sessions.feature.ts index a6a8639dac..a15d08ad2a 100644 --- a/packages/acceptance-tests/src/docs/authentication/session-tokens/revoking-sessions.feature.ts +++ b/packages/acceptance-tests/src/docs/authentication/session-tokens/revoking-sessions.feature.ts @@ -5,7 +5,7 @@ import { notStrictEqual, strictEqual } from 'assert'; import { DataSource } from 'typeorm'; // FoalTS -import { Config, createService, createSession, readSession, Store } from '@foal/core'; +import { Config, createSession, readSession, ServiceManager, Store } from '@foal/core'; import { DatabaseSession } from '@foal/typeorm'; import { createAndInitializeDataSource, getTypeORMStorePath } from '../../../common'; @@ -28,10 +28,10 @@ describe('Feature: Revoking sessions', () => { /* ======================= DOCUMENTATION BEGIN ======================= */ - async function main({ token }: { token: string }) { + async function main({ token }: { token: string }, services: ServiceManager) { // await dataSource.initialize(); - const store = createService(Store); + const store = services.get(Store); await store.boot(); const session = await readSession(store, token); @@ -42,7 +42,8 @@ describe('Feature: Revoking sessions', () => { /* ======================= DOCUMENTATION END ========================= */ - const store = createService(Store); + const services = new ServiceManager(); + const store = services.get(Store); dataSource = await createAndInitializeDataSource([ DatabaseSession ]); @@ -51,7 +52,7 @@ describe('Feature: Revoking sessions', () => { notStrictEqual(await readSession(store, session.getToken()), null); - await main({ token: session.getToken() }); + await main({ token: session.getToken() }, services); strictEqual(await readSession(store, session.getToken()), null); @@ -61,17 +62,18 @@ describe('Feature: Revoking sessions', () => { /* ======================= DOCUMENTATION BEGIN ======================= */ - async function main() { + async function main(args: any, services: ServiceManager) { // await dataSource.initialize(); - const store = createService(Store); + const store = services.get(Store); await store.boot(); await store.clear(); } /* ======================= DOCUMENTATION END ========================= */ - const store = createService(Store); + const services = new ServiceManager(); + const store = services.get(Store); dataSource = await createAndInitializeDataSource([ DatabaseSession ]); @@ -83,7 +85,7 @@ describe('Feature: Revoking sessions', () => { notStrictEqual(await readSession(store, session.getToken()), null); notStrictEqual(await readSession(store, session2.getToken()), null); - await main(); + await main(undefined, services); strictEqual(await readSession(store, session.getToken()), null); strictEqual(await readSession(store, session2.getToken()), null); diff --git a/packages/cli/package.json b/packages/cli/package.json index 99a844e33e..d0adc3f67c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -64,6 +64,7 @@ "commander": "~12.1.0" }, "devDependencies": { + "@foal/core": "^4.5.0", "@types/mocha": "10.0.7", "@types/node": "20.14.8", "copyfiles": "~2.4.1", diff --git a/packages/cli/src/generate/specs/script/test-foo-bar.ts b/packages/cli/src/generate/specs/script/test-foo-bar.ts index f9e39a2362..eb9c6fd568 100644 --- a/packages/cli/src/generate/specs/script/test-foo-bar.ts +++ b/packages/cli/src/generate/specs/script/test-foo-bar.ts @@ -1,3 +1,5 @@ +import { ServiceManager } from '@foal/core'; + export const schema = { additionalProperties: false, properties: { @@ -9,6 +11,6 @@ export const schema = { type: 'object', }; -export async function main(args: any) { +export async function main(args: any, services: ServiceManager) { } diff --git a/packages/cli/src/generate/templates/script/script.ts b/packages/cli/src/generate/templates/script/script.ts index f9e39a2362..eb9c6fd568 100644 --- a/packages/cli/src/generate/templates/script/script.ts +++ b/packages/cli/src/generate/templates/script/script.ts @@ -1,3 +1,5 @@ +import { ServiceManager } from '@foal/core'; + export const schema = { additionalProperties: false, properties: { @@ -9,6 +11,6 @@ export const schema = { type: 'object', }; -export async function main(args: any) { +export async function main(args: any, services: ServiceManager) { } diff --git a/packages/cli/src/run/run-script.spec.ts b/packages/cli/src/run/run-script.spec.ts index a135e0574f..48f059d225 100644 --- a/packages/cli/src/run/run-script.spec.ts +++ b/packages/cli/src/run/run-script.spec.ts @@ -162,6 +162,38 @@ describe('runScript', () => { }); }); + it('should call the "main" function of build/scripts/my-script.js with a ServiceManager.', async () => { + mkdirIfDoesNotExist('build/scripts'); + const scriptContent = `const { writeFileSync } = require('fs'); + const { ServiceManager } = require('@foal/core'); + module.exports.main = function main(args, services) { + const isServiceManager = services instanceof ServiceManager; + writeFileSync('my-script-temp', JSON.stringify({ + isServiceManager + }), 'utf8'); + }`; + writeFileSync('build/scripts/my-script.js', scriptContent, 'utf8'); + + delete require.cache[join(process.cwd(), `./build/scripts/my-script.js`)]; + + await runScript({ name: 'my-script' }, [ + '/Users/loicpoullain/.nvm/versions/node/v8.11.3/bin/node', + '/Users/loicpoullain/.nvm/versions/node/v8.11.3/bin/foal', + 'run', + 'my-script', + 'foo=bar', + ]); + + if (!existsSync('my-script-temp')) { + throw new Error('The script was not executed'); + } + const actual = JSON.parse(readFileSync('my-script-temp', 'utf8')); + + deepStrictEqual(actual, { + isServiceManager: true, + }); + }); + it('should catch and log errors thrown in the "main" function.', async () => { mkdirIfDoesNotExist('build/scripts'); const scriptContent = `const { writeFileSync } = require('fs'); diff --git a/packages/cli/src/run/run-script.ts b/packages/cli/src/run/run-script.ts index 61eefef6a5..f78a847b81 100644 --- a/packages/cli/src/run/run-script.ts +++ b/packages/cli/src/run/run-script.ts @@ -1,6 +1,5 @@ // std import { existsSync } from 'fs'; -import { join } from 'path'; // 3p import Ajv from 'ajv'; @@ -10,6 +9,12 @@ import addFormats from 'ajv-formats'; import { getCommandLineArguments } from './get-command-line-arguments.util'; export async function runScript({ name }: { name: string }, argv: string[], log = console.log) { + const { ServiceManager } = require(require.resolve('@foal/core', { + paths: [ process.cwd() ], + })) as typeof import('@foal/core'); + + const services = new ServiceManager(); + if (!existsSync(`build/scripts/${name}.js`)) { if (existsSync(`src/scripts/${name}.ts`)) { log( @@ -22,7 +27,9 @@ export async function runScript({ name }: { name: string }, argv: string[], log return; } - const { main, schema } = require(join(process.cwd(), `./build/scripts/${name}`)); + const { main, schema } = require(require.resolve(`./build/scripts/${name}`, { + paths: [ process.cwd() ], + })); if (!main) { log(`Error: No "main" function was found in build/scripts/${name}.js.`); @@ -47,7 +54,7 @@ export async function runScript({ name }: { name: string }, argv: string[], log } try { - await main(args); + await main(args, services); } catch (error: any) { log(error); } diff --git a/packages/examples/src/scripts/revoke-token.ts b/packages/examples/src/scripts/revoke-token.ts index b51b1715b5..527e3b6175 100644 --- a/packages/examples/src/scripts/revoke-token.ts +++ b/packages/examples/src/scripts/revoke-token.ts @@ -1,5 +1,5 @@ // 3p -import { createService } from '@foal/core'; +import { ServiceManager } from '@foal/core'; import { TypeORMStore } from '@foal/typeorm'; // App @@ -14,8 +14,8 @@ export const schema = { type: 'object', }; -export async function main({ token }: { token: string }) { +export async function main({ token }: { token: string }, services: ServiceManager) { await dataSource.initialize(); - await createService(TypeORMStore).destroy(token); + await services.get(TypeORMStore).destroy(token); } From 7692b0a8e640ec1ff47146041fdef0180fd30530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 26 Aug 2024 16:52:41 +0200 Subject: [PATCH 2/2] [Blog] Provide a ServiceManager to shell scripts --- docs/blog/version-5.0-release-notes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/blog/version-5.0-release-notes.md b/docs/blog/version-5.0-release-notes.md index fc2f1cbc8c..50ff8fe179 100644 --- a/docs/blog/version-5.0-release-notes.md +++ b/docs/blog/version-5.0-release-notes.md @@ -74,6 +74,16 @@ Version 5.0 of [Foal](https://foalts.org/) is out! - The `Logger.addLogContext(key, value)` method now accepts a record as parameter: `Logger.addLogContext(context)`. This makes the function's signature more consistent with other logging methods (`info`, `warn`, etc.) and allows multiple keys/values to be passed at once. - The deprecated `settings.loggerFormat` configuration has been removed. If you want to disable HTTP logging, set `settings.logger.logHttpRequests` to false instead. +## Shell scripts + +- The `main` function of shell scripts now receives an instance of `ServiceManager` as second argument: + ```typescript + export async function main(args: any, services: ServiceManager) { + // ... + } + ``` + + ## Removal of deprecated components - The deprecated hook `@Log` has been removed. Use the `Logger` service in a custom `@Hook` instead.