diff --git a/packages/config/src/configProcessor/helpers/schema.ts b/packages/config/src/configProcessor/helpers/schema.ts index 4880d56c..f8d64fc4 100644 --- a/packages/config/src/configProcessor/helpers/schema.ts +++ b/packages/config/src/configProcessor/helpers/schema.ts @@ -424,6 +424,84 @@ const createRuleSchema = (isRequestPhase = false) => ({ }, }); +const schemaStorage = { + type: 'object', + properties: { + name: { + type: 'string', + minLength: 6, + maxLength: 63, + pattern: '^.{6,63}$', + errorMessage: "The 'name' field must be a string between 6 and 63 characters.", + }, + dir: { + type: 'string', + errorMessage: "The 'dir' field must be a string.", + }, + edgeAccess: { + type: 'string', + enum: ['read_only', 'read_write', 'restricted'], + errorMessage: "The 'edge_access' field must be one of: read_only, read_write, restricted.", + }, + }, + required: ['name', 'dir'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in storage items.', + required: "The 'name' and 'dir' fields are required.", + }, +}; + +const schemaFunction = { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string", + }, + path: { + type: 'string', + errorMessage: "The 'path' field must be a string", + }, + args: { + type: 'object', + errorMessage: "The 'args' field must be an object", + }, + bindings: { + type: 'object', + properties: { + storage: { + type: 'object', + properties: { + bucket: { + type: 'string', + errorMessage: "The 'bucket' field must be a string", + }, + prefix: { + type: 'string', + errorMessage: "The 'prefix' field must be a string", + }, + }, + required: ['bucket'], + additionalProperties: false, + errorMessage: { + type: "The 'storage' field must be an object", + additionalProperties: 'No additional properties are allowed in the storage object', + required: "The 'bucket' field is required in the storage object", + }, + }, + }, + additionalProperties: false, + errorMessage: { + type: "The 'bindings' field must be an object", + additionalProperties: 'No additional properties are allowed in the bindings object', + }, + }, + }, + required: ['name', 'path'], + additionalProperties: false, +}; + const azionConfigSchema = { $id: 'azionConfig', definitions: { @@ -463,6 +541,10 @@ const azionConfigSchema = { type: 'string', errorMessage: "The 'ext' field in preset metadata must be a string", }, + registry: { + type: 'string', + errorMessage: "The 'registry' field in preset metadata must be a string", + }, }, required: ['name'], additionalProperties: false, @@ -520,30 +602,7 @@ const azionConfigSchema = { }, functions: { type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string', - errorMessage: "The function's 'name' field must be a string", - }, - path: { - type: 'string', - errorMessage: "The function's 'path' field must be a string", - }, - args: { - type: 'object', - additionalProperties: true, - errorMessage: "The function's 'args' field must be an object", - }, - }, - required: ['name', 'path'], - additionalProperties: false, - errorMessage: { - additionalProperties: 'No additional properties are allowed in function items', - required: "Both 'name' and 'path' fields are required for each function", - }, - }, + items: schemaFunction, }, rules: { type: 'object', @@ -961,7 +1020,11 @@ const azionConfigSchema = { }, }, }, + required: ['name'], additionalProperties: false, + errorMessage: { + required: "The 'name' field is required in the domain object.", + }, }, purge: { type: 'array', @@ -1295,6 +1358,11 @@ const azionConfigSchema = { type: "The 'waf' field must be an array", }, }, + storage: { + type: 'array', + items: schemaStorage, + errorMessage: "The 'storage' field must be an array of storage items.", + }, }, additionalProperties: false, errorMessage: { diff --git a/packages/config/src/configProcessor/helpers/schemaManifest.ts b/packages/config/src/configProcessor/helpers/schemaManifest.ts index 41d9e665..bfb5a926 100644 --- a/packages/config/src/configProcessor/helpers/schemaManifest.ts +++ b/packages/config/src/configProcessor/helpers/schemaManifest.ts @@ -719,7 +719,33 @@ const schemaApplicationRules = { additionalProperties: false, }; -// ... resto do arquivo mantido como está ... +const schemaStorageManifest = { + type: 'object', + properties: { + name: { + type: 'string', + minLength: 6, + maxLength: 63, + pattern: '^.{6,63}$', + errorMessage: "The 'name' field must be a string between 6 and 63 characters.", + }, + dir: { + type: 'string', + errorMessage: "The 'dir' field must be a string.", + }, + edge_access: { + type: 'string', + enum: ['read_only', 'read_write', 'restricted'], + errorMessage: "The 'edge_access' field must be one of: read_only, read_write, restricted.", + }, + }, + required: ['name', 'dir'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in storage items.', + required: "The 'name' and 'dir' fields are required.", + }, +}; const schemaApplicationManifest = { type: 'object', @@ -806,6 +832,58 @@ const schemaApplicationManifest = { }, }; +const schemaFunctionManifest = { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string", + }, + target: { + type: 'string', + errorMessage: "The 'target' field must be a string", + }, + args: { + type: 'object', + errorMessage: "The 'args' field must be an object", + }, + bindings: { + type: 'object', + properties: { + edge_storage: { + type: 'array', + items: { + type: 'object', + properties: { + bucket: { + type: 'string', + errorMessage: "The 'bucket' field must be a string", + }, + prefix: { + type: 'string', + errorMessage: "The 'prefix' field must be a string", + }, + }, + required: ['bucket'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in edge_storage items', + required: "The 'bucket' field is required", + }, + }, + errorMessage: "The 'edge_storage' field must be an array of storage bindings", + }, + }, + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in bindings object', + }, + }, + }, + required: ['name', 'target'], + additionalProperties: false, +}; + const schemaManifest = { type: 'object', properties: { @@ -838,6 +916,16 @@ const schemaManifest = { items: schemaApplicationManifest, errorMessage: "The 'application' field must be an array of application items.", }, + storage: { + type: 'array', + items: schemaStorageManifest, + errorMessage: "The 'storage' field must be an array of storage items.", + }, + function: { + type: 'array', + items: schemaFunctionManifest, + errorMessage: "The 'function' field must be an array of function items.", + }, }, }; export { schemaManifest }; diff --git a/packages/config/src/configProcessor/processConfig/index.test.ts b/packages/config/src/configProcessor/processConfig/index.test.ts index 8cc3a0e7..ac953c6a 100644 --- a/packages/config/src/configProcessor/processConfig/index.test.ts +++ b/packages/config/src/configProcessor/processConfig/index.test.ts @@ -9,9 +9,6 @@ describe('processConfig', () => { build: { preset: 'next', polyfills: true, - custom: { - minify: true, - }, }, }; expect(processConfig(config)).toEqual( @@ -19,9 +16,6 @@ describe('processConfig', () => { build: { preset: 'next', polyfills: true, - custom: { - minify: true, - }, }, }), ); diff --git a/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts index 1168428a..16333bc1 100644 --- a/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts +++ b/packages/config/src/configProcessor/processStrategy/implementations/functionsProcessConfigStrategy.ts @@ -7,16 +7,41 @@ import ProcessConfigStrategy from '../processConfigStrategy'; * @description This class is implementation of the Functions ProcessConfig Strategy. */ class FunctionsProcessConfigStrategy extends ProcessConfigStrategy { + private validateStorageBinding(config: AzionConfig, bucketName: string, functionName: string) { + if (!Array.isArray(config?.storage) || !config.storage.find((storage) => storage.name === bucketName)) { + throw new Error( + `Function "${functionName}" references storage bucket "${bucketName}" which is not defined in the storage configuration.`, + ); + } + } + transformToManifest(config: AzionConfig) { if (!Array.isArray(config?.functions) || config?.functions.length === 0) { return; } - return config.functions.map((func) => ({ - name: func.name, - target: func.path, - args: func.args || {}, - })); + return config.functions.map((func) => { + // Validar se o bucket referenciado existe + if (func.bindings?.storage?.bucket) { + this.validateStorageBinding(config, func.bindings.storage.bucket, func.name); + } + + return { + name: func.name, + target: func.path, + args: func.args || {}, + bindings: func.bindings + ? { + edge_storage: func.bindings.storage + ? { + bucket: func.bindings.storage.bucket, + prefix: func.bindings.storage.prefix, + } + : undefined, + } + : undefined, + }; + }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -24,12 +49,32 @@ class FunctionsProcessConfigStrategy extends ProcessConfigStrategy { if (!Array.isArray(payload?.functions) || payload?.functions.length === 0) { return; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any - transformedPayload.functions = payload.functions.map((func: any) => ({ - name: func.name, - path: func.target, - args: func.args || {}, - })); + transformedPayload.functions = payload.functions.map((func: any) => { + const config = { + name: func.name, + path: func.target, + args: func.args || {}, + bindings: func.bindings + ? { + storage: func.bindings.edge_storage + ? { + bucket: func.bindings.edge_storage.bucket, + prefix: func.bindings.edge_storage.prefix, + } + : undefined, + } + : undefined, + }; + + // Validar se o bucket referenciado existe + if (config.bindings?.storage?.bucket) { + this.validateStorageBinding(transformedPayload, config.bindings.storage.bucket, config.name); + } + + return config; + }); return transformedPayload.functions; } diff --git a/packages/config/src/configProcessor/processStrategy/implementations/storageProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/storageProcessConfigStrategy.ts new file mode 100644 index 00000000..e5c860ff --- /dev/null +++ b/packages/config/src/configProcessor/processStrategy/implementations/storageProcessConfigStrategy.ts @@ -0,0 +1,39 @@ +import { AzionConfig } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * StorageProcessConfigStrategy + * @class StorageProcessConfigStrategy + * @description This class is implementation of the Storage ProcessConfig Strategy. + */ +class StorageProcessConfigStrategy extends ProcessConfigStrategy { + transformToManifest(config: AzionConfig) { + if (!Array.isArray(config?.storage) || config?.storage.length === 0) { + return; + } + + return config.storage.map((item) => ({ + name: item.name, + edge_access: item.edgeAccess || 'read_only', + dir: item.dir, + })); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformToConfig(payload: any, transformedPayload: AzionConfig) { + const storageConfig = payload.storage; + if (!Array.isArray(storageConfig) || storageConfig.length === 0) { + return; + } + + transformedPayload.storage = storageConfig.map((item) => ({ + name: item.name, + edgeAccess: item.edge_access || 'read_only', + dir: item.dir, + })); + + return transformedPayload.storage; + } +} + +export default StorageProcessConfigStrategy; diff --git a/packages/config/src/configProcessor/processStrategy/index.ts b/packages/config/src/configProcessor/processStrategy/index.ts index b3fc7d93..d5f8fdca 100644 --- a/packages/config/src/configProcessor/processStrategy/index.ts +++ b/packages/config/src/configProcessor/processStrategy/index.ts @@ -9,6 +9,7 @@ import WafProcessConfigStrategy from '../processStrategy/implementations/secure/ import ProcessConfigContext from '../processStrategy/processConfigContext'; import FunctionsProcessConfigStrategy from './implementations/functionsProcessConfigStrategy'; import FirewallProcessConfigStrategy from './implementations/secure/firewallProcessConfigStrategy'; +import StorageProcessConfigStrategy from './implementations/storageProcessConfigStrategy'; function factoryProcessContext() { const processConfigContext = new ProcessConfigContext(); @@ -19,6 +20,7 @@ function factoryProcessContext() { processConfigContext.setStrategy('purge', new PurgeProcessConfigStrategy()); processConfigContext.setStrategy('networkList', new NetworkListProcessConfigStrategy()); processConfigContext.setStrategy('waf', new WafProcessConfigStrategy()); + processConfigContext.setStrategy('storage', new StorageProcessConfigStrategy()); processConfigContext.setStrategy('firewall', new FirewallProcessConfigStrategy()); processConfigContext.setStrategy('functions', new FunctionsProcessConfigStrategy()); // Rules must be last to apply to behaviors (origin, cache...) diff --git a/packages/config/src/configProcessor/validateConfig/index.test.ts b/packages/config/src/configProcessor/validateConfig/index.test.ts index b51259ec..814c72e6 100644 --- a/packages/config/src/configProcessor/validateConfig/index.test.ts +++ b/packages/config/src/configProcessor/validateConfig/index.test.ts @@ -8,9 +8,6 @@ describe('generate', () => { build: { preset: 'next', polyfills: true, - custom: { - minify: true, - }, }, }; expect(() => validateConfig(config)).not.toThrow(); diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 9095200c..86a950bd 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -341,6 +341,24 @@ export type AzionNetworkList = { listContent: string[] | number[]; }; +/** + * Storage binding configuration for Azion. + */ +export type AzionStorageBinding = { + /** Storage bucket name */ + bucket: string; + /** Storage prefix */ + prefix: string; +}; + +/** + * Bindings configuration for Azion. + */ +export type AzionBindings = { + /** Storage bindings */ + storage?: AzionStorageBinding; +}; + /** * Function configuration for Azion. */ @@ -351,6 +369,20 @@ export type AzionFunction = { path: string; /** Optional arguments to be passed to the function */ args?: Record; + /** Function bindings */ + bindings?: AzionBindings; +}; + +/** + * Storage configuration for Azion. + */ +export type AzionBucket = { + /** Storage name */ + name: string; + /** Edge access type */ + edgeAccess?: 'read_only' | 'read_write' | 'restricted'; + /** Storage path */ + dir: string; }; /** @@ -377,6 +409,8 @@ export type AzionConfig = { networkList?: AzionNetworkList[]; /** WAF configuration */ waf?: AzionWaf[]; + /** Storage configurations */ + storage?: AzionBucket[]; }; /**