Migrate latest to ts

This commit is contained in:
Alexandre Bodin 2023-09-07 01:12:44 +02:00
parent c779954631
commit ddaa9e6775
18 changed files with 328 additions and 1387 deletions

View File

@ -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 };

View File

@ -1,4 +0,0 @@
{
"name": "testing",
"version": "0.0.0"
}

View File

@ -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""
`);
});
});
});

View File

@ -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,
};

View File

@ -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<TService extends Common.Service = Common.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<TContentTypeUID extends Common.UID.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<Strapi>;
/**
* 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<Strapi>;
/**
* 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<void>;
/**
* Run all functions registered for a given lifecycle. (Strapi core, user app, plugins)
*/
runLifecyclesFunctions<T extends Lifecycles[keyof Lifecycles]>(lifecycleName: T): Promise<void>;
/**
* Load the application if needed and start the server
*/
start(): Promise<void>;
/**
* Stop the server and provide a custom error and message
*/
stopWithError<TError = unknown>(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<Strapi>;
/**
* 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<void>;
/**
* 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<void>;
/**
* Start listening for incomming requests
*/
listen(): Promise<void | Error>;
/**
* 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<void>;
/**
* Load the admin panel server logic into the server code and initialize its configuration.
*/
loadAdmin(): Promise<void>;
/**
* Resolve every enabled plugin and load them into the application.
*/
loadPlugins(): Promise<void>;
/**
* Load every global policies in the policies container by
* reading from the `strapi.dirs.dist.policies` directory.
*/
loadPolicies(): Promise<void>;
/**
* Load every APIs and their components (config, routes, controllers, services,
* policies, middlewares, content-types) in the API container.
*/
loadAPIs(): Promise<void>;
/**
* Resolve every components in the user application and store them in `strapi.components`
*/
loadComponents(): Promise<void>;
/**
* Load every global and core middlewares in the middlewares container by
* reading from the `strapi.dirs.dist.middlewares` and internal middlewares directory.
*/
loadMiddlewares(): Promise<void>;
/**
* Load the user application in the server by reading the `src/index.js` file.
*/
loadApp(): Promise<void>;
/**
* 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;
};
}

View File

@ -1,27 +1,24 @@
'use strict'; import fs from 'fs/promises';
import boxen from 'boxen';
const fs = require('fs/promises'); import chalk from 'chalk';
const boxen = require('boxen'); import ora from 'ora';
const chalk = require('chalk'); import { createLogger } from '../../../utils/logger';
const ora = require('ora'); import { notifyExperimentalCommand } from '../../../utils/helpers';
const { createLogger } = require('../../../utils/logger'); import {
const { notifyExperimentalCommand } = require('../../../utils/helpers');
const {
loadPkg, loadPkg,
validatePkg, validatePkg,
validateExportsOrdering, validateExportsOrdering,
getExportExtensionMap, getExportExtensionMap,
} = require('../../../utils/pkg'); } from '../../../utils/pkg';
const { createBuildContext, createBuildTasks } = require('../../../builders/packages'); import { createBuildContext, createBuildTasks } from '../../../builders/packages';
const { buildTaskHandlers } = require('../../../builders/tasks'); import { buildTaskHandlers } from '../../../builders/tasks';
/** interface ActionOptions {
* force?: boolean;
* @param {object} args debug?: boolean;
* @param {boolean} args.force }
* @param {boolean} args.debug
*/ export default async ({ force, debug }: ActionOptions) => {
module.exports = async ({ force, debug }) => {
const logger = createLogger({ debug, timestamp: false }); const logger = createLogger({ debug, timestamp: false });
try { try {
/** /**
@ -106,13 +103,10 @@ module.exports = async ({ force, debug }) => {
} }
for (const task of buildTasks) { for (const task of buildTasks) {
/** const handler = buildTaskHandlers(task);
* @type {import('../../../builders/tasks').TaskHandler<any>}
*/
const handler = buildTaskHandlers[task.type];
handler.print(ctx, 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) { if (err instanceof Error) {
logger.error(err.message); logger.error(err.message);
} }
@ -124,6 +118,7 @@ module.exports = async ({ force, debug }) => {
logger.error( logger.error(
'There seems to be an unexpected error, try again with --debug for more information \n' 'There seems to be an unexpected error, try again with --debug for more information \n'
); );
if (err instanceof Error && err.stack) {
console.log( console.log(
chalk.red( chalk.red(
boxen(err.stack, { boxen(err.stack, {
@ -132,6 +127,7 @@ module.exports = async ({ force, debug }) => {
}) })
) )
); );
}
process.exit(1); process.exit(1);
} }
}; };

View File

@ -1,13 +1,11 @@
'use strict'; import { forceOption } from '../../../utils/commander';
import { getLocalScript } from '../../../utils/helpers';
const { forceOption } = require('../../../utils/commander'); import type { StrapiCommand } from '../../../types';
const { getLocalScript } = require('../../../utils/helpers');
/** /**
* `$ strapi plugin:build` * `$ strapi plugin:build`
* @param {import('../../../../types/core/commands').AddCommandOptions} options
*/ */
module.exports = ({ command }) => { const command: StrapiCommand = ({ command }) => {
command command
.command('plugin:build') .command('plugin:build')
.description('Bundle your strapi plugin for publishing.') .description('Bundle your strapi plugin for publishing.')
@ -15,3 +13,5 @@ module.exports = ({ command }) => {
.option('-d, --debug', 'Enable debugging mode with verbose logs', false) .option('-d, --debug', 'Enable debugging mode with verbose logs', false)
.action(getLocalScript('plugin/build-command')); .action(getLocalScript('plugin/build-command'));
}; };
export default command;

View File

@ -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'); interface BuildContextArgs {
const browserslistToEsbuild = require('browserslist-to-esbuild'); cwd: string;
extMap: ExtMap;
logger: Logger;
pkg: PackageJson;
}
const { parseExports } = require('../utils/pkg'); export type Target = 'node' | 'web' | '*';
/** export type Targets = {
* @typedef {Object} BuildContextArgs [target in Target]: string[];
* @property {string} cwd };
* @property {import('../utils/pkg').ExtMap} extMap
* @property {import('../utils/logger').Logger} logger
* @property {import('../utils/pkg').PackageJson} pkg
*/
/** export interface BuildContext {
* @typedef {Object} Targets cwd: string;
* @property {string[]} node exports: Record<string, Export>;
* @property {string[]} web external: string[];
* @property {string[]} * extMap: ExtMap;
*/ logger: Logger;
pkg: PackageJson;
targets: Targets;
distPath: string;
}
/** export interface BuildTask {
* @typedef {Object} BuildContext type: 'build:js' | 'build:dts';
* @property {string} cwd entries: unknown[];
* @property {import('../utils/pkg').Export[]} exports format?: 'cjs' | 'es';
* @property {string[]} external [key: string]: unknown;
* @property {import('../utils/pkg').ExtMap} extMap }
* @property {import('../utils/logger').Logger} logger
* @property {import('../utils/pkg').PackageJson} pkg
* @property {Targets} targets
*/
const DEFAULT_BROWSERS_LIST_CONFIG = [ const DEFAULT_BROWSERS_LIST_CONFIG = [
'last 3 major versions', 'last 3 major versions',
@ -43,30 +48,33 @@ const DEFAULT_BROWSERS_LIST_CONFIG = [
* @description Create a build context for the pipeline we're creating, * @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 * this is shared among tasks so they all use the same settings for core pieces
* such as a target, distPath, externals etc. * such as a target, distPath, externals etc.
*
* @type {(args: BuildContextArgs) => Promise<BuildContext>}
*/ */
const createBuildContext = async ({ cwd, extMap, logger, pkg }) => { const createBuildContext = async ({
cwd,
extMap,
logger,
pkg,
}: BuildContextArgs): Promise<BuildContext> => {
const targets = { const targets = {
'*': browserslistToEsbuild(pkg.browserslist ?? DEFAULT_BROWSERS_LIST_CONFIG), '*': browserslistToEsbuild(pkg.browserslist ?? DEFAULT_BROWSERS_LIST_CONFIG),
node: browserslistToEsbuild(['node 16.0.0']), node: browserslistToEsbuild(['node 16.0.0']),
web: ['esnext'], web: ['esnext'],
}; };
const exports = parseExports({ extMap, pkg }).reduce((acc, x) => { const exportsArray = parseExports({ extMap, pkg }).reduce((acc, x) => {
const { _path: exportPath, ...exportEntry } = x; const { _path: exportPath, ...exportEntry } = x;
return { ...acc, [exportPath]: exportEntry }; return { ...acc, [exportPath]: exportEntry };
}, {}); }, {} as Record<string, Export>);
const external = [ const external = [
...(pkg.dependencies ? Object.keys(pkg.dependencies) : []), ...(pkg.dependencies ? Object.keys(pkg.dependencies) : []),
...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []), ...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []),
]; ];
const outputPaths = Object.values(exports) const outputPaths = Object.values(exportsArray)
.flatMap((exportEntry) => { .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)); .map((p) => path.resolve(cwd, p));
@ -86,7 +94,7 @@ const createBuildContext = async ({ cwd, extMap, logger, pkg }) => {
logger, logger,
cwd, cwd,
pkg, pkg,
exports, exports: exportsArray,
external, external,
distPath, distPath,
targets, targets,
@ -94,17 +102,11 @@ const createBuildContext = async ({ cwd, extMap, logger, pkg }) => {
}; };
}; };
/** const pathContains = (containerPath: string, itemPath: string): boolean => {
* @type {(containerPath: string, itemPath: string) => boolean}
*/
const pathContains = (containerPath, itemPath) => {
return !path.relative(containerPath, itemPath).startsWith('..'); return !path.relative(containerPath, itemPath).startsWith('..');
}; };
/** const findCommonDirPath = (filePaths: string[]) => {
* @type {(filePaths: string[]) => string | undefined}
*/
const findCommonDirPath = (filePaths) => {
/** /**
* @type {string | undefined} * @type {string | undefined}
*/ */
@ -138,38 +140,36 @@ const findCommonDirPath = (filePaths) => {
return commonPath; return commonPath;
}; };
/** type Task = ViteTask | DtsTask;
* @typedef {import('./tasks/vite').ViteTask | import('./tasks/dts').DtsTask} BuildTask
*/
/** /**
* @description Create the build tasks for the pipeline, this * @description Create the build tasks for the pipeline, this
* comes from the exports map we've created in the build context. * comes from the exports map we've created in the build context.
* But handles each export line uniquely with space to add more * But handles each export line uniquely with space to add more
* as the standard develops. * as the standard develops.
*
* @type {(args: BuildContext) => Promise<BuildTask[]>}
*/ */
const createBuildTasks = async (ctx) => { const createBuildTasks = async (ctx: BuildContext): Promise<Task[]> => {
/** const tasks: Task[] = [];
* @type {BuildTask[]}
*/
const tasks = [];
/** const dtsTask: DtsTask = {
* @type {import('./tasks/dts').DtsTask}
*/
const dtsTask = {
type: 'build:dts', type: 'build:dts',
entries: [], entries: [],
}; };
/** const viteTasks: Record<string, ViteTask> = {};
* @type {Record<string, import('./tasks/vite').ViteTask>}
*/
const viteTasks = {};
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}`; const buildId = `${format}:${output}`;
if (viteTasks[buildId]) { if (viteTasks[buildId]) {
@ -208,11 +208,8 @@ const createBuildTasks = async (ctx) => {
}); });
} }
/**
* @type {keyof Target}
*/
// eslint-disable-next-line no-nested-ternary // eslint-disable-next-line no-nested-ternary
const runtime = exp._path.includes('strapi-admin') const runtime: Target = exp._path.includes('strapi-admin')
? 'web' ? 'web'
: exp._path.includes('strapi-server') : exp._path.includes('strapi-server')
? 'node' ? 'node'
@ -224,7 +221,7 @@ const createBuildTasks = async (ctx) => {
*/ */
createViteTask('cjs', runtime, { createViteTask('cjs', runtime, {
path: exp._path, path: exp._path,
entry: exp.source, entry: exp.source ?? 'src/index.ts',
output: exp.require, output: exp.require,
}); });
} }
@ -235,7 +232,7 @@ const createBuildTasks = async (ctx) => {
*/ */
createViteTask('es', runtime, { createViteTask('es', runtime, {
path: exp._path, path: exp._path,
entry: exp.source, entry: exp.source ?? 'src/index.ts',
output: exp.import, output: exp.import,
}); });
} }
@ -246,7 +243,4 @@ const createBuildTasks = async (ctx) => {
return tasks; return tasks;
}; };
module.exports = { export { createBuildContext, createBuildTasks };
createBuildContext,
createBuildTasks,
};

View File

@ -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'); interface LoadTsConfigOptions {
const chalk = require('chalk'); cwd: string;
const ora = require('ora'); path: string;
const ts = require('typescript'); }
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 * @description Load a tsconfig.json file and return the parsed config
* *
* @internal * @internal
*
* @type {(args: { cwd: string; path: string }) => Promise<ts.ParsedCommandLine>)}
*/ */
const loadTsConfig = async ({ cwd, path }) => { const loadTsConfig = async ({ cwd, path }: LoadTsConfigOptions) => {
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, path); const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, path);
if (!configPath) { if (!configPath) {
@ -24,25 +39,19 @@ const loadTsConfig = async ({ cwd, path }) => {
return ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd); return ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd);
}; };
class TSConfigNotFoundError extends Error { interface BuildTypesOptions {
// eslint-disable-next-line no-useless-constructor cwd: string;
constructor(message, options) { logger: Logger;
super(message, options); outDir: string;
} tsconfig: ts.ParsedCommandLine;
get code() {
return 'TS_CONFIG_NOT_FOUND';
}
} }
/** /**
* @description * @description
* *
* @internal * @internal
*
* @type {(args: { cwd: string; logger: import('../../utils/logger').Logger; outDir: string; tsconfig: ts.ParsedCommandLine }) => Promise<void>}
*/ */
const buildTypes = ({ cwd, logger, outDir, tsconfig }) => { const buildTypes = ({ cwd, logger, outDir, tsconfig }: BuildTypesOptions) => {
const compilerOptions = { const compilerOptions = {
...tsconfig.options, ...tsconfig.options,
declaration: true, declaration: true,
@ -102,23 +111,19 @@ const buildTypes = ({ cwd, logger, outDir, tsconfig }) => {
} }
}; };
/** export interface DtsTaskEntry {
* @typedef {Object} DtsTaskEntry importId: string;
* @property {string} exportPath exportPath: string;
* @property {string} sourcePath sourcePath?: string;
* @property {string} targetPath targetPath: string;
*/ }
/** export interface DtsTask extends BuildTask {
* @typedef {Object} DtsTask type: 'build:dts';
* @property {"build:dts"} type entries: DtsTaskEntry[];
* @property {DtsTaskEntry[]} entries }
*/
/** const dtsTask: TaskHandler<DtsTask> = {
* @type {import('./index').TaskHandler<DtsTask>}
*/
const dtsTask = {
_spinner: null, _spinner: null,
print(ctx, task) { print(ctx, task) {
const entries = [ const entries = [
@ -146,7 +151,7 @@ const dtsTask = {
* TODO: this will not scale and assumes all project sourcePaths are `src/index.ts` * 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... * 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', path: 'tsconfig.build.json',
}).catch((err) => { }).catch((err) => {
if (err instanceof TSConfigNotFoundError) { if (err instanceof TSConfigNotFoundError) {
@ -187,13 +192,13 @@ const dtsTask = {
} }
}, },
async success() { async success() {
this._spinner.succeed('Built type files'); this._spinner?.succeed('Built type files');
}, },
async fail(ctx, task, err) { async fail(ctx, task, err) {
this._spinner.fail('Failed to build type files'); this._spinner?.fail('Failed to build type files');
throw err; throw err;
}, },
}; };
module.exports = { dtsTask }; export { dtsTask };

View File

@ -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<void>} run
* @property {(ctx: import("../packages").BuildContext, task: Task) => Promise<void>} success
* @property {(ctx: import("../packages").BuildContext, task: Task, err: unknown) => Promise<void>} fail
* @property {import('ora').Ora | null} _spinner
*/
/**
* @type {{ "build:js": TaskHandler<import("./vite").ViteTask>; "build:dts": TaskHandler<import("./dts").DtsTask>; }}}
*/
const buildTaskHandlers = {
'build:js': viteTask,
'build:dts': dtsTask,
};
module.exports = {
buildTaskHandlers,
};

View File

@ -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<Task extends BuildTask> {
print: (ctx: BuildContext, task: Task) => void;
run: (ctx: BuildContext, task: Task) => Promise<void>;
success: (ctx: BuildContext, task: Task) => Promise<void>;
fail: (ctx: BuildContext, task: Task, err: unknown) => Promise<void>;
_spinner: Ora | null;
}
const handlers = {
'build:js': viteTask,
'build:dts': dtsTask,
};
const buildTaskHandlers = <T extends ViteTask | DtsTask>(t: T): TaskHandler<T> => {
return handlers[t.type] as TaskHandler<T>;
};
export { buildTaskHandlers };

View File

@ -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'); import type { TaskHandler } from '.';
const { build, createLogger } = require('vite'); import type { BuildContext, BuildTask, Target } from '../packages';
const react = require('@vitejs/plugin-react');
const ora = require('ora');
const chalk = require('chalk');
/** /**
* @internal * @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 { cwd, distPath, targets, external, extMap, pkg } = ctx;
const { entries, format, output, runtime } = task; const { entries, format, output, runtime } = task;
const outputExt = extMap[pkg.type || 'commonjs'][format]; const outputExt = extMap[pkg.type || 'commonjs'][format];
@ -22,10 +23,7 @@ const resolveViteConfig = (ctx, task) => {
customLogger.warnOnce = (msg) => ctx.logger.warn(msg); customLogger.warnOnce = (msg) => ctx.logger.warn(msg);
customLogger.error = (msg) => ctx.logger.error(msg); customLogger.error = (msg) => ctx.logger.error(msg);
/** const config: InlineConfig = {
* @type {import('vite').InlineConfig}
*/
const config = {
configFile: false, configFile: false,
root: cwd, root: cwd,
mode: 'production', mode: 'production',
@ -79,25 +77,20 @@ const resolveViteConfig = (ctx, task) => {
return config; return config;
}; };
/** interface ViteTaskEntry {
* @typedef {Object} ViteTaskEntry path: string;
* @property {string} path entry: string;
* @property {string} entry }
*/
/** export interface ViteTask extends BuildTask {
* @typedef {Object} ViteTask type: 'build:js';
* @property {"build:js"} type entries: ViteTaskEntry[];
* @property {ViteTaskEntry[]} entries format: 'cjs' | 'es';
* @property {string} format output: string;
* @property {string} output runtime: Target;
* @property {keyof import('../packages').Targets} runtime }
*/
/** const viteTask: TaskHandler<ViteTask> = {
* @type {import('./index').TaskHandler<ViteTask>}
*/
const viteTask = {
_spinner: null, _spinner: null,
print(ctx, task) { print(ctx, task) {
const targetLines = [ const targetLines = [
@ -130,15 +123,13 @@ const viteTask = {
} }
}, },
async success() { async success() {
this._spinner.succeed('Built javascript files'); this._spinner?.succeed('Built javascript files');
}, },
async fail(ctx, task, err) { async fail(ctx, task, err) {
this._spinner.fail('Failed to build javascript files'); this._spinner?.fail('Failed to build javascript files');
throw err; throw err;
}, },
}; };
module.exports = { export { viteTask };
viteTask,
};

View File

@ -10,10 +10,8 @@ import consoleCommand from './actions/console/command';
import listContentTypes from './actions/content-types/list/command'; import listContentTypes from './actions/content-types/list/command';
import listControllers from './actions/controllers/list/command'; import listControllers from './actions/controllers/list/command';
import developCommand from './actions/develop/command'; import developCommand from './actions/develop/command';
// import exportCommand from './actions/export/command';
import generateCommand from './actions/generate/command'; import generateCommand from './actions/generate/command';
import listHooks from './actions/hooks/list/command'; import listHooks from './actions/hooks/list/command';
// import importCommand from './actions/import/command';
import installCommand from './actions/install/command'; import installCommand from './actions/install/command';
import listMiddlewares from './actions/middlewares/list/command'; import listMiddlewares from './actions/middlewares/list/command';
import newCommand from './actions/new/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 disableTelemetry from './actions/telemetry/disable/command';
import enableTelemetry from './actions/telemetry/enable/command'; import enableTelemetry from './actions/telemetry/enable/command';
import generateTemplates from './actions/templates/generate/command'; import generateTemplates from './actions/templates/generate/command';
// import transferCommand from './actions/transfer/command';
import generateTsTypes from './actions/ts/generate-types/command'; import generateTsTypes from './actions/ts/generate-types/command';
import uninstallCommand from './actions/uninstall/command'; import uninstallCommand from './actions/uninstall/command';
import versionCommand from './actions/version/command'; import versionCommand from './actions/version/command';
import watchAdminCommand from './actions/watch-admin/command'; import watchAdminCommand from './actions/watch-admin/command';
import buildPluginCommand from './actions/plugin/build-command/command';
const strapiCommands = { const strapiCommands = {
createAdminUser, createAdminUser,
@ -59,6 +57,7 @@ const strapiCommands = {
uninstallCommand, uninstallCommand,
versionCommand, versionCommand,
watchAdminCommand, watchAdminCommand,
buildPluginCommand,
} as const; } as const;
const buildStrapiCommand = (argv: string[], command = new Command()) => { const buildStrapiCommand = (argv: string[], command = new Command()) => {

View File

@ -1,25 +1,14 @@
<<<<<<< HEAD:packages/core/strapi/src/commands/utils/helpers.ts
/* eslint-disable @typescript-eslint/no-var-requires */ /* 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 { has, isString, isArray } from 'lodash/fp';
import resolveCwd from 'resolve-cwd'; import resolveCwd from 'resolve-cwd';
import { prompt } from 'inquirer';
import boxen from 'boxen';
import type { Command } from 'commander'; import type { Command } from 'commander';
=======
'use strict';
/** /**
* Helper functions for the Strapi CLI * 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 bytesPerKb = 1024;
const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB']; const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB'];
@ -133,16 +122,11 @@ const assertCwdContainsStrapiProject = (name: string) => {
try { try {
const pkgJSON = require(`${process.cwd()}/package.json`); const pkgJSON = require(`${process.cwd()}/package.json`);
<<<<<<< HEAD:packages/core/strapi/src/commands/utils/helpers.ts
if (!has('dependencies.@strapi/strapi', pkgJSON)) {
logErrorAndExit();
=======
if ( if (
!has('dependencies.@strapi/strapi', pkgJSON) && !has('dependencies.@strapi/strapi', pkgJSON) &&
!has('devDependencies.@strapi/strapi', pkgJSON) !has('devDependencies.@strapi/strapi', pkgJSON)
) { ) {
logErrorAndExit(name); logErrorAndExit();
>>>>>>> main:packages/core/strapi/lib/commands/utils/helpers.js
} }
} catch (err) { } catch (err) {
logErrorAndExit(); 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 * @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. * 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( console.log(
boxen( boxen(
`The ${chalk.bold( `The ${chalk.bold(
@ -224,8 +205,7 @@ const notifyExperimentalCommand = async ({ force } = {}) => {
} }
}; };
module.exports = { export {
>>>>>>> main:packages/core/strapi/lib/commands/utils/helpers.js
exitWith, exitWith,
assertUrlHasProtocol, assertUrlHasProtocol,
ifOptions, ifOptions,

View File

@ -1,26 +1,22 @@
'use strict'; import chalk from 'chalk';
const chalk = require('chalk'); export interface LoggerOptions {
silent?: boolean;
debug?: boolean;
timestamp?: boolean;
}
/** export interface Logger {
* @typedef {{ silent?: boolean; debug?: boolean; timestamp?: boolean; }} LoggerOptions warnings: number;
*/ errors: number;
debug: (...args: unknown[]) => void;
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
log: (...args: unknown[]) => void;
}
/** const createLogger = (options: LoggerOptions = {}): Logger => {
* @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 { silent = false, debug = false, timestamp = true } = options; const { silent = false, debug = false, timestamp = true } = options;
const state = { errors: 0, warning: 0 }; const state = { errors: 0, warning: 0 };
@ -92,6 +88,4 @@ const createLogger = (options = {}) => {
}; };
}; };
module.exports = { export { createLogger };
createLogger,
};

View File

@ -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'); export interface PackageJson extends Omit<yup.Asserts<typeof packageJsonSchema>, 'exports'> {
const path = require('path'); type: Extensions;
const chalk = require('chalk'); exports?: {
const yup = require('yup'); [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 * Utility functions for loading and validating package.json
@ -18,14 +45,19 @@ const yup = require('yup');
const packageJsonSchema = yup.object({ const packageJsonSchema = yup.object({
name: yup.string().required(), name: yup.string().required(),
version: yup.string().required(), version: yup.string().required(),
type: yup.string().matches(/(commonjs|module)/), type: yup.mixed().oneOf(['commonjs', 'module']),
license: yup.string(), license: yup.string(),
bin: yup.mixed().oneOf([ bin: yup.lazy((value) =>
yup.string(), typeof value === 'object'
yup.object({ ? yup.object(
[yup.string()]: yup.string(), Object.entries(value).reduce((acc, [key]) => {
}), acc[key] = yup.string().required();
]),
return acc;
}, {} as Record<string, yup.SchemaOf<string>>)
)
: yup.string()
),
main: yup.string(), main: yup.string(),
module: yup.string(), module: yup.string(),
source: yup.string(), source: yup.string(),
@ -52,7 +84,7 @@ const packageJsonSchema = yup.object({
} }
return acc; return acc;
}, {}) }, {} as Record<string, yup.SchemaOf<string> | yup.SchemaOf<Export>>)
: undefined : undefined
) )
), ),
@ -64,18 +96,18 @@ const packageJsonSchema = yup.object({
engines: yup.object(), engines: yup.object(),
}); });
/** interface LoadPkgOptions {
* @typedef {import('yup').Asserts<typeof packageJsonSchema>} PackageJson cwd: string;
*/ logger: Logger;
}
/** /**
* @description being a task to load the package.json starting from the current working directory * @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, * 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. * the process will throw with an appropriate error message.
* *
* @type {(args: { cwd: string, logger: import('./logger').Logger }) => Promise<object>}
*/ */
const loadPkg = async ({ cwd, logger }) => { const loadPkg = async ({ cwd, logger }: LoadPkgOptions): Promise<PackageJson> => {
const directory = path.resolve(cwd); const directory = path.resolve(cwd);
const pkgPath = path.join(directory, 'package.json'); 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`. * @description validate the package.json against a standardised schema using `yup`.
* If the validation fails, the process will throw with an appropriate error message. * If the validation fails, the process will throw with an appropriate error message.
*
* @type {(args: { pkg: object }) => Promise<PackageJson | null>}
*/ */
const validatePkg = async ({ pkg }) => { const validatePkg = async ({ pkg }: { pkg: PackageJson }) => {
try { try {
const validatedPkg = await packageJsonSchema.validate(pkg, { const validatedPkg = await packageJsonSchema.validate(pkg, {
strict: true, strict: true,
@ -111,14 +141,14 @@ const validatePkg = async ({ pkg }) => {
case 'required': case 'required':
throw new Error( throw new Error(
`'${err.path}' in 'package.json' is required as type '${chalk.magenta( `'${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': case 'matches':
throw new Error( throw new Error(
`'${err.path}' in 'package.json' must be of type '${chalk.magenta( `'${err.path}' in 'package.json' must be of type '${chalk.magenta(
err.params.regex err.params?.regex
)}' (recieved the value '${chalk.magenta(err.params.value)}')` )}' (recieved the value '${chalk.magenta(err.params?.value)}')`
); );
/** /**
* This will only be thrown if there are keys in the export map * This will only be thrown if there are keys in the export map
@ -127,7 +157,7 @@ const validatePkg = async ({ pkg }) => {
case 'noUnknown': case 'noUnknown':
throw new Error( throw new Error(
`'${err.path}' in 'package.json' contains the unknown key ${chalk.magenta( `'${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( )}, for compatability only the following keys are allowed: ${chalk.magenta(
"['types', 'source', 'import', 'require', 'default']" "['types', 'source', 'import', 'require', 'default']"
)}` )}`
@ -135,8 +165,8 @@ const validatePkg = async ({ pkg }) => {
default: default:
throw new Error( throw new Error(
`'${err.path}' in 'package.json' must be of type '${chalk.magenta( `'${err.path}' in 'package.json' must be of type '${chalk.magenta(
err.params.type err.params?.type
)}' (recieved '${chalk.magenta(typeof err.params.value)}')` )}' (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. * @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 * 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 * there is no `exports` property we check the standard export-like properties on the root
* of the package.json. * of the package.json.
*
* @type {(args: { pkg: object, logger: import('./logger').Logger }) => Promise<PackageJson>}
*/ */
const validateExportsOrdering = async ({ pkg, logger }) => { const validateExportsOrdering = async ({
pkg,
logger,
}: ValidateExportsOrderingOptions): Promise<PackageJson> => {
if (pkg.exports) { if (pkg.exports) {
const exports = Object.entries(pkg.exports); const exports = Object.entries(pkg.exports);
@ -195,7 +231,7 @@ const validateExportsOrdering = async ({ pkg, logger }) => {
}; };
/** @internal */ /** @internal */
function assertFirst(key, arr) { function assertFirst(key: string, arr: string[]) {
const aIdx = arr.indexOf(key); const aIdx = arr.indexOf(key);
// if not found, then we don't care // if not found, then we don't care
@ -207,7 +243,7 @@ function assertFirst(key, arr) {
} }
/** @internal */ /** @internal */
function assertLast(key, arr) { function assertLast(key: string, arr: string[]) {
const aIdx = arr.indexOf(key); const aIdx = arr.indexOf(key);
// if not found, then we don't care // if not found, then we don't care
@ -219,7 +255,7 @@ function assertLast(key, arr) {
} }
/** @internal */ /** @internal */
function assertOrder(keyA, keyB, arr) { function assertOrder(keyA: string, keyB: string, arr: string[]) {
const aIdx = arr.indexOf(keyA); const aIdx = arr.indexOf(keyA);
const bIdx = arr.indexOf(keyB); const bIdx = arr.indexOf(keyB);
@ -231,24 +267,10 @@ function assertOrder(keyA, keyB, arr) {
return aIdx < bIdx; return aIdx < bIdx;
} }
/**
* @typedef {Object} Extensions
* @property {string} commonjs
* @property {string} esm
*/
/**
* @typedef {Object} ExtMap
* @property {Extensions} commonjs
* @property {Extensions} esm
*/
/** /**
* @internal * @internal
*
* @type {ExtMap}
*/ */
const DEFAULT_PKG_EXT_MAP = { const DEFAULT_PKG_EXT_MAP: ExtMap = {
// pkg.type: "commonjs" // pkg.type: "commonjs"
commonjs: { commonjs: {
cjs: '.js', cjs: '.js',
@ -266,21 +288,24 @@ const DEFAULT_PKG_EXT_MAP = {
* We potentially might need to support legacy exports or as package * We potentially might need to support legacy exports or as package
* development continues we have space to tweak this. * development continues we have space to tweak this.
* *
* @type {() => ExtMap}
*/ */
const getExportExtensionMap = () => { const getExportExtensionMap = (): ExtMap => {
return DEFAULT_PKG_EXT_MAP; return DEFAULT_PKG_EXT_MAP;
}; };
interface ValidateExportsOptions {
extMap: ExtMap;
pkg: PackageJson;
}
/** /**
* @internal * @internal
* *
* @description validate the `require` and `import` properties of a given exports maps from the package.json * @description validate the `require` and `import` properties of a given exports maps from the package.json
* returning if any errors are found. * 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 { extMap, pkg } = options;
const ext = extMap[pkg.type || 'commonjs']; const ext = extMap[pkg.type || 'commonjs'];
@ -303,27 +328,17 @@ const validateExports = (_exports, options) => {
return errors; return errors;
}; };
/** interface ParseExportsOptions {
* @typedef {Object} Export extMap: ExtMap;
* @property {string} _path the path of the export, `.` for the root. pkg: PackageJson;
* @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
*/
/** /**
* @description parse the exports map from the package.json into a standardised * @description parse the exports map from the package.json into a standardised
* format that we can use to generate build tasks from. * format that we can use to generate build tasks from.
*
* @type {(args: { extMap: ExtMap, pkg: PackageJson }) => Export[]}
*/ */
const parseExports = ({ extMap, pkg }) => { const parseExports = ({ extMap, pkg }: ParseExportsOptions): ExportWithMeta[] => {
/** const rootExport: ExportWithMeta = {
* @type {Export}
*/
const rootExport = {
_path: '.', _path: '.',
types: pkg.types, types: pkg.types,
source: pkg.source, source: pkg.source,
@ -332,15 +347,9 @@ const parseExports = ({ extMap, pkg }) => {
default: pkg.module || pkg.main, default: pkg.module || pkg.main,
}; };
/** const extraExports: ExportWithMeta[] = [];
* @type {Export[]}
*/
const extraExports = [];
/** const errors: string[] = [];
* @type {string[]}
*/
const errors = [];
if (pkg.exports) { if (pkg.exports) {
if (!pkg.exports['./package.json']) { 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 * 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. * 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, Object.values(rootExport).some((exp) => exp !== rootExport._path && Boolean(exp)) && rootExport,
...extraExports, ...extraExports,
].filter(Boolean); ].filter((v): v is ExportWithMeta => Boolean(v));
errors.push(...validateExports(_exports, { extMap, pkg })); errors.push(...validateExports(_exports, { extMap, pkg }));
@ -412,10 +421,4 @@ const parseExports = ({ extMap, pkg }) => {
return _exports; return _exports;
}; };
module.exports = { export { loadPkg, validatePkg, validateExportsOrdering, getExportExtensionMap, parseExports };
loadPkg,
validatePkg,
validateExportsOrdering,
getExportExtensionMap,
parseExports,
};

View File

@ -2,7 +2,7 @@ import type { Attribute, Common, Utils } from '../../../../../types';
import type * as Operator from './operators'; import type * as Operator from './operators';
import type * as AttributeUtils from '../attributes'; import type * as AttributeUtils from '../attributes';
import type Params from '../index'; import type * as Params from '../index';
export { Operator }; export { Operator };

View File

@ -1,10 +1,15 @@
{ {
"$schema": "http://json.schemastore.org/tsconfig", "$schema": "http://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": { "compilerOptions": {
"module": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"moduleResolution": "bundler",
"target": "es2015",
"jsx": "react-jsx", "jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true "noEmit": true
} }
} }