diff --git a/packages/core/data-transfer/src/commands/__tests__/commands.test.utils.ts b/packages/core/data-transfer/src/commands/__tests__/commands.test.utils.ts new file mode 100644 index 0000000000..8c4273b039 --- /dev/null +++ b/packages/core/data-transfer/src/commands/__tests__/commands.test.utils.ts @@ -0,0 +1,14 @@ +import type { Utils } from '@strapi/strapi'; + +const expectExit = async (code: number, fn: Utils.Function.Any) => { + const exit = jest.spyOn(process, 'exit').mockImplementation((number) => { + throw new Error(`process.exit: ${number}`); + }); + await expect(async () => { + await fn(); + }).rejects.toThrow('process.exit'); + expect(exit).toHaveBeenCalledWith(code); + exit.mockRestore(); +}; + +export { expectExit }; diff --git a/packages/core/data-transfer/src/commands/__tests__/fixtures/test.pkg.json b/packages/core/data-transfer/src/commands/__tests__/fixtures/test.pkg.json deleted file mode 100644 index 85a32fd882..0000000000 --- a/packages/core/data-transfer/src/commands/__tests__/fixtures/test.pkg.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "testing", - "version": "0.0.0" -} diff --git a/packages/core/data-transfer/src/commands/__tests__/pkg.test.js b/packages/core/data-transfer/src/commands/__tests__/pkg.test.js deleted file mode 100644 index e3252dfb0f..0000000000 --- a/packages/core/data-transfer/src/commands/__tests__/pkg.test.js +++ /dev/null @@ -1,509 +0,0 @@ -'use strict'; - -const fs = require('fs/promises'); -const path = require('path'); - -const { - loadPkg, - validatePkg, - validateExportsOrdering, - parseExports, - getExportExtensionMap, -} = require('../pkg'); - -const loggerMock = { - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), -}; - -describe('pkg', () => { - const tmpfolder = path.resolve(__dirname, '.tmp'); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe('loadPkg', () => { - beforeEach(async () => { - await fs.mkdir(tmpfolder); - await fs.copyFile( - path.resolve(__dirname, 'fixtures', 'test.pkg.json'), - path.resolve(tmpfolder, 'package.json') - ); - }); - - afterEach(async () => { - await fs.rm(tmpfolder, { recursive: true }); - }); - - it('should succesfully load the package.json closest to the cwd provided & call the debug logger', async () => { - const pkg = await loadPkg({ cwd: tmpfolder, logger: loggerMock }); - - expect(pkg).toMatchInlineSnapshot(` - { - "name": "testing", - "version": "0.0.0", - } - `); - - expect(loggerMock.debug).toHaveBeenCalled(); - }); - - it('should throw an error if it cannot find a package.json', async () => { - await expect( - loadPkg({ cwd: '/', logger: loggerMock }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Could not find a package.json in the current directory"` - ); - }); - }); - - describe('validatePkg', () => { - it("should return the validated package.json if it's valid", async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - }; - - const validatedPkg = await validatePkg({ pkg }); - - expect(validatedPkg).toMatchInlineSnapshot(` - { - "name": "testing", - "version": "0.0.0", - } - `); - }); - - it('should fail if a required field is missing and call the error logger with the correct message', async () => { - expect(() => - validatePkg({ - pkg: { - version: '0.0.0', - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'name' in 'package.json' is required as type 'string'"` - ); - - expect(() => - validatePkg({ - pkg: { - name: 'testing', - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'version' in 'package.json' is required as type 'string'"` - ); - }); - - it('should fail if a required field does not match the correct type and call the error logger with the correct message', async () => { - expect(() => - validatePkg({ - pkg: { - name: 'testing', - version: 0, - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'version' in 'package.json' must be of type 'string' (recieved 'number')"` - ); - }); - - it("should fail if the regex for a field doesn't match and call the error logger with the correct message", async () => { - expect(() => - validatePkg({ - pkg: { - name: 'testing', - version: '0.0.0', - exports: { - apple: './apple.xyzx', - }, - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'exports.apple' in 'package.json' must be of type '/^\\.\\/.*\\.json$/' (recieved the value './apple.xyzx')"` - ); - - expect(() => - validatePkg({ - pkg: { - name: 'testing', - version: '0.0.0', - type: 'something', - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'type' in 'package.json' must be of type '/(commonjs|module)/' (recieved the value 'something')"` - ); - }); - - it('should fail if the exports object does not match expectations', async () => { - expect(() => - validatePkg({ - pkg: { - name: 'testing', - version: '0.0.0', - exports: 'hello', - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'exports' in 'package.json' must be of type 'object' (recieved 'string')"` - ); - - expect(() => - validatePkg({ - pkg: { - name: 'testing', - version: '0.0.0', - exports: { - './package.json': './package.json', - './admin': { - import: './admin/index.js', - something: 'xyz', - }, - }, - }, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'exports["./admin"]' in 'package.json' contains the unknown key something, for compatability only the following keys are allowed: ['types', 'source', 'import', 'require', 'default']"` - ); - }); - }); - - describe('validateExportsOrdering', () => { - it('should throw if there are no exports at all and log that error', async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - }; - - await expect( - validateExportsOrdering({ pkg, logger: loggerMock }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"'package.json' must contain a 'main' and 'module' property"` - ); - }); - - it("should return the package if there is at least a 'main' or 'module' property", async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - main: './index.js', - }; - - const validatedPkg = await validateExportsOrdering({ pkg, logger: loggerMock }); - - expect(validatedPkg).toMatchInlineSnapshot(` - { - "main": "./index.js", - "name": "testing", - "version": "0.0.0", - } - `); - }); - - it('should return the package if there is an exports property with a valid structure', async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - exports: { - './package.json': './package.json', - './admin': { - types: './admin/index.d.ts', - import: './admin/index.js', - require: './admin/index.cjs', - default: './admin/index.js', - }, - }, - }; - - const validatedPkg = await validateExportsOrdering({ pkg, logger: loggerMock }); - - expect(validatedPkg).toMatchInlineSnapshot(` - { - "exports": { - "./admin": { - "default": "./admin/index.js", - "import": "./admin/index.js", - "require": "./admin/index.cjs", - "types": "./admin/index.d.ts", - }, - "./package.json": "./package.json", - }, - "name": "testing", - "version": "0.0.0", - } - `); - }); - - it('should throw if the types property is not the first in an export object', async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - exports: { - './admin': { - import: './admin/index.js', - types: './admin/index.d.ts', - }, - }, - }; - - await expect( - validateExportsOrdering({ pkg, logger: loggerMock }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"exports["./admin"]: the 'types' property should be the first property"` - ); - }); - - it('should log a warning if the require property comes before the import property in an export object', async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - exports: { - './admin': { - require: './admin/index.cjs', - import: './admin/index.js', - }, - }, - }; - - await validateExportsOrdering({ pkg, logger: loggerMock }); - - expect(loggerMock.warn.mock.calls[0]).toMatchInlineSnapshot(` - [ - "exports["./admin"]: the 'import' property should come before the 'require' property", - ] - `); - }); - - it('should log a warning if the import property comes the module property in an export object', async () => { - const pkg = { - name: 'testing', - version: '0.0.0', - exports: { - './admin': { - import: './admin/index.js', - module: './admin/index.js', - }, - }, - }; - - await validateExportsOrdering({ pkg, logger: loggerMock }); - - expect(loggerMock.warn.mock.calls[0]).toMatchInlineSnapshot(` - [ - "exports["./admin"]: the 'module' property should come before 'import' property", - ] - `); - }); - }); - - describe('getExportExtensionMap', () => { - it('should just return the default mapping', async () => { - expect(getExportExtensionMap()).toMatchInlineSnapshot(` - { - "commonjs": { - "cjs": ".js", - "es": ".mjs", - }, - "module": { - "cjs": ".cjs", - "es": ".js", - }, - } - `); - }); - }); - - describe('parseExports', () => { - const extMap = getExportExtensionMap(); - - it('should by default return a root exports map using the standard export fields from the pkg.json', () => { - const pkg = { - types: './dist/index.d.ts', - main: './dist/index.js', - module: './dist/index.mjs', - source: './src/index.ts', - }; - - expect(parseExports({ pkg, extMap })).toMatchInlineSnapshot(` - [ - { - "_path": ".", - "default": "./dist/index.mjs", - "import": "./dist/index.mjs", - "require": "./dist/index.js", - "source": "./src/index.ts", - "types": "./dist/index.d.ts", - }, - ] - `); - }); - - it("should not return anything if the standard export fields don't exist and there is no export map", () => { - const pkg = {}; - - expect(parseExports({ pkg, extMap })).toMatchInlineSnapshot(`[]`); - }); - - it('should return a combination of the standard export fields and the export map if they both exist', () => { - const pkg = { - types: './dist/index.d.ts', - main: './dist/index.js', - module: './dist/index.mjs', - source: './src/index.ts', - exports: { - './package.json': './package.json', - './admin': { - types: './admin/index.d.ts', - import: './admin/index.mjs', - require: './admin/index.js', - default: './admin/index.js', - source: './src/admin/index.js', - }, - }, - }; - - expect(parseExports({ pkg, extMap })).toMatchInlineSnapshot(` - [ - { - "_path": ".", - "default": "./dist/index.mjs", - "import": "./dist/index.mjs", - "require": "./dist/index.js", - "source": "./src/index.ts", - "types": "./dist/index.d.ts", - }, - { - "_exported": true, - "_path": "./admin", - "default": "./admin/index.js", - "import": "./admin/index.mjs", - "require": "./admin/index.js", - "source": "./src/admin/index.js", - "types": "./admin/index.d.ts", - }, - ] - `); - }); - - it('should return just the exports map if there are no standard export fields and the export map exists', () => { - const pkg = { - exports: { - './package.json': './package.json', - './admin': { - types: './admin/index.d.ts', - import: './admin/index.mjs', - require: './admin/index.js', - default: './admin/index.js', - source: './src/admin/index.js', - }, - }, - }; - - expect(parseExports({ pkg, extMap })).toMatchInlineSnapshot(` - [ - { - "_exported": true, - "_path": "./admin", - "default": "./admin/index.js", - "import": "./admin/index.mjs", - "require": "./admin/index.js", - "source": "./src/admin/index.js", - "types": "./admin/index.d.ts", - }, - ] - `); - }); - - it('should throw an error if you try to use an exports map without supplying an export for the package.json file', () => { - const pkg = { - exports: { - './admin': { - types: './admin/index.d.ts', - import: './admin/index.mjs', - require: './admin/index.js', - default: './admin/index.js', - source: './src/admin/index.js', - }, - }, - }; - - expect(() => parseExports({ pkg, extMap })).toThrowErrorMatchingInlineSnapshot(` - " - - package.json: \`exports["./package.json"] must be declared." - `); - }); - - it('should throw an error if the pkg.json type is undefined and you try to export like a module', () => { - const pkg = { - exports: { - './package.json': './package.json', - './admin': { - types: './admin/index.d.ts', - import: './admin/index.js', - require: './admin/index.cjs', - default: './admin/index.cjs', - source: './src/admin/index.js', - }, - }, - }; - - expect(() => parseExports({ pkg, extMap, type: 'module' })) - .toThrowErrorMatchingInlineSnapshot(` - " - - package.json with \`type: "undefined"\` - \`exports["./admin"].require\` must end with ".js" - - package.json with \`type: "undefined"\` - \`exports["./admin"].import\` must end with ".mjs"" - `); - }); - - it('should throw an error if the pkg.json type is commonjs and you try to export like a module', () => { - const pkg = { - type: 'commonjs', - exports: { - './package.json': './package.json', - './admin': { - types: './admin/index.d.ts', - import: './admin/index.js', - require: './admin/index.cjs', - default: './admin/index.cjs', - source: './src/admin/index.js', - }, - }, - }; - - expect(() => parseExports({ pkg, extMap, type: 'module' })) - .toThrowErrorMatchingInlineSnapshot(` - " - - package.json with \`type: "commonjs"\` - \`exports["./admin"].require\` must end with ".js" - - package.json with \`type: "commonjs"\` - \`exports["./admin"].import\` must end with ".mjs"" - `); - }); - - it('should throw an error if the pkg.json type is module and you try to export like a commonjs', () => { - const pkg = { - type: 'module', - exports: { - './package.json': './package.json', - './admin': { - types: './admin/index.d.ts', - import: './admin/index.mjs', - require: './admin/index.js', - default: './admin/index.js', - source: './src/admin/index.js', - }, - }, - }; - - expect(() => parseExports({ pkg, extMap, type: 'module' })) - .toThrowErrorMatchingInlineSnapshot(` - " - - package.json with \`type: "module"\` - \`exports["./admin"].require\` must end with ".cjs" - - package.json with \`type: "module"\` - \`exports["./admin"].import\` must end with ".js"" - `); - }); - }); -}); diff --git a/packages/core/strapi/lib/commands/index.js b/packages/core/strapi/lib/commands/index.js deleted file mode 100644 index ca053e715f..0000000000 --- a/packages/core/strapi/lib/commands/index.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -const { Command } = require('commander'); - -const strapiCommands = { - 'admin/create-user': require('./actions/admin/create-user/command'), - 'admin/reset-user-password': require('./actions/admin/reset-user-password/command'), - build: require('./actions/build-command/command'), // in 'build-command' to avoid problems with 'build' being commonly ignored - 'components/list': require('./actions/components/list/command'), - 'configuration/dump': require('./actions/configuration/dump/command'), - 'configuration/restore': require('./actions/configuration/restore/command'), - console: require('./actions/console/command'), - 'content-types/list': require('./actions/content-types/list/command'), - 'controllers/list': require('./actions/controllers/list/command'), - develop: require('./actions/develop/command'), - export: require('./actions/export/command'), - generate: require('./actions/generate/command'), - 'hooks/list': require('./actions/hooks/list/command'), - import: require('./actions/import/command'), - install: require('./actions/install/command'), - 'middlewares/list': require('./actions/middlewares/list/command'), - new: require('./actions/new/command'), - 'plugin/build': require('./actions/plugin/build-command/command'), - 'policies/list': require('./actions/policies/list/command'), - report: require('./actions/report/command'), - 'routes/list': require('./actions/routes/list/command'), - 'services/list': require('./actions/services/list/command'), - start: require('./actions/start/command'), - 'telemetry/disable': require('./actions/telemetry/disable/command'), - 'telemetry/enable': require('./actions/telemetry/enable/command'), - 'templates/generate': require('./actions/templates/generate/command'), - transfer: require('./actions/transfer/command'), - 'ts/generate-types': require('./actions/ts/generate-types/command'), - uninstall: require('./actions/uninstall/command'), - version: require('./actions/version/command'), - 'watch-admin': require('./actions/watch-admin/command'), -}; - -const buildStrapiCommand = (argv, command = new Command()) => { - // Initial program setup - command.storeOptionsAsProperties(false).allowUnknownOption(true); - - // Help command - command.helpOption('-h, --help', 'Display help for command'); - command.addHelpCommand('help [command]', 'Display help for command'); - - // Load all commands - Object.keys(strapiCommands).forEach((name) => { - try { - // Add this command to the Commander command object - strapiCommands[name]({ command, argv }); - } catch (e) { - console.error(`Failed to load command ${name}`, e); - } - }); - - return command; -}; - -const runStrapiCommand = async (argv = process.argv, command = new Command()) => { - await buildStrapiCommand(argv, command).parseAsync(argv); -}; - -module.exports = { - runStrapiCommand, - buildStrapiCommand, - strapiCommands, -}; diff --git a/packages/core/strapi/lib/types/core/strapi/index.d.ts b/packages/core/strapi/lib/types/core/strapi/index.d.ts deleted file mode 100644 index 47b68bc14b..0000000000 --- a/packages/core/strapi/lib/types/core/strapi/index.d.ts +++ /dev/null @@ -1,453 +0,0 @@ -import type { Database } from '@strapi/database'; -import type { Comomn, EntityService, Shared } from '@strapi/strapi'; - -// TODO move custom fields types to a separate file -interface CustomFieldServerOptions { - /** - * The name of the custom field - */ - name: string; - - /** - * The name of the plugin creating the custom field - */ - pluginId?: string; - - /** - * The existing Strapi data type the custom field uses - */ - type: string; - - /** - * Settings for the input size in the Admin UI - */ - inputSize?: { - default: 4 | 6 | 8 | 12; - isResizable: boolean; - }; -} - -interface CustomFields { - register: (customFields: CustomFieldServerOptions[] | CustomFieldServerOptions) => void; -} - -/** - * The Strapi interface implemented by the main Strapi class. - */ -export interface Strapi { - /** - * Getter for the Strapi enterprise edition configuration - */ - readonly EE: any; - - /** - * Getter for the Strapi configuration container - */ - readonly config: any; - - /** - * Getter for the Strapi admin container - */ - readonly admin: any; - - /** - * Getter for the Strapi auth container - */ - readonly auth: any; - - /** - * Getter for the Strapi content API container - */ - readonly contentAPI: any; - - /** - * Getter for the Strapi sanitizers container - */ - readonly sanitizers: any; - - /** - * Getter for the Strapi validators container - */ - readonly validators: any; - - /** - * Getter for the Strapi services container - * - * It returns all the registered services - */ - readonly services: Shared.Services; - - /** - * Find a service using its unique identifier - */ - service(uid: string): TService | undefined; - - /** - * Getter for the Strapi controllers container - * - * It returns all the registered controllers - */ - readonly controllers: Shared.Controllers; - - /** - * Find a controller using its unique identifier - */ - controller( - uid: TContentTypeUID - ): Shared.Controllers[TContentTypeUID]; - - /** - * Getter for the Strapi content types container - * - * It returns all the registered content types - */ - readonly contentTypes: any; - - /** - * Find a content type using its unique identifier - */ - contentType(uid: string): any; - - /** - * Getter for the Strapi component container - * - * It returns all the registered components - */ - readonly components: any; - - /** - * The custom fields registry - * - * It returns the custom fields interface - */ - readonly customFields: CustomFields; - - /** - * Getter for the Strapi policies container - * - * It returns all the registered policies - */ - readonly policies: any; - - /** - * Find a policy using its name - */ - policy(name: string): any; - - /** - * Getter for the Strapi middlewares container - * - * It returns all the registered middlewares - */ - readonly middlewares: any; - - /** - * Find a middleware using its name - */ - middleware(): any; - - /** - * Getter for the Strapi plugins container - * - * It returns all the registered plugins - */ - readonly plugins: any; - - /** - * Find a plugin using its name - */ - plugin(name: string): any; - - /** - * Getter for the Strapi hooks container - * - * It returns all the registered hooks - */ - readonly hooks: any; - - /** - * Find a hook using its name - */ - hook(): any; - - /** - * Getter for the Strapi APIs container - * - * It returns all the registered APIs - */ - readonly api: any; - - /** - * Strapi Register Lifecycle. - * - * - Load - * - The user application - * - The plugins - * - The admin - * - The APIs - * - The components - * - The middlewares - * - The policies - * - Trigger Strapi internal bootstrap - * - Create the webhooks runner - * - Create the internal hooks registry. - * - Init the telemetry cron job and middleware - * - Run all the `register` lifecycle methods loaded by the user application or the enabled plugins - */ - register(): Promise; - - /** - * Bootstraping phase. - * - * - Load all the content types - * - Initialize the database layer - * - Initialize the entity service - * - Run the schemas/database synchronization - * - Start the webhooks and initializing middlewares and routes - * - Run all the `bootstrap` lifecycle methods loaded by the - * user application or the enabled plugins - */ - bootstrap(): Promise; - - /** - * Destroy phase - * - * - Destroy Strapi server - * - Run all the `destroy` lifecycle methods loaded by the - * user application or the enabled plugins - * - Cleanup the event hub - * - Gracefully stop the database - * - Stop the telemetry and cron instance - * - Cleanup the global scope by removing global.strapi - */ - destroy(): Promise; - - /** - * Run all functions registered for a given lifecycle. (Strapi core, user app, plugins) - */ - runLifecyclesFunctions(lifecycleName: T): Promise; - - /** - * Load the application if needed and start the server - */ - start(): Promise; - - /** - * Stop the server and provide a custom error and message - */ - stopWithError(error: TError, customMessage?: string): void; - - /** - * Gracefully stop the server - * Call the destroy method. - */ - stop(code?: number): void; - - /** - * Load the server and the user application. - * It basically triggers the register and bootstrap phases - */ - load(): Promise; - - /** - * Restart the server and reload all the configuration. - * It re-runs all the lifecycles phases. - * - * @example - * ``` ts - * setImmediate(() => strapi.reload()); - * ``` - */ - reload(): () => void; - - /** - * Initialize and start all the webhooks registered in the webhook store - */ - startWebhooks(): Promise; - - /** - * Method called when the server is fully initialized and listen to incomming requests. - * It handles tasks such as logging the startup message - * or automatically opening the administration panel. - */ - postListen(): Promise; - - /** - * Start listening for incomming requests - */ - listen(): Promise; - - /** - * Opent he administration panel in a browser if the option is enabled. - * You can disable it using the admin.autoOpen configuration variable. - * - * Note: It only works in development envs. - */ - openAdmin(options: { isInitialized: boolean }): Promise; - - /** - * Load the admin panel server logic into the server code and initialize its configuration. - */ - loadAdmin(): Promise; - - /** - * Resolve every enabled plugin and load them into the application. - */ - loadPlugins(): Promise; - - /** - * Load every global policies in the policies container by - * reading from the `strapi.dirs.dist.policies` directory. - */ - loadPolicies(): Promise; - - /** - * Load every APIs and their components (config, routes, controllers, services, - * policies, middlewares, content-types) in the API container. - */ - loadAPIs(): Promise; - - /** - * Resolve every components in the user application and store them in `strapi.components` - */ - loadComponents(): Promise; - - /** - * Load every global and core middlewares in the middlewares container by - * reading from the `strapi.dirs.dist.middlewares` and internal middlewares directory. - */ - loadMiddlewares(): Promise; - - /** - * Load the user application in the server by reading the `src/index.js` file. - */ - loadApp(): Promise; - - /** - * Add internal hooks to the hooks container. - * Those hooks are meant for internal usage and might break in future releases. - */ - registerInternalHooks(): void; - - /** - * Find a model (content-type, component) based on its unique identifier. - */ - getModel(uid: string): any; - - /** - * Binds database queries for a specific model based on its unique identifier. - */ - query(uid: string): any; - - /** - * Main Strapi container holding all the registries and providers (config, content-types, services, policies, etc...) - */ - container: any; - - /** - * References to all the directories handled by Strapi - */ - dirs: StrapiDirectories; - - /** - * Internal flag used to check if the application has been loaded - */ - isLoaded: boolean; - - /** - * Fully reload the application - */ - reload(): void; - - /** - * Holds a reference to the Koa application and the http server used by Strapi - */ - server: any; - - /** - * Strapi util used to manage application files - */ - fs: any; - - /** - * Event hub used to send and receive events from anywhere in the application - */ - eventHub: any; - - /** - * Internal util used to log stats and messages on application Startup - */ - startupLogger: any; - - /** - * Strapi logger used to send errors, warning or information messages - */ - log: any; - - /** - * Used to manage cron within Strapi - */ - cron: any; - - /** - * Telemetry util used to collect anonymous data on the application usage - */ - telemetry: any; - - /** - * Used to access ctx from anywhere within the Strapi application - */ - requestContext: any; - - /** - * Strapi DB layer instance - */ - db: Database; - - /** - * Core Store accessor - */ - store: any; - - /** - * Entity Validator instance - */ - entityValidator: any; - - /** - * Entity Service instance - */ - entityService: EntityService.EntityService; -} - -export interface Lifecycles { - REGISTER: 'register'; - BOOTSTRAP: 'bootstrap'; - DESTROY: 'destroy'; -} - -export interface StrapiDirectories { - static: { - public: string; - }; - app: { - root: string; - src: string; - api: string; - components: string; - extensions: string; - policies: string; - middlewares: string; - config: string; - }; - dist: { - root: string; - src: string; - api: string; - components: string; - extensions: string; - policies: string; - middlewares: string; - config: string; - }; -} diff --git a/packages/core/strapi/lib/commands/actions/plugin/build-command/action.js b/packages/core/strapi/src/commands/actions/plugin/build-command/action.ts similarity index 75% rename from packages/core/strapi/lib/commands/actions/plugin/build-command/action.js rename to packages/core/strapi/src/commands/actions/plugin/build-command/action.ts index cc29dbc894..726db3c677 100644 --- a/packages/core/strapi/lib/commands/actions/plugin/build-command/action.js +++ b/packages/core/strapi/src/commands/actions/plugin/build-command/action.ts @@ -1,27 +1,24 @@ -'use strict'; - -const fs = require('fs/promises'); -const boxen = require('boxen'); -const chalk = require('chalk'); -const ora = require('ora'); -const { createLogger } = require('../../../utils/logger'); -const { notifyExperimentalCommand } = require('../../../utils/helpers'); -const { +import fs from 'fs/promises'; +import boxen from 'boxen'; +import chalk from 'chalk'; +import ora from 'ora'; +import { createLogger } from '../../../utils/logger'; +import { notifyExperimentalCommand } from '../../../utils/helpers'; +import { loadPkg, validatePkg, validateExportsOrdering, getExportExtensionMap, -} = require('../../../utils/pkg'); -const { createBuildContext, createBuildTasks } = require('../../../builders/packages'); -const { buildTaskHandlers } = require('../../../builders/tasks'); +} from '../../../utils/pkg'; +import { createBuildContext, createBuildTasks } from '../../../builders/packages'; +import { buildTaskHandlers } from '../../../builders/tasks'; -/** - * - * @param {object} args - * @param {boolean} args.force - * @param {boolean} args.debug - */ -module.exports = async ({ force, debug }) => { +interface ActionOptions { + force?: boolean; + debug?: boolean; +} + +export default async ({ force, debug }: ActionOptions) => { const logger = createLogger({ debug, timestamp: false }); try { /** @@ -106,13 +103,10 @@ module.exports = async ({ force, debug }) => { } for (const task of buildTasks) { - /** - * @type {import('../../../builders/tasks').TaskHandler} - */ - const handler = buildTaskHandlers[task.type]; + const handler = buildTaskHandlers(task); handler.print(ctx, task); - await handler.run(ctx, task).catch((err) => { + await handler.run(ctx, task).catch((err: NodeJS.ErrnoException) => { if (err instanceof Error) { logger.error(err.message); } @@ -124,14 +118,16 @@ module.exports = async ({ force, debug }) => { logger.error( 'There seems to be an unexpected error, try again with --debug for more information \n' ); - console.log( - chalk.red( - boxen(err.stack, { - padding: 1, - align: 'left', - }) - ) - ); + if (err instanceof Error && err.stack) { + console.log( + chalk.red( + boxen(err.stack, { + padding: 1, + align: 'left', + }) + ) + ); + } process.exit(1); } }; diff --git a/packages/core/strapi/lib/commands/actions/plugin/build-command/command.js b/packages/core/strapi/src/commands/actions/plugin/build-command/command.ts similarity index 54% rename from packages/core/strapi/lib/commands/actions/plugin/build-command/command.js rename to packages/core/strapi/src/commands/actions/plugin/build-command/command.ts index 0c387dd64e..1607cc5fb7 100644 --- a/packages/core/strapi/lib/commands/actions/plugin/build-command/command.js +++ b/packages/core/strapi/src/commands/actions/plugin/build-command/command.ts @@ -1,13 +1,11 @@ -'use strict'; - -const { forceOption } = require('../../../utils/commander'); -const { getLocalScript } = require('../../../utils/helpers'); +import { forceOption } from '../../../utils/commander'; +import { getLocalScript } from '../../../utils/helpers'; +import type { StrapiCommand } from '../../../types'; /** * `$ strapi plugin:build` - * @param {import('../../../../types/core/commands').AddCommandOptions} options */ -module.exports = ({ command }) => { +const command: StrapiCommand = ({ command }) => { command .command('plugin:build') .description('Bundle your strapi plugin for publishing.') @@ -15,3 +13,5 @@ module.exports = ({ command }) => { .option('-d, --debug', 'Enable debugging mode with verbose logs', false) .action(getLocalScript('plugin/build-command')); }; + +export default command; diff --git a/packages/core/strapi/src/commands/builders/packages.js b/packages/core/strapi/src/commands/builders/packages.ts similarity index 63% rename from packages/core/strapi/src/commands/builders/packages.js rename to packages/core/strapi/src/commands/builders/packages.ts index cb5ddbf09e..3d47918a66 100644 --- a/packages/core/strapi/src/commands/builders/packages.js +++ b/packages/core/strapi/src/commands/builders/packages.ts @@ -1,35 +1,40 @@ -'use strict'; +import path from 'path'; +import browserslistToEsbuild from 'browserslist-to-esbuild'; +import { parseExports, PackageJson, ExtMap, Export } from '../utils/pkg'; +import type { Logger } from '../utils/logger'; +import type { ViteTask } from './tasks/vite'; +import type { DtsTask } from './tasks/dts'; -const path = require('path'); -const browserslistToEsbuild = require('browserslist-to-esbuild'); +interface BuildContextArgs { + cwd: string; + extMap: ExtMap; + logger: Logger; + pkg: PackageJson; +} -const { parseExports } = require('../utils/pkg'); +export type Target = 'node' | 'web' | '*'; -/** - * @typedef {Object} BuildContextArgs - * @property {string} cwd - * @property {import('../utils/pkg').ExtMap} extMap - * @property {import('../utils/logger').Logger} logger - * @property {import('../utils/pkg').PackageJson} pkg - */ +export type Targets = { + [target in Target]: string[]; +}; -/** - * @typedef {Object} Targets - * @property {string[]} node - * @property {string[]} web - * @property {string[]} * - */ +export interface BuildContext { + cwd: string; + exports: Record; + external: string[]; + extMap: ExtMap; + logger: Logger; + pkg: PackageJson; + targets: Targets; + distPath: string; +} -/** - * @typedef {Object} BuildContext - * @property {string} cwd - * @property {import('../utils/pkg').Export[]} exports - * @property {string[]} external - * @property {import('../utils/pkg').ExtMap} extMap - * @property {import('../utils/logger').Logger} logger - * @property {import('../utils/pkg').PackageJson} pkg - * @property {Targets} targets - */ +export interface BuildTask { + type: 'build:js' | 'build:dts'; + entries: unknown[]; + format?: 'cjs' | 'es'; + [key: string]: unknown; +} const DEFAULT_BROWSERS_LIST_CONFIG = [ 'last 3 major versions', @@ -43,30 +48,33 @@ const DEFAULT_BROWSERS_LIST_CONFIG = [ * @description Create a build context for the pipeline we're creating, * this is shared among tasks so they all use the same settings for core pieces * such as a target, distPath, externals etc. - * - * @type {(args: BuildContextArgs) => Promise} */ -const createBuildContext = async ({ cwd, extMap, logger, pkg }) => { +const createBuildContext = async ({ + cwd, + extMap, + logger, + pkg, +}: BuildContextArgs): Promise => { const targets = { '*': browserslistToEsbuild(pkg.browserslist ?? DEFAULT_BROWSERS_LIST_CONFIG), node: browserslistToEsbuild(['node 16.0.0']), web: ['esnext'], }; - const exports = parseExports({ extMap, pkg }).reduce((acc, x) => { + const exportsArray = parseExports({ extMap, pkg }).reduce((acc, x) => { const { _path: exportPath, ...exportEntry } = x; return { ...acc, [exportPath]: exportEntry }; - }, {}); + }, {} as Record); const external = [ ...(pkg.dependencies ? Object.keys(pkg.dependencies) : []), ...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []), ]; - const outputPaths = Object.values(exports) + const outputPaths = Object.values(exportsArray) .flatMap((exportEntry) => { - return [exportEntry.import, exportEntry.require].filter(Boolean); + return [exportEntry.import, exportEntry.require].filter((v): v is string => Boolean(v)); }) .map((p) => path.resolve(cwd, p)); @@ -86,7 +94,7 @@ const createBuildContext = async ({ cwd, extMap, logger, pkg }) => { logger, cwd, pkg, - exports, + exports: exportsArray, external, distPath, targets, @@ -94,17 +102,11 @@ const createBuildContext = async ({ cwd, extMap, logger, pkg }) => { }; }; -/** - * @type {(containerPath: string, itemPath: string) => boolean} - */ -const pathContains = (containerPath, itemPath) => { +const pathContains = (containerPath: string, itemPath: string): boolean => { return !path.relative(containerPath, itemPath).startsWith('..'); }; -/** - * @type {(filePaths: string[]) => string | undefined} - */ -const findCommonDirPath = (filePaths) => { +const findCommonDirPath = (filePaths: string[]) => { /** * @type {string | undefined} */ @@ -138,38 +140,36 @@ const findCommonDirPath = (filePaths) => { return commonPath; }; -/** - * @typedef {import('./tasks/vite').ViteTask | import('./tasks/dts').DtsTask} BuildTask - */ +type Task = ViteTask | DtsTask; /** * @description Create the build tasks for the pipeline, this * comes from the exports map we've created in the build context. * But handles each export line uniquely with space to add more * as the standard develops. - * - * @type {(args: BuildContext) => Promise} */ -const createBuildTasks = async (ctx) => { - /** - * @type {BuildTask[]} - */ - const tasks = []; +const createBuildTasks = async (ctx: BuildContext): Promise => { + const tasks: Task[] = []; - /** - * @type {import('./tasks/dts').DtsTask} - */ - const dtsTask = { + const dtsTask: DtsTask = { type: 'build:dts', entries: [], }; - /** - * @type {Record} - */ - const viteTasks = {}; + const viteTasks: Record = {}; - const createViteTask = (format, runtime, { output, ...restEntry }) => { + const createViteTask = ( + format: 'cjs' | 'es', + runtime: Target, + { + output, + ...restEntry + }: { + output: string; + path: string; + entry: string; + } + ) => { const buildId = `${format}:${output}`; if (viteTasks[buildId]) { @@ -208,11 +208,8 @@ const createBuildTasks = async (ctx) => { }); } - /** - * @type {keyof Target} - */ // eslint-disable-next-line no-nested-ternary - const runtime = exp._path.includes('strapi-admin') + const runtime: Target = exp._path.includes('strapi-admin') ? 'web' : exp._path.includes('strapi-server') ? 'node' @@ -224,7 +221,7 @@ const createBuildTasks = async (ctx) => { */ createViteTask('cjs', runtime, { path: exp._path, - entry: exp.source, + entry: exp.source ?? 'src/index.ts', output: exp.require, }); } @@ -235,7 +232,7 @@ const createBuildTasks = async (ctx) => { */ createViteTask('es', runtime, { path: exp._path, - entry: exp.source, + entry: exp.source ?? 'src/index.ts', output: exp.import, }); } @@ -246,7 +243,4 @@ const createBuildTasks = async (ctx) => { return tasks; }; -module.exports = { - createBuildContext, - createBuildTasks, -}; +export { createBuildContext, createBuildTasks }; diff --git a/packages/core/strapi/src/commands/builders/tasks/dts.js b/packages/core/strapi/src/commands/builders/tasks/dts.ts similarity index 79% rename from packages/core/strapi/src/commands/builders/tasks/dts.js rename to packages/core/strapi/src/commands/builders/tasks/dts.ts index e9e741770f..118d6e898d 100644 --- a/packages/core/strapi/src/commands/builders/tasks/dts.js +++ b/packages/core/strapi/src/commands/builders/tasks/dts.ts @@ -1,18 +1,33 @@ -'use strict'; +import path from 'path'; +import chalk from 'chalk'; +import ora from 'ora'; +import ts from 'typescript'; +import type { Logger } from '../../utils/logger'; +import type { TaskHandler } from '.'; +import type { BuildTask } from '../packages'; -const path = require('path'); -const chalk = require('chalk'); -const ora = require('ora'); -const ts = require('typescript'); +interface LoadTsConfigOptions { + cwd: string; + path: string; +} + +class TSConfigNotFoundError extends Error { + // eslint-disable-next-line no-useless-constructor + // constructor(message, options) { + // super(message, options); + // } + + get code() { + return 'TS_CONFIG_NOT_FOUND'; + } +} /** * @description Load a tsconfig.json file and return the parsed config * * @internal - * - * @type {(args: { cwd: string; path: string }) => Promise)} */ -const loadTsConfig = async ({ cwd, path }) => { +const loadTsConfig = async ({ cwd, path }: LoadTsConfigOptions) => { const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, path); if (!configPath) { @@ -24,25 +39,19 @@ const loadTsConfig = async ({ cwd, path }) => { return ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd); }; -class TSConfigNotFoundError extends Error { - // eslint-disable-next-line no-useless-constructor - constructor(message, options) { - super(message, options); - } - - get code() { - return 'TS_CONFIG_NOT_FOUND'; - } +interface BuildTypesOptions { + cwd: string; + logger: Logger; + outDir: string; + tsconfig: ts.ParsedCommandLine; } /** * @description * * @internal - * - * @type {(args: { cwd: string; logger: import('../../utils/logger').Logger; outDir: string; tsconfig: ts.ParsedCommandLine }) => Promise} */ -const buildTypes = ({ cwd, logger, outDir, tsconfig }) => { +const buildTypes = ({ cwd, logger, outDir, tsconfig }: BuildTypesOptions) => { const compilerOptions = { ...tsconfig.options, declaration: true, @@ -102,23 +111,19 @@ const buildTypes = ({ cwd, logger, outDir, tsconfig }) => { } }; -/** - * @typedef {Object} DtsTaskEntry - * @property {string} exportPath - * @property {string} sourcePath - * @property {string} targetPath - */ +export interface DtsTaskEntry { + importId: string; + exportPath: string; + sourcePath?: string; + targetPath: string; +} -/** - * @typedef {Object} DtsTask - * @property {"build:dts"} type - * @property {DtsTaskEntry[]} entries - */ +export interface DtsTask extends BuildTask { + type: 'build:dts'; + entries: DtsTaskEntry[]; +} -/** - * @type {import('./index').TaskHandler} - */ -const dtsTask = { +const dtsTask: TaskHandler = { _spinner: null, print(ctx, task) { const entries = [ @@ -146,7 +151,7 @@ const dtsTask = { * TODO: this will not scale and assumes all project sourcePaths are `src/index.ts` * so we can go back to the "root" of the project... */ - cwd: path.join(ctx.cwd, entry.sourcePath, '..', '..'), + cwd: path.join(ctx.cwd, entry.sourcePath ?? 'src/index.ts', '..', '..'), path: 'tsconfig.build.json', }).catch((err) => { if (err instanceof TSConfigNotFoundError) { @@ -187,13 +192,13 @@ const dtsTask = { } }, async success() { - this._spinner.succeed('Built type files'); + this._spinner?.succeed('Built type files'); }, async fail(ctx, task, err) { - this._spinner.fail('Failed to build type files'); + this._spinner?.fail('Failed to build type files'); throw err; }, }; -module.exports = { dtsTask }; +export { dtsTask }; diff --git a/packages/core/strapi/src/commands/builders/tasks/index.js b/packages/core/strapi/src/commands/builders/tasks/index.js deleted file mode 100644 index 4fc4ebfefa..0000000000 --- a/packages/core/strapi/src/commands/builders/tasks/index.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -const { dtsTask } = require('./dts'); -const { viteTask } = require('./vite'); - -/** - * @template Task - * @param {Task} - * @returns {Task} - * - * @typedef {Object} TaskHandler - * @property {(ctx: import("../packages").BuildContext, task: Task) => import('ora').Ora} print - * @property {(ctx: import("../packages").BuildContext, task: Task) => Promise} run - * @property {(ctx: import("../packages").BuildContext, task: Task) => Promise} success - * @property {(ctx: import("../packages").BuildContext, task: Task, err: unknown) => Promise} fail - * @property {import('ora').Ora | null} _spinner - */ - -/** - * @type {{ "build:js": TaskHandler; "build:dts": TaskHandler; }}} - */ -const buildTaskHandlers = { - 'build:js': viteTask, - 'build:dts': dtsTask, -}; - -module.exports = { - buildTaskHandlers, -}; diff --git a/packages/core/strapi/src/commands/builders/tasks/index.ts b/packages/core/strapi/src/commands/builders/tasks/index.ts new file mode 100644 index 0000000000..d3c2543dac --- /dev/null +++ b/packages/core/strapi/src/commands/builders/tasks/index.ts @@ -0,0 +1,23 @@ +import type { Ora } from 'ora'; +import { dtsTask, DtsTask } from './dts'; +import { viteTask, ViteTask } from './vite'; +import type { BuildContext, BuildTask } from '../packages'; + +export interface TaskHandler { + print: (ctx: BuildContext, task: Task) => void; + run: (ctx: BuildContext, task: Task) => Promise; + success: (ctx: BuildContext, task: Task) => Promise; + fail: (ctx: BuildContext, task: Task, err: unknown) => Promise; + _spinner: Ora | null; +} + +const handlers = { + 'build:js': viteTask, + 'build:dts': dtsTask, +}; + +const buildTaskHandlers = (t: T): TaskHandler => { + return handlers[t.type] as TaskHandler; +}; + +export { buildTaskHandlers }; diff --git a/packages/core/strapi/src/commands/builders/tasks/vite.js b/packages/core/strapi/src/commands/builders/tasks/vite.ts similarity index 74% rename from packages/core/strapi/src/commands/builders/tasks/vite.js rename to packages/core/strapi/src/commands/builders/tasks/vite.ts index a262b2111f..42f04058d0 100644 --- a/packages/core/strapi/src/commands/builders/tasks/vite.js +++ b/packages/core/strapi/src/commands/builders/tasks/vite.ts @@ -1,17 +1,18 @@ -'use strict'; +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import path from 'path'; +// @ts-ignore +import { build, createLogger, InlineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import ora from 'ora'; +import chalk from 'chalk'; -const path = require('path'); -const { build, createLogger } = require('vite'); -const react = require('@vitejs/plugin-react'); -const ora = require('ora'); -const chalk = require('chalk'); +import type { TaskHandler } from '.'; +import type { BuildContext, BuildTask, Target } from '../packages'; /** * @internal - * - * @type {(ctx: import('../packages').BuildContext, task: ViteTask) => import('vite').UserConfig} */ -const resolveViteConfig = (ctx, task) => { +const resolveViteConfig = (ctx: BuildContext, task: ViteTask): InlineConfig => { const { cwd, distPath, targets, external, extMap, pkg } = ctx; const { entries, format, output, runtime } = task; const outputExt = extMap[pkg.type || 'commonjs'][format]; @@ -22,10 +23,7 @@ const resolveViteConfig = (ctx, task) => { customLogger.warnOnce = (msg) => ctx.logger.warn(msg); customLogger.error = (msg) => ctx.logger.error(msg); - /** - * @type {import('vite').InlineConfig} - */ - const config = { + const config: InlineConfig = { configFile: false, root: cwd, mode: 'production', @@ -79,25 +77,20 @@ const resolveViteConfig = (ctx, task) => { return config; }; -/** - * @typedef {Object} ViteTaskEntry - * @property {string} path - * @property {string} entry - */ +interface ViteTaskEntry { + path: string; + entry: string; +} -/** - * @typedef {Object} ViteTask - * @property {"build:js"} type - * @property {ViteTaskEntry[]} entries - * @property {string} format - * @property {string} output - * @property {keyof import('../packages').Targets} runtime - */ +export interface ViteTask extends BuildTask { + type: 'build:js'; + entries: ViteTaskEntry[]; + format: 'cjs' | 'es'; + output: string; + runtime: Target; +} -/** - * @type {import('./index').TaskHandler} - */ -const viteTask = { +const viteTask: TaskHandler = { _spinner: null, print(ctx, task) { const targetLines = [ @@ -130,15 +123,13 @@ const viteTask = { } }, async success() { - this._spinner.succeed('Built javascript files'); + this._spinner?.succeed('Built javascript files'); }, async fail(ctx, task, err) { - this._spinner.fail('Failed to build javascript files'); + this._spinner?.fail('Failed to build javascript files'); throw err; }, }; -module.exports = { - viteTask, -}; +export { viteTask }; diff --git a/packages/core/strapi/src/commands/index.ts b/packages/core/strapi/src/commands/index.ts index 4836e37229..94b6e2ab73 100644 --- a/packages/core/strapi/src/commands/index.ts +++ b/packages/core/strapi/src/commands/index.ts @@ -10,10 +10,8 @@ import consoleCommand from './actions/console/command'; import listContentTypes from './actions/content-types/list/command'; import listControllers from './actions/controllers/list/command'; import developCommand from './actions/develop/command'; -// import exportCommand from './actions/export/command'; import generateCommand from './actions/generate/command'; import listHooks from './actions/hooks/list/command'; -// import importCommand from './actions/import/command'; import installCommand from './actions/install/command'; import listMiddlewares from './actions/middlewares/list/command'; import newCommand from './actions/new/command'; @@ -25,11 +23,11 @@ import startCommand from './actions/start/command'; import disableTelemetry from './actions/telemetry/disable/command'; import enableTelemetry from './actions/telemetry/enable/command'; import generateTemplates from './actions/templates/generate/command'; -// import transferCommand from './actions/transfer/command'; import generateTsTypes from './actions/ts/generate-types/command'; import uninstallCommand from './actions/uninstall/command'; import versionCommand from './actions/version/command'; import watchAdminCommand from './actions/watch-admin/command'; +import buildPluginCommand from './actions/plugin/build-command/command'; const strapiCommands = { createAdminUser, @@ -59,6 +57,7 @@ const strapiCommands = { uninstallCommand, versionCommand, watchAdminCommand, + buildPluginCommand, } as const; const buildStrapiCommand = (argv: string[], command = new Command()) => { diff --git a/packages/core/strapi/src/commands/utils/helpers.ts b/packages/core/strapi/src/commands/utils/helpers.ts index 0114152249..af799e3b08 100644 --- a/packages/core/strapi/src/commands/utils/helpers.ts +++ b/packages/core/strapi/src/commands/utils/helpers.ts @@ -1,25 +1,14 @@ -<<<<<<< HEAD:packages/core/strapi/src/commands/utils/helpers.ts /* eslint-disable @typescript-eslint/no-var-requires */ -import { yellow, red, green } from 'chalk'; +import chalk, { yellow, red, green } from 'chalk'; import { has, isString, isArray } from 'lodash/fp'; import resolveCwd from 'resolve-cwd'; +import { prompt } from 'inquirer'; +import boxen from 'boxen'; import type { Command } from 'commander'; -======= -'use strict'; /** * Helper functions for the Strapi CLI */ - -const { yellow, red, green } = require('chalk'); -const { isString, isArray } = require('lodash/fp'); -const resolveCwd = require('resolve-cwd'); -const { has } = require('lodash/fp'); -const { prompt } = require('inquirer'); -const boxen = require('boxen'); -const chalk = require('chalk'); ->>>>>>> main:packages/core/strapi/lib/commands/utils/helpers.js - const bytesPerKb = 1024; const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB']; @@ -133,16 +122,11 @@ const assertCwdContainsStrapiProject = (name: string) => { try { const pkgJSON = require(`${process.cwd()}/package.json`); -<<<<<<< HEAD:packages/core/strapi/src/commands/utils/helpers.ts - if (!has('dependencies.@strapi/strapi', pkgJSON)) { - logErrorAndExit(); -======= if ( !has('dependencies.@strapi/strapi', pkgJSON) && !has('devDependencies.@strapi/strapi', pkgJSON) ) { - logErrorAndExit(name); ->>>>>>> main:packages/core/strapi/lib/commands/utils/helpers.js + logErrorAndExit(); } } catch (err) { logErrorAndExit(); @@ -176,9 +160,6 @@ const getLocalScript = }); }; -<<<<<<< HEAD:packages/core/strapi/src/commands/utils/helpers.ts -export { -======= /** * @description Notify users this is an experimental command and get them to approve first * this can be opted out by passing `yes` as a property of the args object. @@ -194,7 +175,7 @@ export { * } * ``` */ -const notifyExperimentalCommand = async ({ force } = {}) => { +const notifyExperimentalCommand = async ({ force }: { force?: boolean } = {}) => { console.log( boxen( `The ${chalk.bold( @@ -224,8 +205,7 @@ const notifyExperimentalCommand = async ({ force } = {}) => { } }; -module.exports = { ->>>>>>> main:packages/core/strapi/lib/commands/utils/helpers.js +export { exitWith, assertUrlHasProtocol, ifOptions, diff --git a/packages/core/data-transfer/src/commands/logger.js b/packages/core/strapi/src/commands/utils/logger.ts similarity index 68% rename from packages/core/data-transfer/src/commands/logger.js rename to packages/core/strapi/src/commands/utils/logger.ts index d9ca0e6e23..6c871f843b 100644 --- a/packages/core/data-transfer/src/commands/logger.js +++ b/packages/core/strapi/src/commands/utils/logger.ts @@ -1,26 +1,22 @@ -'use strict'; +import chalk from 'chalk'; -const chalk = require('chalk'); +export interface LoggerOptions { + silent?: boolean; + debug?: boolean; + timestamp?: boolean; +} -/** - * @typedef {{ silent?: boolean; debug?: boolean; timestamp?: boolean; }} LoggerOptions - */ +export interface Logger { + warnings: number; + errors: number; + debug: (...args: unknown[]) => void; + info: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; + log: (...args: unknown[]) => void; +} -/** - * @typedef {object} Logger - * @property {number} warnings - * @property {number} errors - * @property {(...args: any[]) => void} debug - * @property {(...args: any[]) => void} info - * @property {(...args: any[]) => void} warn - * @property {(...args: any[]) => void} error - * @property {(...args: any[]) => void} log - */ - -/** - * @type {(options: LoggerOptions) => Logger} - */ -const createLogger = (options = {}) => { +const createLogger = (options: LoggerOptions = {}): Logger => { const { silent = false, debug = false, timestamp = true } = options; const state = { errors: 0, warning: 0 }; @@ -92,6 +88,4 @@ const createLogger = (options = {}) => { }; }; -module.exports = { - createLogger, -}; +export { createLogger }; diff --git a/packages/core/data-transfer/src/commands/pkg.js b/packages/core/strapi/src/commands/utils/pkg.ts similarity index 77% rename from packages/core/data-transfer/src/commands/pkg.js rename to packages/core/strapi/src/commands/utils/pkg.ts index 45f599ecaa..c37d20e8b5 100644 --- a/packages/core/data-transfer/src/commands/pkg.js +++ b/packages/core/strapi/src/commands/utils/pkg.ts @@ -1,9 +1,36 @@ -'use strict'; +import fs from 'fs/promises'; +import path from 'path'; +import chalk from 'chalk'; +import * as yup from 'yup'; +import type { Logger } from './logger'; -const fs = require('fs/promises'); -const path = require('path'); -const chalk = require('chalk'); -const yup = require('yup'); +export interface PackageJson extends Omit, 'exports'> { + type: Extensions; + exports?: { + [key: string]: Export | string; + }; + browserslist?: string[]; +} + +export type Extensions = 'commonjs' | 'module'; +export type ExtMap = { + [key in Extensions]: { + cjs: string; + es: string; + }; +}; + +export interface Export { + types?: string; + source?: string; + require?: string; + import?: string; + default?: string; +} + +export interface ExportWithMeta extends Export { + _path: string; +} /** * Utility functions for loading and validating package.json @@ -18,14 +45,19 @@ const yup = require('yup'); const packageJsonSchema = yup.object({ name: yup.string().required(), version: yup.string().required(), - type: yup.string().matches(/(commonjs|module)/), + type: yup.mixed().oneOf(['commonjs', 'module']), license: yup.string(), - bin: yup.mixed().oneOf([ - yup.string(), - yup.object({ - [yup.string()]: yup.string(), - }), - ]), + bin: yup.lazy((value) => + typeof value === 'object' + ? yup.object( + Object.entries(value).reduce((acc, [key]) => { + acc[key] = yup.string().required(); + + return acc; + }, {} as Record>) + ) + : yup.string() + ), main: yup.string(), module: yup.string(), source: yup.string(), @@ -52,7 +84,7 @@ const packageJsonSchema = yup.object({ } return acc; - }, {}) + }, {} as Record | yup.SchemaOf>) : undefined ) ), @@ -64,18 +96,18 @@ const packageJsonSchema = yup.object({ engines: yup.object(), }); -/** - * @typedef {import('yup').Asserts} PackageJson - */ +interface LoadPkgOptions { + cwd: string; + logger: Logger; +} /** * @description being a task to load the package.json starting from the current working directory * using a shallow find for the package.json and `fs` to read the file. If no package.json is found, * the process will throw with an appropriate error message. * - * @type {(args: { cwd: string, logger: import('./logger').Logger }) => Promise} */ -const loadPkg = async ({ cwd, logger }) => { +const loadPkg = async ({ cwd, logger }: LoadPkgOptions): Promise => { const directory = path.resolve(cwd); const pkgPath = path.join(directory, 'package.json'); @@ -95,10 +127,8 @@ const loadPkg = async ({ cwd, logger }) => { /** * @description validate the package.json against a standardised schema using `yup`. * If the validation fails, the process will throw with an appropriate error message. - * - * @type {(args: { pkg: object }) => Promise} */ -const validatePkg = async ({ pkg }) => { +const validatePkg = async ({ pkg }: { pkg: PackageJson }) => { try { const validatedPkg = await packageJsonSchema.validate(pkg, { strict: true, @@ -111,14 +141,14 @@ const validatePkg = async ({ pkg }) => { case 'required': throw new Error( `'${err.path}' in 'package.json' is required as type '${chalk.magenta( - yup.reach(packageJsonSchema, err.path).type + yup.reach(packageJsonSchema, err.path ?? '').type )}'` ); case 'matches': throw new Error( `'${err.path}' in 'package.json' must be of type '${chalk.magenta( - err.params.regex - )}' (recieved the value '${chalk.magenta(err.params.value)}')` + err.params?.regex + )}' (recieved the value '${chalk.magenta(err.params?.value)}')` ); /** * This will only be thrown if there are keys in the export map @@ -127,7 +157,7 @@ const validatePkg = async ({ pkg }) => { case 'noUnknown': throw new Error( `'${err.path}' in 'package.json' contains the unknown key ${chalk.magenta( - err.params.unknown + err.params?.unknown )}, for compatability only the following keys are allowed: ${chalk.magenta( "['types', 'source', 'import', 'require', 'default']" )}` @@ -135,8 +165,8 @@ const validatePkg = async ({ pkg }) => { default: throw new Error( `'${err.path}' in 'package.json' must be of type '${chalk.magenta( - err.params.type - )}' (recieved '${chalk.magenta(typeof err.params.value)}')` + err.params?.type + )}' (recieved '${chalk.magenta(typeof err.params?.value)}')` ); } } @@ -145,15 +175,21 @@ const validatePkg = async ({ pkg }) => { } }; +interface ValidateExportsOrderingOptions { + pkg: PackageJson; + logger: Logger; +} + /** * @description validate the `exports` property of the package.json against a set of rules. * If the validation fails, the process will throw with an appropriate error message. If * there is no `exports` property we check the standard export-like properties on the root * of the package.json. - * - * @type {(args: { pkg: object, logger: import('./logger').Logger }) => Promise} */ -const validateExportsOrdering = async ({ pkg, logger }) => { +const validateExportsOrdering = async ({ + pkg, + logger, +}: ValidateExportsOrderingOptions): Promise => { if (pkg.exports) { const exports = Object.entries(pkg.exports); @@ -195,7 +231,7 @@ const validateExportsOrdering = async ({ pkg, logger }) => { }; /** @internal */ -function assertFirst(key, arr) { +function assertFirst(key: string, arr: string[]) { const aIdx = arr.indexOf(key); // if not found, then we don't care @@ -207,7 +243,7 @@ function assertFirst(key, arr) { } /** @internal */ -function assertLast(key, arr) { +function assertLast(key: string, arr: string[]) { const aIdx = arr.indexOf(key); // if not found, then we don't care @@ -219,7 +255,7 @@ function assertLast(key, arr) { } /** @internal */ -function assertOrder(keyA, keyB, arr) { +function assertOrder(keyA: string, keyB: string, arr: string[]) { const aIdx = arr.indexOf(keyA); const bIdx = arr.indexOf(keyB); @@ -231,24 +267,10 @@ function assertOrder(keyA, keyB, arr) { return aIdx < bIdx; } -/** - * @typedef {Object} Extensions - * @property {string} commonjs - * @property {string} esm - */ - -/** - * @typedef {Object} ExtMap - * @property {Extensions} commonjs - * @property {Extensions} esm - */ - /** * @internal - * - * @type {ExtMap} */ -const DEFAULT_PKG_EXT_MAP = { +const DEFAULT_PKG_EXT_MAP: ExtMap = { // pkg.type: "commonjs" commonjs: { cjs: '.js', @@ -266,21 +288,24 @@ const DEFAULT_PKG_EXT_MAP = { * We potentially might need to support legacy exports or as package * development continues we have space to tweak this. * - * @type {() => ExtMap} */ -const getExportExtensionMap = () => { +const getExportExtensionMap = (): ExtMap => { return DEFAULT_PKG_EXT_MAP; }; +interface ValidateExportsOptions { + extMap: ExtMap; + pkg: PackageJson; +} + /** * @internal * * @description validate the `require` and `import` properties of a given exports maps from the package.json * returning if any errors are found. * - * @type {(_exports: unknown, options: {extMap: ExtMap; pkg: PackageJson}) => string[]} */ -const validateExports = (_exports, options) => { +const validateExports = (_exports: ExportWithMeta[], options: ValidateExportsOptions): string[] => { const { extMap, pkg } = options; const ext = extMap[pkg.type || 'commonjs']; @@ -303,27 +328,17 @@ const validateExports = (_exports, options) => { return errors; }; -/** - * @typedef {Object} Export - * @property {string} _path the path of the export, `.` for the root. - * @property {string=} types the path to the types file - * @property {string} source the path to the source file - * @property {string=} require the path to the commonjs require file - * @property {string=} import the path to the esm import file - * @property {string=} default the path to the default file - */ +interface ParseExportsOptions { + extMap: ExtMap; + pkg: PackageJson; +} /** * @description parse the exports map from the package.json into a standardised * format that we can use to generate build tasks from. - * - * @type {(args: { extMap: ExtMap, pkg: PackageJson }) => Export[]} */ -const parseExports = ({ extMap, pkg }) => { - /** - * @type {Export} - */ - const rootExport = { +const parseExports = ({ extMap, pkg }: ParseExportsOptions): ExportWithMeta[] => { + const rootExport: ExportWithMeta = { _path: '.', types: pkg.types, source: pkg.source, @@ -332,15 +347,9 @@ const parseExports = ({ extMap, pkg }) => { default: pkg.module || pkg.main, }; - /** - * @type {Export[]} - */ - const extraExports = []; + const extraExports: ExportWithMeta[] = []; - /** - * @type {string[]} - */ - const errors = []; + const errors: string[] = []; if (pkg.exports) { if (!pkg.exports['./package.json']) { @@ -394,14 +403,14 @@ const parseExports = ({ extMap, pkg }) => { }); } - const _exports = [ + const _exports: ExportWithMeta[] = [ /** * In the case of strapi plugins, we don't have a root export because we * ship a server side and client side package. So this can be completely omitted. */ Object.values(rootExport).some((exp) => exp !== rootExport._path && Boolean(exp)) && rootExport, ...extraExports, - ].filter(Boolean); + ].filter((v): v is ExportWithMeta => Boolean(v)); errors.push(...validateExports(_exports, { extMap, pkg })); @@ -412,10 +421,4 @@ const parseExports = ({ extMap, pkg }) => { return _exports; }; -module.exports = { - loadPkg, - validatePkg, - validateExportsOrdering, - getExportExtensionMap, - parseExports, -}; +export { loadPkg, validatePkg, validateExportsOrdering, getExportExtensionMap, parseExports }; diff --git a/packages/core/strapi/src/services/entity-service/types/params/filters/index.ts b/packages/core/strapi/src/services/entity-service/types/params/filters/index.ts index daddfccff3..c3ec0c3730 100644 --- a/packages/core/strapi/src/services/entity-service/types/params/filters/index.ts +++ b/packages/core/strapi/src/services/entity-service/types/params/filters/index.ts @@ -2,7 +2,7 @@ import type { Attribute, Common, Utils } from '../../../../../types'; import type * as Operator from './operators'; import type * as AttributeUtils from '../attributes'; -import type Params from '../index'; +import type * as Params from '../index'; export { Operator }; diff --git a/packages/utils/tsconfig/client.json b/packages/utils/tsconfig/client.json index 5f85e459b8..b7ea54685b 100644 --- a/packages/utils/tsconfig/client.json +++ b/packages/utils/tsconfig/client.json @@ -1,10 +1,15 @@ { "$schema": "http://json.schemastore.org/tsconfig", - "extends": "./base.json", "compilerOptions": { - "module": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], + "module": "esnext", + "moduleResolution": "bundler", + "target": "es2015", "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, "noEmit": true } }