|  | 
| 1 |  | -import { ManagementClient, SharedModels } from '@kontent-ai/management-sdk'; | 
| 2 |  | -import chalk from 'chalk'; | 
| 3 |  | -import path from 'path'; | 
| 4 |  | -import fs from 'fs'; | 
| 5 |  | -import { listFiles } from './fileUtils'; | 
| 6 |  | -import { TemplateType } from '../models/templateType'; | 
| 7 |  | -import { MigrationModule } from '../types'; | 
| 8 |  | -import { IMigration } from '../models/migration'; | 
| 9 |  | -import { markAsCompleted, wasSuccessfullyExecuted } from './statusManager'; | 
| 10 |  | - | 
| 11 |  | -export const getMigrationDirectory = (): string => { | 
| 12 |  | -    const migrationDirectory = 'Migrations'; | 
| 13 |  | -    return path.join(process.cwd(), migrationDirectory); | 
| 14 |  | -}; | 
| 15 |  | - | 
| 16 |  | -export const getMigrationFilepath = (filename: string): string => { | 
| 17 |  | -    return path.join(getMigrationDirectory(), filename); | 
| 18 |  | -}; | 
| 19 |  | - | 
| 20 |  | -const ensureMigrationsDirectoryExists = () => { | 
| 21 |  | -    const directory = getMigrationDirectory(); | 
| 22 |  | -    if (!fs.existsSync(directory)) { | 
| 23 |  | -        fs.mkdirSync(directory); | 
| 24 |  | -    } | 
| 25 |  | -}; | 
| 26 |  | - | 
| 27 |  | -export const saveMigrationFile = (migrationName: string, migrationData: string, templateType: TemplateType): string => { | 
| 28 |  | -    ensureMigrationsDirectoryExists(); | 
| 29 |  | -    const fileExtension = templateType === TemplateType.TypeScript ? '.ts' : '.js'; | 
| 30 |  | -    const migrationFilepath = getMigrationFilepath(migrationName + fileExtension); | 
| 31 |  | - | 
| 32 |  | -    try { | 
| 33 |  | -        fs.writeFileSync(migrationFilepath, migrationData); | 
| 34 |  | -        console.log(chalk.green(`Migration template ${migrationName} (${migrationFilepath}) was generated.`)); | 
| 35 |  | -    } catch (e) { | 
| 36 |  | -        console.error("Couldn't save the migration.", e instanceof Error ? e.message : 'Unknown error occurred.'); | 
| 37 |  | -    } | 
| 38 |  | - | 
| 39 |  | -    return migrationFilepath; | 
| 40 |  | -}; | 
| 41 |  | - | 
| 42 |  | -export const runMigration = async (migration: IMigration, client: ManagementClient, projectId: string): Promise<number> => { | 
| 43 |  | -    console.log(`Running the ${migration.name} migration.`); | 
| 44 |  | - | 
| 45 |  | -    let isSuccess = true; | 
| 46 |  | - | 
| 47 |  | -    try { | 
| 48 |  | -        await migration.module.run(client).then(() => { | 
| 49 |  | -            markAsCompleted(projectId, migration.name, migration.module.order); | 
| 50 |  | -        }); | 
| 51 |  | -    } catch (e) { | 
| 52 |  | -        console.error(chalk.redBright('An error occurred while running migration:'), chalk.yellowBright(migration.name), chalk.redBright('see the output from running the script.')); | 
| 53 |  | - | 
| 54 |  | -        let error = e; | 
| 55 |  | -        if (e instanceof SharedModels.ContentManagementBaseKontentError && e.originalError !== undefined) { | 
| 56 |  | -            console.group('Error details'); | 
| 57 |  | -            console.error(chalk.redBright('Message:'), e.message); | 
| 58 |  | -            console.error(chalk.redBright('Code:'), e.errorCode); | 
| 59 |  | -            console.error(chalk.redBright('Validation Errors:'), e.validationErrors); | 
| 60 |  | -            console.groupEnd(); | 
| 61 |  | -            console.log(); | 
| 62 |  | -            console.group('Response details:'); | 
| 63 |  | -            console.error(chalk.redBright('Message:'), e.originalError.message); | 
| 64 |  | -            console.groupEnd(); | 
| 65 |  | - | 
| 66 |  | -            error = e.originalError; | 
| 67 |  | -        } else { | 
| 68 |  | -            console.group('Error details'); | 
| 69 |  | -            console.error(chalk.redBright('Message:'), e instanceof Error ? e.message : 'Unknown error'); | 
| 70 |  | -            console.groupEnd(); | 
| 71 |  | -        } | 
| 72 |  | - | 
| 73 |  | -        const requestConfig = error.config; | 
| 74 |  | -        if (requestConfig) { | 
| 75 |  | -            const bodyData = JSON.parse(requestConfig.data || {}); | 
| 76 |  | -            console.log(); | 
| 77 |  | -            console.group('Request details:'); | 
| 78 |  | -            console.error(chalk.yellow('URL:'), requestConfig.url); | 
| 79 |  | -            console.error(chalk.yellow('Method:'), requestConfig.method); | 
| 80 |  | -            console.error(chalk.yellow('Body:'), bodyData); | 
| 81 |  | -            console.groupEnd(); | 
| 82 |  | -        } | 
| 83 |  | - | 
| 84 |  | -        isSuccess = false; | 
| 85 |  | -    } | 
| 86 |  | - | 
| 87 |  | -    if (!isSuccess) { | 
| 88 |  | -        return 1; | 
| 89 |  | -    } | 
| 90 |  | - | 
| 91 |  | -    console.log(chalk.green(`The \"${migration.name}\" migration on a project with ID \"${projectId}\" executed successfully.`)); | 
| 92 |  | -    return 0; | 
| 93 |  | -}; | 
| 94 |  | - | 
| 95 |  | -export const generateTypedMigration = (): string => { | 
| 96 |  | -    return `import {MigrationModule} from "@kontent-ai/cli"; | 
| 97 |  | -
 | 
| 98 |  | -const migration: MigrationModule = { | 
| 99 |  | -    order: 1, | 
| 100 |  | -    run: async (apiClient) => { | 
| 101 |  | -    }, | 
| 102 |  | -}; | 
| 103 |  | -
 | 
| 104 |  | -export default migration; | 
| 105 |  | -`; | 
| 106 |  | -}; | 
| 107 |  | - | 
| 108 |  | -export const generatePlainMigration = (): string => { | 
| 109 |  | -    return ` | 
| 110 |  | -const migration = { | 
| 111 |  | -    order: 1, | 
| 112 |  | -    run: async (apiClient) => { | 
| 113 |  | -    }, | 
| 114 |  | -}; | 
| 115 |  | -
 | 
| 116 |  | -module.exports = migration; | 
| 117 |  | -`; | 
| 118 |  | -}; | 
| 119 |  | - | 
| 120 |  | -export const createMigration = (migrationName: string, templateType: TemplateType): string => { | 
| 121 |  | -    ensureMigrationsDirectoryExists(); | 
| 122 |  | -    const generatedMigration = templateType === TemplateType.TypeScript ? generateTypedMigration() : generatePlainMigration(); | 
| 123 |  | - | 
| 124 |  | -    return saveMigrationFile(migrationName, generatedMigration, templateType); | 
| 125 |  | -}; | 
| 126 |  | - | 
| 127 |  | -export const getDuplicates = <T extends any>(array: T[], key: (obj: T) => number): T[] => { | 
| 128 |  | -    const allEntries = new Map<number, T[]>(); | 
| 129 |  | -    let duplicates: T[] = []; | 
| 130 |  | - | 
| 131 |  | -    for (const item of array) { | 
| 132 |  | -        const itemKey = key(item); | 
| 133 |  | -        const prevItem = allEntries.get(itemKey) || []; | 
| 134 |  | -        allEntries.set(itemKey, prevItem.concat(item)); | 
| 135 |  | -    } | 
| 136 |  | - | 
| 137 |  | -    for (const [, value] of allEntries.entries()) { | 
| 138 |  | -        if (value.length > 1) { | 
| 139 |  | -            duplicates = duplicates.concat(value); | 
| 140 |  | -        } | 
| 141 |  | -    } | 
| 142 |  | - | 
| 143 |  | -    return duplicates; | 
| 144 |  | -}; | 
| 145 |  | - | 
| 146 |  | -export const getMigrationsWithInvalidOrder = <T extends { module: any }>(array: T[]): T[] => { | 
| 147 |  | -    const migrationsWithInvalidOrder: T[] = []; | 
| 148 |  | - | 
| 149 |  | -    for (const migration of array) { | 
| 150 |  | -        if (!Number.isInteger(migration.module.order) || Number(migration.module.order) < 0) { | 
| 151 |  | -            migrationsWithInvalidOrder.push(migration); | 
| 152 |  | -        } | 
| 153 |  | -    } | 
| 154 |  | - | 
| 155 |  | -    return migrationsWithInvalidOrder; | 
| 156 |  | -}; | 
| 157 |  | - | 
| 158 |  | -export const loadModule = async (migrationFile: string): Promise<MigrationModule> => { | 
| 159 |  | -    const migrationPath = getMigrationFilepath(migrationFile); | 
| 160 |  | - | 
| 161 |  | -    return await import(migrationPath) | 
| 162 |  | -        .then(async (module) => { | 
| 163 |  | -            const importedModule: MigrationModule = module.default; | 
| 164 |  | -            return importedModule; | 
| 165 |  | -        }) | 
| 166 |  | -        .catch((error) => { | 
| 167 |  | -            throw new Error(chalk.red(`Couldn't import the migration script from \"${migrationPath}"\ due to an error: \"${error.message}\".`)); | 
| 168 |  | -        }); | 
| 169 |  | -}; | 
| 170 |  | - | 
| 171 |  | -export const loadMigrationFiles = async (): Promise<IMigration[]> => { | 
| 172 |  | -    const migrations: IMigration[] = []; | 
| 173 |  | - | 
| 174 |  | -    const files = listFiles('.js'); | 
| 175 |  | - | 
| 176 |  | -    for (const file of files) { | 
| 177 |  | -        migrations.push({ name: file.name, module: await loadModule(file.name) }); | 
| 178 |  | -    } | 
| 179 |  | - | 
| 180 |  | -    return migrations.filter(String); | 
| 181 |  | -}; | 
| 182 |  | - | 
| 183 |  | -export const getSuccessfullyExecutedMigrations = (migrations: IMigration[], projectId: string): IMigration[] => { | 
| 184 |  | -    const alreadyExecutedMigrations: IMigration[] = []; | 
| 185 |  | - | 
| 186 |  | -    // filter by execution status | 
| 187 |  | -    for (const migration of migrations) { | 
| 188 |  | -        if (wasSuccessfullyExecuted(migration.name, projectId)) { | 
| 189 |  | -            alreadyExecutedMigrations.push(migration); | 
| 190 |  | -        } | 
| 191 |  | -    } | 
| 192 |  | - | 
| 193 |  | -    return alreadyExecutedMigrations.filter(String); | 
| 194 |  | -}; | 
|  | 1 | +import { ManagementClient, SharedModels } from '@kontent-ai/management-sdk'; | 
|  | 2 | +import chalk from 'chalk'; | 
|  | 3 | +import path from 'path'; | 
|  | 4 | +import fs from 'fs'; | 
|  | 5 | +import { listFiles } from './fileUtils'; | 
|  | 6 | +import { TemplateType } from '../models/templateType'; | 
|  | 7 | +import { MigrationModule } from '../types'; | 
|  | 8 | +import { IMigration } from '../models/migration'; | 
|  | 9 | +import { markAsCompleted, wasSuccessfullyExecuted } from './statusManager'; | 
|  | 10 | + | 
|  | 11 | +export const getMigrationDirectory = (): string => { | 
|  | 12 | +    const migrationDirectory = 'Migrations'; | 
|  | 13 | +    return path.join(process.cwd(), migrationDirectory); | 
|  | 14 | +}; | 
|  | 15 | + | 
|  | 16 | +export const getMigrationFilepath = (filename: string): string => { | 
|  | 17 | +    return path.join(getMigrationDirectory(), filename); | 
|  | 18 | +}; | 
|  | 19 | + | 
|  | 20 | +const ensureMigrationsDirectoryExists = () => { | 
|  | 21 | +    const directory = getMigrationDirectory(); | 
|  | 22 | +    if (!fs.existsSync(directory)) { | 
|  | 23 | +        fs.mkdirSync(directory); | 
|  | 24 | +    } | 
|  | 25 | +}; | 
|  | 26 | + | 
|  | 27 | +export const saveMigrationFile = (migrationName: string, migrationData: string, templateType: TemplateType): string => { | 
|  | 28 | +    ensureMigrationsDirectoryExists(); | 
|  | 29 | +    const fileExtension = templateType === TemplateType.TypeScript ? '.ts' : '.js'; | 
|  | 30 | +    const migrationFilepath = getMigrationFilepath(migrationName + fileExtension); | 
|  | 31 | + | 
|  | 32 | +    try { | 
|  | 33 | +        fs.writeFileSync(migrationFilepath, migrationData); | 
|  | 34 | +        console.log(chalk.green(`Migration template ${migrationName} (${migrationFilepath}) was generated.`)); | 
|  | 35 | +    } catch (e) { | 
|  | 36 | +        console.error("Couldn't save the migration.", e instanceof Error ? e.message : 'Unknown error occurred.'); | 
|  | 37 | +    } | 
|  | 38 | + | 
|  | 39 | +    return migrationFilepath; | 
|  | 40 | +}; | 
|  | 41 | + | 
|  | 42 | +export const runMigration = async (migration: IMigration, client: ManagementClient, projectId: string): Promise<number> => { | 
|  | 43 | +    console.log(`Running the ${migration.name} migration.`); | 
|  | 44 | + | 
|  | 45 | +    let isSuccess = true; | 
|  | 46 | + | 
|  | 47 | +    try { | 
|  | 48 | +        await migration.module.run(client).then(() => { | 
|  | 49 | +            markAsCompleted(projectId, migration.name, migration.module.order); | 
|  | 50 | +        }); | 
|  | 51 | +    } catch (e) { | 
|  | 52 | +        console.error(chalk.redBright('An error occurred while running migration:'), chalk.yellowBright(migration.name), chalk.redBright('see the output from running the script.')); | 
|  | 53 | + | 
|  | 54 | +        let error = e as any; | 
|  | 55 | +        if (e instanceof SharedModels.ContentManagementBaseKontentError && e.originalError !== undefined) { | 
|  | 56 | +            console.group('Error details'); | 
|  | 57 | +            console.error(chalk.redBright('Message:'), e.message); | 
|  | 58 | +            console.error(chalk.redBright('Code:'), e.errorCode); | 
|  | 59 | +            console.error(chalk.redBright('Validation Errors:'), e.validationErrors); | 
|  | 60 | +            console.groupEnd(); | 
|  | 61 | +            console.log(); | 
|  | 62 | +            console.group('Response details:'); | 
|  | 63 | +            console.error(chalk.redBright('Message:'), e.originalError.message); | 
|  | 64 | +            console.groupEnd(); | 
|  | 65 | + | 
|  | 66 | +            error = e.originalError; | 
|  | 67 | +        } else { | 
|  | 68 | +            console.group('Error details'); | 
|  | 69 | +            console.error(chalk.redBright('Message:'), e instanceof Error ? e.message : 'Unknown error'); | 
|  | 70 | +            console.groupEnd(); | 
|  | 71 | +        } | 
|  | 72 | + | 
|  | 73 | +        const requestConfig = error.config; | 
|  | 74 | +        if (requestConfig) { | 
|  | 75 | +            const bodyData = JSON.parse(requestConfig.data || {}); | 
|  | 76 | +            console.log(); | 
|  | 77 | +            console.group('Request details:'); | 
|  | 78 | +            console.error(chalk.yellow('URL:'), requestConfig.url); | 
|  | 79 | +            console.error(chalk.yellow('Method:'), requestConfig.method); | 
|  | 80 | +            console.error(chalk.yellow('Body:'), bodyData); | 
|  | 81 | +            console.groupEnd(); | 
|  | 82 | +        } | 
|  | 83 | + | 
|  | 84 | +        isSuccess = false; | 
|  | 85 | +    } | 
|  | 86 | + | 
|  | 87 | +    if (!isSuccess) { | 
|  | 88 | +        return 1; | 
|  | 89 | +    } | 
|  | 90 | + | 
|  | 91 | +    console.log(chalk.green(`The \"${migration.name}\" migration on a project with ID \"${projectId}\" executed successfully.`)); | 
|  | 92 | +    return 0; | 
|  | 93 | +}; | 
|  | 94 | + | 
|  | 95 | +export const generateTypedMigration = (): string => { | 
|  | 96 | +    return `import {MigrationModule} from "@kontent-ai/cli"; | 
|  | 97 | +
 | 
|  | 98 | +const migration: MigrationModule = { | 
|  | 99 | +    order: 1, | 
|  | 100 | +    run: async (apiClient) => { | 
|  | 101 | +    }, | 
|  | 102 | +}; | 
|  | 103 | +
 | 
|  | 104 | +export default migration; | 
|  | 105 | +`; | 
|  | 106 | +}; | 
|  | 107 | + | 
|  | 108 | +export const generatePlainMigration = (): string => { | 
|  | 109 | +    return ` | 
|  | 110 | +const migration = { | 
|  | 111 | +    order: 1, | 
|  | 112 | +    run: async (apiClient) => { | 
|  | 113 | +    }, | 
|  | 114 | +}; | 
|  | 115 | +
 | 
|  | 116 | +module.exports = migration; | 
|  | 117 | +`; | 
|  | 118 | +}; | 
|  | 119 | + | 
|  | 120 | +export const createMigration = (migrationName: string, templateType: TemplateType): string => { | 
|  | 121 | +    ensureMigrationsDirectoryExists(); | 
|  | 122 | +    const generatedMigration = templateType === TemplateType.TypeScript ? generateTypedMigration() : generatePlainMigration(); | 
|  | 123 | + | 
|  | 124 | +    return saveMigrationFile(migrationName, generatedMigration, templateType); | 
|  | 125 | +}; | 
|  | 126 | + | 
|  | 127 | +export const getDuplicates = <T extends any>(array: T[], key: (obj: T) => number): T[] => { | 
|  | 128 | +    const allEntries = new Map<number, T[]>(); | 
|  | 129 | +    let duplicates: T[] = []; | 
|  | 130 | + | 
|  | 131 | +    for (const item of array) { | 
|  | 132 | +        const itemKey = key(item); | 
|  | 133 | +        const prevItem = allEntries.get(itemKey) || []; | 
|  | 134 | +        allEntries.set(itemKey, prevItem.concat(item)); | 
|  | 135 | +    } | 
|  | 136 | + | 
|  | 137 | +    for (const [, value] of allEntries.entries()) { | 
|  | 138 | +        if (value.length > 1) { | 
|  | 139 | +            duplicates = duplicates.concat(value); | 
|  | 140 | +        } | 
|  | 141 | +    } | 
|  | 142 | + | 
|  | 143 | +    return duplicates; | 
|  | 144 | +}; | 
|  | 145 | + | 
|  | 146 | +export const getMigrationsWithInvalidOrder = <T extends { module: any }>(array: T[]): T[] => { | 
|  | 147 | +    const migrationsWithInvalidOrder: T[] = []; | 
|  | 148 | + | 
|  | 149 | +    for (const migration of array) { | 
|  | 150 | +        if (!Number.isInteger(migration.module.order) || Number(migration.module.order) < 0) { | 
|  | 151 | +            migrationsWithInvalidOrder.push(migration); | 
|  | 152 | +        } | 
|  | 153 | +    } | 
|  | 154 | + | 
|  | 155 | +    return migrationsWithInvalidOrder; | 
|  | 156 | +}; | 
|  | 157 | + | 
|  | 158 | +export const loadModule = async (migrationFile: string): Promise<MigrationModule> => { | 
|  | 159 | +    const migrationPath = getMigrationFilepath(migrationFile); | 
|  | 160 | + | 
|  | 161 | +    return await import(migrationPath) | 
|  | 162 | +        .then(async (module) => { | 
|  | 163 | +            const importedModule: MigrationModule = module.default; | 
|  | 164 | +            return importedModule; | 
|  | 165 | +        }) | 
|  | 166 | +        .catch((error) => { | 
|  | 167 | +            throw new Error(chalk.red(`Couldn't import the migration script from \"${migrationPath}"\ due to an error: \"${error.message}\".`)); | 
|  | 168 | +        }); | 
|  | 169 | +}; | 
|  | 170 | + | 
|  | 171 | +export const loadMigrationFiles = async (): Promise<IMigration[]> => { | 
|  | 172 | +    const migrations: IMigration[] = []; | 
|  | 173 | + | 
|  | 174 | +    const files = listFiles('.js'); | 
|  | 175 | + | 
|  | 176 | +    for (const file of files) { | 
|  | 177 | +        migrations.push({ name: file.name, module: await loadModule(file.name) }); | 
|  | 178 | +    } | 
|  | 179 | + | 
|  | 180 | +    return migrations.filter(String); | 
|  | 181 | +}; | 
|  | 182 | + | 
|  | 183 | +export const getSuccessfullyExecutedMigrations = (migrations: IMigration[], projectId: string): IMigration[] => { | 
|  | 184 | +    const alreadyExecutedMigrations: IMigration[] = []; | 
|  | 185 | + | 
|  | 186 | +    // filter by execution status | 
|  | 187 | +    for (const migration of migrations) { | 
|  | 188 | +        if (wasSuccessfullyExecuted(migration.name, projectId)) { | 
|  | 189 | +            alreadyExecutedMigrations.push(migration); | 
|  | 190 | +        } | 
|  | 191 | +    } | 
|  | 192 | + | 
|  | 193 | +    return alreadyExecutedMigrations.filter(String); | 
|  | 194 | +}; | 
0 commit comments