From e370dc79c6f4eda26e7e19b4fbc8af23b6f24c83 Mon Sep 17 00:00:00 2001 From: Ludwig Stockbauer-Muhr Date: Thu, 3 Oct 2024 16:01:11 +0200 Subject: [PATCH] feat: add types for `cds.build` --- apis/build.d.ts | 159 ++++++++++++++++++++++ apis/facade.d.ts | 1 + test/typescript/apis/project/cds-build.ts | 36 +++++ 3 files changed, 196 insertions(+) create mode 100644 apis/build.d.ts create mode 100644 test/typescript/apis/project/cds-build.ts diff --git a/apis/build.d.ts b/apis/build.d.ts new file mode 100644 index 00000000..7eda6fda --- /dev/null +++ b/apis/build.d.ts @@ -0,0 +1,159 @@ +import { CSN } from './csn' + +/** + * WARNING: `cds.build` is only accessible during running `cds build` via the CDS SDK. + * You can only use it in build contexts like registering a custom build plugin inside `cds-plugin.js` + */ +export namespace build { + type TaskDefaults = { src?: string, dest?: string, options?: object } & Record; + type Task = { src: string, dest: string, for: string, options: object } & Record; + type Context = { options: Record, tasks: Task[] }; + type FileWriter = { + /** + * @param {string} dest - absolute or relative file path. Relative paths will be resolved to this task's destination path. + */ + to: (dest: string) => Promise, + }; + type Severity = 'Info' | 'Warning' | 'Error'; + + /** + * The build plugin creates the build output for a dedicated build task. It is uniquely identified + * by the build task's `for` or `use` property. The `use` property represents + * the fully qualified node module path of the build plugin implementation. + * + * The build task engine defines the following protocol. The methods are invoked in descending order: + * - init() - optional + * - get priority() - optional + * - async clean() + * - async build() + * + * The reflected CSN can be accessed using the async method `model()`. + */ + abstract class Plugin { + static taskDefaults: TaskDefaults + static readonly INFO: Severity + static readonly WARNING: Severity + static readonly ERROR: Severity + + /** + * Determines whether a task of this type will be created when cds build is executed, + * @returns `true` by default + */ + static hasTask(): boolean; + + /** + * Returns the build task executed by this build plugin. + */ + get task(): Task; + + /** + * Returns a list of build and compiler messages created by this build plugin. + * Supported message severities are 'Info', 'Warning', and 'Error'. + */ + get messages(): string[]; + + /** + * Returns the list of files and folders written by this build plugin. + */ + get files(): string[]; + + /** + * Returns the build context + */ + get context(): Context; + + /** + * Returns the priority of this plugin as number, where 1024 represents the default value + * to ensure that custom plugins are by default executed before the built-in plugins. + * The higher the priority value, the sooner the build plugin will be run. + * The range of valid priority values is from -1024 to +1024. + * Built-in plugins have a priority value range from 0 - 524. + */ + get priority(): number; + + /** + * Called by the framework immediately after this instance has been created. + * The instance has already been fully initialized. + */ + init(): void; + + /** + * Called by the framework to create the artifacts of this build plugin. + */ + abstract build(): Promise; + + /** + * Called by the framework immediately before 'build' to delete any output created by this build plugin. + * + * Note: The `BuildTaskEngine` is cleaning the common generation target folder if the build is + * executed in staging mode, e.g. build.target: "gen". + */ + clean(): Promise; + + /** + * Asynchronously write the given content to a given file path. + * If the file exists the content is replaced. If the file does not exist, a new file will be created. + * The file name is stored in the list of files written by this build plugin. + * @param {any} data - If data is of type object the JSON-stringified version is written. + * @returns {FileWriter} object to write the file asynchronously to its destination + */ + write(data: any): FileWriter; + + /** + * Asynchronously copies a single file or the entire directory structure from 'src' to 'dest', including subdirectories and files. + * + * Note: The file names are stored in the list of files written by this build plugin. + * + * @param {string} src The absolute or relative source path of the file or directory to copy. + * Relative paths will be resolved to this task's source path. + * @returns {FileWriter} object to copy the file asynchronously to its destination + */ + copy(src: string): FileWriter; + + /** + * Adds the given user message and severity to the list of messages issued by this build task. + *

+ * User messages will be logged after CDS build has been finished based on the log-level that has been set. + * By default messages with severity warning and error will be logged. + * @param {string} message the message text + * @param {string} severity the severity of the message, if ommitted 'Error' is used + */ + pushMessage(message: string, severity?: Severity): void; + + /** + * Returns a compiled CSN model according to the model paths defined by this build task. + * The model includes model enhancements defined by feature toggles, if any. + * @return {object} the compiled CSN model + */ + model(): Promise; + + /** + * Returns a compiled base model CSN according to the model paths defined by this build task. + * The base model does not include any model enhancements defined by feature toggles. + * @return {CSN} the compiled base model CSN + */ + baseModel(): Promise; + + options(): { messages: string[] }; + + /** + * Adds the given fully qualified file path to the list of files that are written by this build task. + */ + pushFile(filePath: string): void; + } + + /** + * Registers plugin class with the given identifier + * + * @param id identifier of plugin + * @param plugin Plugin implementation that inherits from `cds.build.Plugin` + */ + export function register(id: string, plugin: { new (): Plugin }): void; + + /** + * Executes cds build in the directory defined by cds.root. + * + * @param {Record} options - command options as defined by build command. + */ + export function build(options: Record): Promise +} diff --git a/apis/facade.d.ts b/apis/facade.d.ts index 366cebd5..6b914b78 100644 --- a/apis/facade.d.ts +++ b/apis/facade.d.ts @@ -5,6 +5,7 @@ export * from './models' export * from './services' export * from './events' export * from './utils' +export * from './build' export * from './cqn' export * from './global' export { log, debug } from './log' diff --git a/test/typescript/apis/project/cds-build.ts b/test/typescript/apis/project/cds-build.ts new file mode 100644 index 00000000..47fb594f --- /dev/null +++ b/test/typescript/apis/project/cds-build.ts @@ -0,0 +1,36 @@ +import cds from "@sap/cds"; + +class MyPlugin extends cds.build.Plugin { + static taskDefaults: cds.build.TaskDefaults = { src: "." }; + async build(): Promise { + await this.write({ content: "" }).to("my target"); + + const model = await this.model(); + model.definitions!["entity"].elements.ID; + + this.pushMessage("error message"); + this.pushMessage("warning message", cds.build.Plugin.WARNING); + this.pushMessage("info message", "Info"); + + // @ts-expect-error + this.pushMessage("invalid severity", "aa"); + + this.pushFile("./path-to-file"); + + this.files[0].startsWith(""); + this.context.tasks.length; + this.context.tasks.find(t => t.for === "nodejs"); + this.context.options.test === true + this.messages.length + + this.task.src; + this.task.dest; + } + get priority(): number { + return -1; + } +} + +cds.build.register("custom", MyPlugin); + +cds.build.build({}) \ No newline at end of file