diff --git a/.gitignore b/.gitignore index bc013215..0003c882 100644 --- a/.gitignore +++ b/.gitignore @@ -187,7 +187,7 @@ PublishScripts/ # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* +# **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed @@ -349,10 +349,10 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + upload-api/node_modules upload-api/build package-lock.json ui/.env upload-api/sitecoreMigrationData upload-api/extracted_files -locale-cli diff --git a/api/.gitignore b/api/.gitignore index 8fd3924d..e3a5464e 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -361,4 +361,5 @@ package-lock.json !example.env database/ -/sitecoreMigrationData \ No newline at end of file +/sitecoreMigrationData +/migration-data \ No newline at end of file diff --git a/api/package.json b/api/package.json index 35570827..c7616c29 100644 --- a/api/package.json +++ b/api/package.json @@ -35,6 +35,7 @@ "express": "^4.18.2", "express-validator": "^7.0.1", "express-winston": "^4.2.0", + "fs-extra": "^11.2.0", "fs-readdir-recursive": "^1.1.0", "helmet": "^7.1.0", "html-to-json-parser": "^2.0.1", @@ -42,6 +43,7 @@ "jsdom": "^24.1.0", "jsonwebtoken": "^9.0.2", "lowdb": "^7.0.1", + "mkdirp": "^3.0.1", "shelljs": "^0.8.5", "socket.io": "^4.7.5", "uuid": "^9.0.1", @@ -50,10 +52,12 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/fs-extra": "^11.0.4", "@types/fs-readdir-recursive": "^1.1.3", "@types/jsdom": "^21.1.7", "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.17.0", + "@types/mkdirp": "^2.0.0", "@types/node": "^20.10.4", "@types/shelljs": "^0.8.15", "@types/uuid": "^9.0.8", diff --git a/api/src/config/index.ts b/api/src/config/index.ts index f2d75b84..5290be08 100644 --- a/api/src/config/index.ts +++ b/api/src/config/index.ts @@ -10,6 +10,7 @@ dotenv.config({ path: path.resolve(process.cwd(), `${process.env.NODE_ENV}.env`), }); + /** * Configuration type for the application. */ @@ -34,6 +35,7 @@ export type ConfigType = { AZURE_EU?: string; GCP_NA?: string; }; + LOG_FILE_PATH: string; }; /** diff --git a/api/src/controllers/migration.controller.ts b/api/src/controllers/migration.controller.ts index ac2113ad..3f8ac5c8 100644 --- a/api/src/controllers/migration.controller.ts +++ b/api/src/controllers/migration.controller.ts @@ -21,7 +21,7 @@ const createTestStack = async (req: Request, res: Response): Promise => { * @returns {Promise} - A Promise that resolves when the stack is deleted. */ const fieldMapping = async (req: Request, res: Response): Promise => { - const resp = await migrationService.fieldMapping(req); + const resp = migrationService.fieldMapping(req); res.status(200).json(resp); }; diff --git a/api/src/controllers/org.controller.ts b/api/src/controllers/org.controller.ts index 52577058..b97c769d 100644 --- a/api/src/controllers/org.controller.ts +++ b/api/src/controllers/org.controller.ts @@ -62,11 +62,24 @@ const getStackLocale = async (req: Request, res: Response) => { res.status(resp.status).json(resp.data); }; +/* Retrieves the org details. +* +* @param req - The request object. +* @param res - The response object. +* @returns A Promise that resolves to the org details response. +*/ +const getOrgDetails = async (req: Request, res: Response) => { + const resp = await orgService.getOrgDetails(req); + res.status(resp.status).json(resp.data); +}; + + export const orgController = { getAllStacks, createStack, getLocales, getStackStatus, - getStackLocale + getStackLocale, + getOrgDetails, }; diff --git a/api/src/controllers/projects.contentMapper.controller.ts b/api/src/controllers/projects.contentMapper.controller.ts index fd1000ae..03881ff8 100644 --- a/api/src/controllers/projects.contentMapper.controller.ts +++ b/api/src/controllers/projects.contentMapper.controller.ts @@ -125,6 +125,18 @@ const getSingleContentTypes = async ( res.status(201).json(resp); }; +/** + * Retrieves single global field. + * + * @param req - The request object. + * @param res - The response object. + * @returns A Promise that resolves to void. + */ +const getSingleGlobalField = async(req: Request, res: Response): Promise => { + const resp = await contentMapperService.getSingleGlobalField(req); + res.status(201).json(resp); +} + /** * update content mapping details a project. * @@ -148,5 +160,6 @@ export const contentMapperController = { getSingleContentTypes, removeContentMapper, updateContentMapper, - getExistingGlobalFields + getExistingGlobalFields, + getSingleGlobalField }; diff --git a/api/src/models/project-lowdb.ts b/api/src/models/project-lowdb.ts index 4de76bac..693899ba 100644 --- a/api/src/models/project-lowdb.ts +++ b/api/src/models/project-lowdb.ts @@ -1,3 +1,4 @@ +import path from 'path'; import { JSONFile } from "lowdb/node"; import LowWithLodash from "../utils/lowdb-lodash.utils.js"; @@ -27,11 +28,11 @@ interface LegacyCMS { is_localPath: boolean; } -interface StackDetails{ +interface StackDetails { uid: string; label: string; master_locale: string; - created_at: string; + created_at: string; isNewStack: boolean; } @@ -84,7 +85,7 @@ const defaultData: ProjectDocument = { projects: [] }; * Represents the database instance for the project. */ const db = new LowWithLodash( - new JSONFile("database/project.json"), + new JSONFile(path.join(process.cwd(), "database/project.json")), defaultData ); diff --git a/api/src/routes/contentMapper.routes.ts b/api/src/routes/contentMapper.routes.ts index 6b8fed79..30853461 100644 --- a/api/src/routes/contentMapper.routes.ts +++ b/api/src/routes/contentMapper.routes.ts @@ -45,7 +45,7 @@ router.get( * @route GET /:projectId */ router.get( - "/globalFields/:projectId", + "/:projectId/globalFields", asyncRouter(contentMapperController.getExistingGlobalFields) ); @@ -85,7 +85,18 @@ router.get( asyncRouter(contentMapperController.removeContentMapper) ); -//update content mapper details +/** + * Update content mapper + * @route GET /:orgId/:projectId + */ router.patch("/:orgId/:projectId/mapper_keys", asyncRouter(contentMapperController.updateContentMapper)); +/** + * Get Single Global Field data + * @route GET /:projectId/:globalFieldUid + */ +router.get("/:projectId/globalFields/:globalFieldUid", + asyncRouter(contentMapperController.getSingleGlobalField) +); + export default router; diff --git a/api/src/routes/migration.routes.ts b/api/src/routes/migration.routes.ts index feaeea2c..ac0ce8f6 100644 --- a/api/src/routes/migration.routes.ts +++ b/api/src/routes/migration.routes.ts @@ -33,4 +33,17 @@ router.post( asyncRouter(migrationController.deleteTestStack) ); +/** + * Route for creating a test stack. + * @route POST /test-stack/:projectId + * @group Migration + * @param {string} projectId - The ID of the project. + * @returns {Promise} - A promise that resolves when the test stack is deleted. + */ +router.post( + "/create-test-stack/:orgId/:projectId", + asyncRouter(migrationController.createTestStack) +); + + export default router; diff --git a/api/src/routes/org.routes.ts b/api/src/routes/org.routes.ts index 8450be11..2b0eb222 100644 --- a/api/src/routes/org.routes.ts +++ b/api/src/routes/org.routes.ts @@ -51,4 +51,13 @@ router.post( router.get("/get_stack_locales", asyncRouter(orgController.getStackLocale)); +/** + * GET all contentstack org details route. + * @param req - Express request object. + * @param res - Express response object. + */ +router.get("/get_org_details", asyncRouter(orgController.getOrgDetails)); + + + export default router; diff --git a/api/src/server.ts b/api/src/server.ts index fbaf4cb6..5ae0274b 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -18,24 +18,55 @@ import contentMapperRoutes from "./routes/contentMapper.routes.js"; import migrationRoutes from "./routes/migration.routes.js"; import chokidar from "chokidar"; import { Server } from "socket.io"; -import http from "http"; import fs from "fs"; +// Initialize file watcher for the log file +const watcher = chokidar.watch(config.LOG_FILE_PATH, { + usePolling: true, // Enables polling to detect changes in all environments + interval: 100, // Poll every 100ms (you can adjust this if needed) + awaitWriteFinish: { // Wait for file to finish being written before triggering + stabilityThreshold: 500, // Time to wait before considering the file stable + pollInterval: 100, // Interval at which to poll for file stability + }, + persistent: true, // Keeps watching the file even after initial change +}); // Initialize with initial log path + +let io: Server; // Socket.IO server instance + +// Dynamically change the log file path and update the watcher +export async function setLogFilePath(path: string) { + console.info(`Setting new log file path: ${path}`); + + // Stop watching the old log file + watcher.unwatch(config.LOG_FILE_PATH); + + // Update the config and start watching the new log file + config.LOG_FILE_PATH = path; + watcher.add(path); +} + try { const app = express(); + + // Set security-related HTTP headers app.use( helmet({ - crossOriginOpenerPolicy: false, + crossOriginOpenerPolicy: false, // Disable to allow cross-origin resource sharing }) ); + // Enable CORS for all origins app.use(cors({ origin: "*" })); + + // Parsing request bodies app.use(express.urlencoded({ extended: false, limit: "10mb" })); app.use(express.json({ limit: "10mb" })); + + // Custom middleware for logging and request headers app.use(loggerMiddleware); app.use(requestHeadersMiddleware); - // Routes + // Define routes app.use("/v2/auth", authRoutes); app.use("/v2/user", authenticateUser, userRoutes); app.use("/v2/org/:orgId", authenticateUser, orgRoutes); @@ -43,64 +74,53 @@ try { app.use("/v2/mapper", authenticateUser, contentMapperRoutes); app.use("/v2/migration", authenticateUser, migrationRoutes); - //For unmatched route patterns + // Handle unmatched routes app.use(unmatchedRoutesMiddleware); - // Error Middleware + // Handle errors app.use(errorMiddleware); - // starting the server & DB connection. + // Start the server and establish DB connection (async () => { - await connectToDatabase(); + await connectToDatabase(); // Establish DB connection + const server = app.listen(config.PORT, () => logger.info(`Server listening at port ${config.PORT}`) ); - // Chokidar - Watch for log file changes - const logFilePath = config.LOG_FILE_PATH; - const watcher = chokidar.watch(logFilePath); - // Socket.IO - Send logs to client - /** - * The Socket.IO server instance. - * - * @remarks - * This server instance is responsible for handling real-time communication between the client and the server using the Socket.IO library. - * - * @type {Server} - */ - const io = new Server( - server, - (http, - { - cors: { - origin: "*", // This allows all origins. For production, specify exact origins for security. - methods: ["GET", "POST"], // Specify which HTTP methods are allowed. - allowedHeaders: ["my-custom-header"], // Specify which headers are allowed. - credentials: true, // If your client needs to send cookies or credentials with the requests. - }, - }) - ); + // Initialize Socket.IO for real-time log updates + io = new Server(server, { + cors: { + origin: "*", // Allow all origins; adjust for production + methods: ["GET", "POST"], + allowedHeaders: ["my-custom-header"], + credentials: true, + }, + }); + + // Emit initial log file content to connected clients + + // File watcher for log file changes watcher.on("change", (path) => { - // Read the updated log file + console.info(`File changed: ${path}`); + // Read the updated file content fs.readFile(path, "utf8", (err, data) => { if (err) { logger.error(`Error reading log file: ${err}`); return; } - // Get just the updated data - // const updatedData = data.slice(data.lastIndexOf("\n") + 1); - console.info("updates", data); - // Emit the updated data to all connected clients try { - const parsedData = data; - io.emit("logUpdate", parsedData); + // Emit the updated log content to connected clients + io.emit("logUpdate", data); } catch (error) { - logger.error(`Error parsing data: ${error}`); + logger.error(`Error emitting log data: ${error}`); } }); }); + })(); } catch (e) { logger.error("Error while starting the server!"); logger.error(e); } + diff --git a/api/src/services/auth.service.ts b/api/src/services/auth.service.ts index fe5eff2f..02399807 100644 --- a/api/src/services/auth.service.ts +++ b/api/src/services/auth.service.ts @@ -23,7 +23,6 @@ import logger from "../utils/logger.js"; */ const login = async (req: Request): Promise => { const srcFun = "Login"; - const cliUtilities: any = await import('@contentstack/cli-utilities'); /* handles user authentication by making a request to an API, performing various checks and validations, @@ -110,10 +109,6 @@ const login = async (req: Request): Promise => { data.users[userIndex].authtoken = res?.data.user?.authtoken; data.users[userIndex].updated_at = new Date().toISOString(); } - - cliUtilities?.configHandler?.set('region', userData?.region); - cliUtilities?.configHandler?.set('authtoken', res?.data.user?.authtoken); - }); // JWT token generation diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index 2146509c..e13275e6 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -863,9 +863,56 @@ const getSingleContentTypes = async (req: Request) => { return { title: res?.data?.content_type?.title, uid: res?.data?.content_type?.uid, - schema: res?.data?.content_type?.schema, + schema: res?.data?.content_type?.schema }; }; + +/** + * Retrieves a single global field from the specified project. + * @param req - The request object containing the project ID, content type UID, and token payload. + * @returns An object containing the title, UID, and schema of the content type, or an error object if an error occurs. + */ +const getSingleGlobalField = async (req: Request) => { + const projectId = req?.params?.projectId; + const globalFieldUID = req?.params?.globalFieldUid; + const { token_payload } = req.body; + + const authtoken = await getAuthtoken( + token_payload?.region, + token_payload?.user_id + ); + await ProjectModelLowdb.read(); + const project = ProjectModelLowdb.chain + .get("projects") + .find({ id: projectId }) + .value(); + const stackId = project?.destination_stack_id; + + const [err, res] = await safePromise( + https({ + method: "GET", + url: `${config.CS_API[ + token_payload?.region as keyof typeof config.CS_API + ]!}/global_fields/${globalFieldUID}`, + headers: { + api_key: stackId, + authtoken: authtoken, + }, + }) + ); + + if (err) + return { + data: err.response.data, + status: err.response.status, + }; + + return { + title: res?.data?.global_field?.title, + uid: res?.data?.global_field?.uid, + schema: res?.data?.global_field?.schema + }; +} /** * Removes the content mapping for a project. * @param req - The request object containing the project ID. @@ -1037,5 +1084,6 @@ export const contentMapperService = { removeMapping, getSingleContentTypes, updateContentMapper, - getExistingGlobalFields + getExistingGlobalFields, + getSingleGlobalField }; diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 2f2639eb..133f27ac 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -1,21 +1,28 @@ import { Request } from "express"; -import cliUtilities from '@contentstack/cli-utilities'; +import fs from 'fs'; +// import cliUtilities from '@contentstack/cli-utilities'; import { config } from "../config/index.js"; import { safePromise, getLogMessage } from "../utils/index.js"; import https from "../utils/https.utils.js"; import { LoginServiceType } from "../models/types.js"; import getAuthtoken from "../utils/auth.utils.js"; import logger from "../utils/logger.js"; -import { HTTP_TEXTS, HTTP_CODES, CS_REGIONS } from "../constants/index.js"; +import { HTTP_TEXTS, HTTP_CODES, CS_REGIONS, LOCALE_MAPPER } from "../constants/index.js"; import { ExceptionFunction } from "../utils/custom-errors.utils.js"; import { fieldAttacher } from "../utils/field-attacher.utils.js"; import ProjectModelLowdb from "../models/project-lowdb.js"; -// import shell from 'shelljs' -// import path from "path"; +import shell from 'shelljs' +import path from "path"; import AuthenticationModel from "../models/authentication.js"; import { siteCoreService } from "./sitecore.service.js"; +import { copyDirectory } from '../utils/index.js' +import { v4 } from "uuid"; +import { setLogFilePath } from "../server.js"; +import { mkdirp } from 'mkdirp'; +import { testFolderCreator } from "../utils/test-folder-creator.utils.js"; + + -// const importCmd: any = await import('@contentstack/cli-cm-import'); /** * Creates a test stack. @@ -28,7 +35,11 @@ const createTestStack = async (req: Request): Promise => { const srcFun = "createTestStack"; const orgId = req?.params?.orgId; const projectId = req?.params?.projectId; - const { token_payload, name, description, master_locale } = req.body; + const { token_payload } = req.body; + const description = 'This is a system-generated test stack.' + const name = 'Test'; + const master_locale = Object?.keys?.(LOCALE_MAPPER?.masterLocale)?.[0]; + try { const authtoken = await getAuthtoken( @@ -37,8 +48,9 @@ const createTestStack = async (req: Request): Promise => { ); await ProjectModelLowdb.read(); - const projectData = ProjectModelLowdb.chain.get("projects").value(); - const testStackCount = projectData[0]?.test_stacks?.length + 1; + const projectData: any = ProjectModelLowdb.chain.get("projects").find({ id: projectId }).value(); + console.info("🚀 ~ createTestStack ~ projectData:", projectData) + const testStackCount = projectData?.test_stacks?.length + 1; const newName = name + "-" + testStackCount; const [err, res] = await safePromise( @@ -81,11 +93,10 @@ const createTestStack = async (req: Request): Promise => { .get("projects") .findIndex({ id: projectId }) .value(); - console.info(index); if (index > -1) { ProjectModelLowdb.update((data: any) => { - data.projects[index].current_test_stack_id = res.data.stack.uid; - data.projects[index].test_stacks.push(res.data.stack.uid); + data.projects[index].current_test_stack_id = res?.data?.stack?.api_key; + data.projects[index].test_stacks.push({ stackUid: res?.data?.stack?.api_key, isMigrated: false }); }); } return { @@ -193,66 +204,78 @@ const deleteTestStack = async (req: Request): Promise => { } }; -const cliLogger = (child: any) => { - if (child.code !== 0) { - console.info(`Error: Failed to install @contentstack/cli. Exit code: ${child.code}`); - console.info(`stderr: ${child.stderr}`); + +function createDirectoryAndFile(filePath: string) { + // Get the directory from the file path + const dirPath = path.dirname(filePath); + // Create the directory if it doesn't exist + mkdirp.sync(dirPath); + // Check if the file exists; if not, create it + if (!fs.existsSync(filePath)) { + fs.writeFileSync(filePath, '', { mode: 0o666 }); // Create file with read/write for everyone + console.info(`File created at: ${filePath}`); } else { - console.info('Installation successful', child?.stdout); + console.info(`File already exists at: ${filePath}`); } -}; +} -const runCli = async (rg: string, user_id: string) => { + +const runCli = async (rg: string, user_id: string, stack_uid: any, projectId: string) => { try { const regionPresent = CS_REGIONS?.find((item: string) => item === rg) ?? 'NA'; - // const email = 'umesh.more+10@contentstack.com' await AuthenticationModel.read(); const userData = AuthenticationModel.chain .get("users") .find({ region: regionPresent, user_id }) .value(); - if (userData?.authtoken) { - - cliUtilities?.configHandler?.set('region', regionPresent); - cliUtilities?.configHandler?.set('authtoken', userData?.authtoken); - // shell.cd(path.resolve(process.cwd(), `../cli/packages/contentstack`)); - // const pwd = shell.exec('pwd'); - // cliLogger(pwd); - // const region = shell.exec(`node bin/run config:set:region ${regionPresent}`); - // cliLogger(region); - // const login = shell.exec(`node bin/run login -a ${userData?.authtoken} -e ${email}`) - // cliLogger(login); - // const exportData = shell.exec(`node bin/run cm:stacks:import -k blt3e7d2a4135d8bfab -d "/Users/umesh.more/Documents/ui-migration/migration-v2-node-server/data" --backup-dir="/Users/umesh.more/Documents/ui-migration/migration-v2-node-server/migrations/blt3e7d2a4135d8bfab"`); - // cliLogger(exportData); - // const cmd = [`-k ${userData?.authtoken}`, "-d /Users/umesh.more/Documents/ui-migration/migration-v2-node-server/api/sitecoreMigrationData", "--backup-dir=/Users/umesh.more/Documents/ui-migration/migration-v2-node-server/migrations/blt3e7d2a4135d8bfab", "--yes"] - - // await importCmd.default.run(cmd); // This will bypass the type issue - // shell.cd(path.resolve(process.cwd(), '..', 'locale-cli', 'packages', 'contentstack')); - // const pwd = shell.exec('pwd'); - // cliLogger(pwd); - // const region = shell.exec(`node bin/run config:set:region ${regionPresent}`); - // cliLogger(region); - // const login = shell.exec(`node bin/run login -a ${userData?.authtoken} -e ${email}`) - // cliLogger(login); - // const exportData = shell.exec(`node bin/run cm:stacks:import -k blt69235b992c3d99c6 -d "/Users/umesh.more/Documents/ui-migration/migration-v2-node-server/api/sitecoreMigrationData" --backup-dir="/Users/umesh.more/Documents/ui-migration/migration-v2-node-server/test"`); - // cliLogger(exportData); + if (userData?.authtoken && stack_uid) { + const sourcePath = path.join(process.cwd(), 'sitecoreMigrationData', stack_uid); + const backupPath = path.join(process.cwd(), 'migration-data', `${stack_uid}_${v4().slice(0, 4)}`); + await copyDirectory(sourcePath, backupPath); + const loggerPath = path.join(backupPath, 'logs', 'import', 'success.log'); + createDirectoryAndFile(loggerPath); + await setLogFilePath(loggerPath); + shell.cd(path.join(process.cwd(), '..', 'cli', 'packages', 'contentstack')); + shell.exec('pwd'); + shell.exec(`node bin/run config:set:region ${regionPresent}`); + shell.exec(`node bin/run login -a ${userData?.authtoken} -e ${userData?.email}`); + const exportData = shell.exec(`node bin/run cm:stacks:import -k ${stack_uid} -d ${sourcePath} --backup-dir=${backupPath} --yes`, { async: true }); + exportData.on('exit', (code) => { + console.info(`Process exited with code: ${code}`); + if (code === 1) { + const projectIndex = ProjectModelLowdb.chain.get("projects").findIndex({ id: projectId }).value(); + if (projectIndex > -1) { + ProjectModelLowdb?.data.projects[projectIndex].test_stacks.map((item: any) => { + if (item?.stackUid === stack_uid) { + item.isMigrated = true; + } + return item; + }) + ProjectModelLowdb.write(); + } + } + }); } else { console.info('user not found.') } } catch (er) { - console.info("🚀 ~ runCli ~ er:", er) + console.error("🚀 ~ runCli ~ er:", er) } } const fieldMapping = async (req: Request): Promise => { const { orgId, projectId } = req?.params ?? {}; - const contentTypes = await fieldAttacher({ orgId, projectId }); - const packagePath = '/Users/umesh.more/Documents/ui-migration/migration-v2-node-server/upload-api/extracted_files/package 45'; - await siteCoreService?.createEntry({ packagePath, contentTypes }); - await siteCoreService?.createLocale(req); - await siteCoreService?.createVersionFile(); const { region, user_id } = req?.body?.token_payload ?? {}; - await runCli(region, user_id); + const project = ProjectModelLowdb.chain.get("projects").find({ id: projectId }).value(); + const packagePath = project?.extract_path; + if (packagePath && project?.current_test_stack_id) { + const contentTypes = await fieldAttacher({ orgId, projectId, destinationStackId: project?.current_test_stack_id }); + await siteCoreService?.createEntry({ packagePath, contentTypes, destinationStackId: project?.current_test_stack_id }); + await siteCoreService?.createLocale(req, project?.current_test_stack_id); + await siteCoreService?.createVersionFile(project?.current_test_stack_id); + await testFolderCreator?.({ destinationStackId: project?.current_test_stack_id }); + await runCli(region, user_id, project?.current_test_stack_id, projectId); + } } export const migrationService = { diff --git a/api/src/services/org.service.ts b/api/src/services/org.service.ts index 4a4eac76..eb7eb390 100644 --- a/api/src/services/org.service.ts +++ b/api/src/services/org.service.ts @@ -368,10 +368,68 @@ const getStackLocale = async (req: Request) => { } }; +/** + * Retrieves the plan details of a org. + * @param req - The request object containing the orgId, token_payload. + * @returns An object containing the org details. + * @throws ExceptionFunction if an error occurs while getting the org details. + */ +const getOrgDetails = async (req: Request) => { + const { orgId } = req.params; + const { token_payload } = req.body; + const srcFunc = "getOrgDetails"; + + const authtoken = await getAuthtoken( + token_payload?.region, + token_payload?.user_id + ); + + try { + const [stackErr, stackRes] = await safePromise( + https({ + method: "GET", + url: `${config.CS_API[ + token_payload?.region as keyof typeof config.CS_API + ]!}/organizations/${orgId}?include_plan=true`, + headers: { + authtoken, + }, + }) + ); + + if (stackErr) + return { + data: { + message: HTTP_TEXTS.DESTINATION_STACK_ERROR, + }, + status: stackErr.response.status, + }; + + return { + status: HTTP_CODES.OK, + data: stackRes.data, + }; + } catch (error: any) { + logger.error( + getLogMessage( + srcFunc, + `Error occurred while getting locales a stack.`, + token_payload, + error + ) + ); + throw new ExceptionFunction( + error?.message || HTTP_TEXTS.INTERNAL_ERROR, + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + ); + } +}; + export const orgService = { getAllStacks, getLocales, createStack, getStackStatus, getStackLocale, + getOrgDetails, }; diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index 30e83276..be28fd0d 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -122,7 +122,7 @@ const createProject = async (req: Request) => { uid: '', label: '', master_locale: '', - created_at: '', + created_at: '', isNewStack: false }, mapperKeys: {} @@ -542,11 +542,13 @@ const fileformatConfirmation = async (req: Request) => { true )) as number; - ProjectModelLowdb.update((data: any) => { - data.projects[projectIndex].legacy_cms.file_format_confirmation = - fileformat_confirmation; - data.projects[projectIndex].updated_at = new Date().toISOString(); - }); + if (!fileformat_confirmation) { + ProjectModelLowdb.update((data: any) => { + data.projects[projectIndex].legacy_cms.file_format_confirmation = + fileformat_confirmation; + data.projects[projectIndex].updated_at = new Date().toISOString(); + }); + } return { status: HTTP_CODES.OK, diff --git a/api/src/services/sitecore.service.ts b/api/src/services/sitecore.service.ts index 966f7ba7..4fe9fa45 100644 --- a/api/src/services/sitecore.service.ts +++ b/api/src/services/sitecore.service.ts @@ -6,9 +6,7 @@ import _ from 'lodash'; import { LOCALE_MAPPER } from '../constants/index.js'; import { entriesFieldCreator, unflatten } from '../utils/entries-field-creator.utils.js'; import { orgService } from './org.service.js'; -const assetsSave = path.join('sitecoreMigrationData', 'assets'); -const entrySave = path.join('sitecoreMigrationData', 'entries'); -const localeSave = path.join('sitecoreMigrationData', 'locale'); + const append = "a"; const idCorrector = ({ id }: any) => { @@ -26,7 +24,7 @@ function startsWithNumber(str: string) { function getLastKey(path: string) { const keys = path?.split?.('.'); - const lastKey = keys[keys.length - 1]; + const lastKey = keys?.[keys?.length - 1]; return lastKey; } @@ -79,7 +77,8 @@ const uidCorrector = ({ uid }: any) => { return _.replace(uid, new RegExp("[ -]", "g"), '_')?.toLowerCase() } -const cretaeAssets = async ({ packagePath }: any) => { +const cretaeAssets = async ({ packagePath, baseDir }: any) => { + const assetsSave = path.join(baseDir, 'assets'); const allAssetJSON: any = {}; const folderName: any = path.join(packagePath, 'items', 'master', 'sitecore', 'media library'); const entryPath = read?.(folderName); @@ -123,23 +122,23 @@ const cretaeAssets = async ({ packagePath }: any) => { } catch (err) { console.error("🚀 ~ file: assets.js:52 ~ xml_folder?.forEach ~ err:", err) } + allAssetJSON[mestaData?.uid] = { + urlPath: `/assets/${mestaData?.uid}`, + uid: mestaData?.uid, + content_type: mestaData?.content_type, + file_size: mestaData.size, + tags: [], + filename: `${jsonAsset?.item?.$?.name}.${mestaData?.extension}`, + is_dir: false, + parent_uid: null, + title: jsonAsset?.item?.$?.name, + publish_details: [], + assetPath + } + allAssetJSON[mestaData?.uid].parent_uid = '2146b0cee522cc3a38d' } else { - console.info("asstes id not found.") - } - allAssetJSON[mestaData?.uid] = { - urlPath: `/assets/${mestaData?.uid}`, - uid: mestaData?.uid, - content_type: mestaData?.content_type, - file_size: mestaData.size, - tags: [], - filename: `${jsonAsset?.item?.$?.name}.${mestaData?.extension}`, - is_dir: false, - parent_uid: null, - title: jsonAsset?.item?.$?.name, - publish_details: [], - assetPath + console.info('blob is not there for this asstes', mestaData?.uid, '.') } - allAssetJSON[mestaData?.uid].parent_uid = '2146b0cee522cc3a38d' } } } @@ -163,9 +162,11 @@ const cretaeAssets = async ({ packagePath }: any) => { return allAssetJSON; } -const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us' }: { packagePath: any; contentTypes: any; master_locale?: string }) => { +const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', destinationStackId }: { packagePath: any; contentTypes: any; master_locale?: string, destinationStackId: string }) => { try { - const allAssetJSON: any = await cretaeAssets({ packagePath }); + const baseDir = path.join('sitecoreMigrationData', destinationStackId); + const entrySave = path.join(baseDir, 'entries'); + const allAssetJSON: any = await cretaeAssets({ packagePath, baseDir }); const folderName: any = path.join(packagePath, 'items', 'master', 'sitecore', 'content'); const entriesData: any = []; if (fs.existsSync(folderName)) { @@ -190,7 +191,6 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us' } } } - for await (const ctType of contentTypes) { const entryPresent: any = entriesData?.find((item: any) => uidCorrector({ uid: item?.template }) === ctType?.contentstackUid) if (entryPresent) { @@ -200,7 +200,7 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us' const entryLocale: any = {}; if (typeof LOCALE_MAPPER?.masterLocale === 'object' && LOCALE_MAPPER?.masterLocale !== null && LOCALE_MAPPER?.masterLocale?.[master_locale] === locale) { newLocale = Object?.keys(LOCALE_MAPPER?.masterLocale)?.[0]; - Object.entries(entryPresent?.locale?.[locale] || {}).forEach(async ([uid, entry]: any) => { + Object.entries(entryPresent?.locale?.[locale] || {}).map(async ([uid, entry]: any) => { const entryObj: any = {}; entryObj.uid = uid; for await (const field of entry?.fields?.field ?? []) { @@ -213,34 +213,39 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us' entryObj[fsc?.contentstackFieldUid] = `/${entry?.meta?.key}`; } if (getLastKey(fsc?.uid) === field?.$?.key) { - const content: any = await entriesFieldCreator({ field: fsc, content: field?.content, idCorrector, allAssetJSON }) + const content: any = await entriesFieldCreator({ field: fsc, content: field?.content, idCorrector, allAssetJSON, contentTypes, entriesData, locale }) entryObj[fsc?.contentstackFieldUid] = content; } } } } - entryLocale[uid] = unflatten(entryObj) ?? {}; + if (Object.keys?.(entryObj)?.length > 1) { + entryLocale[uid] = unflatten(entryObj) ?? {}; + } }); } - const fileMeta = { "1": `${locale}.json` }; + const fileMeta = { "1": `${newLocale}.json` }; const entryPath = path.join( process.cwd(), entrySave, ctType?.contentstackUid, newLocale ); - await writeFiles(entryPath, fileMeta, entryLocale, locale) + await writeFiles(entryPath, fileMeta, entryLocale, newLocale) } } else { console.info('Entries missing for', ctType?.contentstackUid) } } + return true; } catch (err) { console.error("🚀 ~ createEntry ~ err:", err) } } -const createLocale = async (req: any) => { +const createLocale = async (req: any, destinationStackId: string) => { + const baseDir = path.join('sitecoreMigrationData', destinationStackId); + const localeSave = path.join(baseDir, 'locale'); const allLocalesResp = await orgService.getLocales(req) const masterLocale = Object?.keys?.(LOCALE_MAPPER?.masterLocale)?.[0]; const msLocale: any = {}; @@ -280,8 +285,9 @@ const createLocale = async (req: any) => { }) } -const createVersionFile = async () => { - fs.writeFile(path?.join?.('sitecoreMigrationData', 'export-info.json'), JSON.stringify({ +const createVersionFile = async (destinationStackId: string) => { + const baseDir = path.join('sitecoreMigrationData', destinationStackId); + fs.writeFile(path?.join?.(baseDir, 'export-info.json'), JSON.stringify({ "contentVersion": 2, "logsPath": "" }), (err) => { diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index 23227fb6..1e0ffce4 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -1,7 +1,5 @@ import fs from 'fs'; import path from 'path'; -const contentSave = path.join('sitecoreMigrationData', 'content_types'); -const globalSave = path.join('sitecoreMigrationData', 'global_fields'); interface Group { data_type: string; display_name?: string; // Assuming item?.contentstackField might be undefined @@ -88,17 +86,28 @@ const convertToSchemaFormate = ({ field, advanced = true }: any) => { case 'json': { return { "data_type": "json", - "display_name": field?.name, + "display_name": field?.title ?? field?.uid, "uid": field?.uid, "field_metadata": { "allow_json_rte": true, + "embed_entry": false, + "description": "", + "default_value": "", + "multiline": false, "rich_text_type": "advanced", + "options": [] }, - "reference_to": [], - "non_localizable": false, + "format": "", + "error_messages": { + "format": "" + }, + "reference_to": [ + "sys_assets" + ], "multiple": false, - "mandatory": false, - "unique": false + "non_localizable": false, + "unique": false, + "mandatory": false } // return { // "display_name": name, @@ -318,7 +327,7 @@ const convertToSchemaFormate = ({ field, advanced = true }: any) => { "display_name": field?.title, "uid": field?.uid, "data_type": "text", - "mandatory": true, + "mandatory": false, "unique": true, "field_metadata": { "_default": true @@ -332,7 +341,7 @@ const convertToSchemaFormate = ({ field, advanced = true }: any) => { } } -const saveContent = async (ct: any) => { +const saveContent = async (ct: any, contentSave: string) => { try { // Check if the directory exists await fs.promises.access(contentSave).catch(async () => { @@ -366,7 +375,7 @@ const saveContent = async (ct: any) => { } -const writeGlobalField = async (schema: any) => { +const writeGlobalField = async (schema: any, globalSave: string) => { const filePath = path.join(process.cwd(), globalSave, 'globalfields.json'); try { await fs.promises.access(globalSave); @@ -396,7 +405,7 @@ const writeGlobalField = async (schema: any) => { } }; -export const contenTypeMaker = async ({ contentType }: any) => { +export const contenTypeMaker = async ({ contentType, destinationStackId }: any) => { const ct: ContentType = { title: contentType?.contentstackTitle, uid: contentType?.contentstackUid, @@ -411,7 +420,7 @@ export const contenTypeMaker = async ({ contentType }: any) => { "field_metadata": {}, "schema": [], "uid": item?.contentstackFieldUid, - "multiple": true, + "multiple": false, "mandatory": false, "unique": false } @@ -441,9 +450,11 @@ export const contenTypeMaker = async ({ contentType }: any) => { }) if (ct?.uid) { if (contentType?.type === 'global_field') { - await writeGlobalField(ct); + const globalSave = path.join('sitecoreMigrationData', destinationStackId, 'global_fields'); + await writeGlobalField(ct, globalSave); } else { - await saveContent(ct); + const contentSave = path.join('sitecoreMigrationData', destinationStackId, 'content_types'); + await saveContent(ct, contentSave); } } else { console.info(contentType?.contentstackUid, 'missing') diff --git a/api/src/utils/entries-field-creator.utils.ts b/api/src/utils/entries-field-creator.utils.ts index 0b6789b0..7f0cb287 100644 --- a/api/src/utils/entries-field-creator.utils.ts +++ b/api/src/utils/entries-field-creator.utils.ts @@ -3,6 +3,20 @@ import { JSDOM } from "jsdom"; import { htmlToJson } from '@contentstack/json-rte-serializer'; import { HTMLToJSON } from 'html-to-json-parser'; +const append = "a"; + +function startsWithNumber(str: string) { + return /^\d/.test(str); +} + +const uidCorrector = ({ uid }: any) => { + if (startsWithNumber(uid)) { + return `${append}_${_.replace(uid, new RegExp("[ -]", "g"), '_')?.toLowerCase()}` + } + return _.replace(uid, new RegExp("[ -]", "g"), '_')?.toLowerCase() +} + + const attachJsonRte = ({ content = "" }: any) => { const dom = new JSDOM(content); @@ -160,7 +174,7 @@ const findAssestInJsoRte = (jsonValue: any, allAssetJSON: any, idCorrector: any) -export const entriesFieldCreator = async ({ field, content, idCorrector, allAssetJSON }: any) => { +export const entriesFieldCreator = async ({ field, content, idCorrector, allAssetJSON, contentTypes, entriesData, locale }: any) => { switch (field?.ContentstackFieldType) { case 'multi_line_text': @@ -174,10 +188,29 @@ export const entriesFieldCreator = async ({ field, content, idCorrector, allAsse } case 'dropdown': { - if (content?.includes('{')) { - return idCorrector({ id: content }); + const isOptionPresent = field?.advanced?.options?.find((ops: any) => ops?.key === content || ops?.value === content); + if (isOptionPresent) { + if (field?.advanced?.Multiple) { + if (!isOptionPresent?.key) { + return isOptionPresent + } + return isOptionPresent; + } + return isOptionPresent?.value ?? null; + } else { + if (field?.advanced?.Default_value) { + const isOptionDefaultValue = field?.advanced?.options?.find((ops: any) => ops?.key === field?.advanced?.Default_value || ops?.value === field?.advanced?.Default_value); + if (field?.advanced?.Multiple) { + if (!isOptionDefaultValue?.key) { + return isOptionDefaultValue + } + return isOptionDefaultValue; + } + return isOptionDefaultValue?.value ?? null; + } else { + return field?.advanced?.Default_value; + } } - return content; } case 'number': { @@ -191,19 +224,19 @@ export const entriesFieldCreator = async ({ field, content, idCorrector, allAsse const fileData = attachJsonRte({ content }); fileData?.children?.forEach((item: any) => { if (item?.attrs?.['redactor-attributes']?.mediaid) { - // const assetUid = idCorrector({ id: item?.attrs?.['redactor-attributes']?.mediaid }); - console.info(''); + const assetUid = idCorrector({ id: item?.attrs?.['redactor-attributes']?.mediaid }); + return allAssetJSON?.[assetUid] ?? null; } else { - console.info(item?.attrs) + console.info('more', item?.attrs) } }) - return content; + return null; } //need to change this case 'link': { - const linkType: any = htmlConverter({ content }) - let obj: any = { title: '', url: '' }; + const linkType: any = await htmlConverter({ content }) + let obj: any = { title: '', href: '' }; if (typeof linkType === 'string') { const parseData = JSON?.parse?.(linkType); if (parseData?.type === 'div') { @@ -211,7 +244,7 @@ export const entriesFieldCreator = async ({ field, content, idCorrector, allAsse if (item?.type === 'link') { obj = { title: item?.attributes?.id, - url: item?.attributes?.url ?? '' + href: item?.attributes?.url ?? '' } } }) @@ -220,8 +253,78 @@ export const entriesFieldCreator = async ({ field, content, idCorrector, allAsse return obj; } + case 'reference': { + const refs: any = []; + if (field?.refrenceTo?.length) { + field?.refrenceTo?.forEach((entry: any) => { + const templatePresent = entriesData?.find((tel: any) => uidCorrector({ uid: tel?.template }) === entry); + content?.split('|')?.forEach((id: string) => { + const entryid = templatePresent?.locale?.[locale]?.[idCorrector({ id })]; + if (entryid) { + refs?.push({ + "uid": idCorrector({ id }), + "_content_type_uid": entry + }) + } else { + // console.info("no entry for following id", id) + } + }) + }) + } else { + console.info('test ====>'); + } + return refs; + } + + case 'text': { + return content; + } + + case 'global_field': { + const globalFieldsSchema = contentTypes?.find?.((gfd: any) => + gfd?.contentstackUid === field?.contentstackFieldUid && gfd?.type === 'global_field' + ); + if (globalFieldsSchema?.fieldMapping) { + const mainSchema = []; + const group: any = {}; + globalFieldsSchema?.fieldMapping?.forEach((item: any) => { + if (item?.ContentstackFieldType === 'group') { + group[item?.contentstackFieldUid] = { ...item, fieldMapping: [] }; + } else { + const groupSchema = group[item?.contentstackFieldUid?.split('.')?.[0]]; + if (groupSchema) { + group?.[groupSchema?.contentstackFieldUid]?.fieldMapping?.push(item); + } else { + mainSchema?.push(item); + } + } + }); + mainSchema?.push(group); + const obj: any = {}; + mainSchema?.forEach(async (field: any) => { + if (field?.['uid']) { + obj[field?.contentstackFieldUid] = await entriesFieldCreator({ field, content }); + } else { + Object?.values(field)?.forEach((item: any) => { + if (item?.ContentstackFieldType === 'group') { + item?.fieldMapping?.forEach(async (ele: any) => { + obj[ele?.contentstackFieldUid] = await entriesFieldCreator({ field: ele, content }); + }) + } + }) + } + }) + return await obj; + } + break; + } + + case 'boolean': { + return typeof content === 'string' && content === '1' ? true : false; + } + default: { - console.info(field?.ContentstackFieldType); + console.info(field?.ContentstackFieldType, 'umesh'); return content; } } diff --git a/api/src/utils/field-attacher.utils.ts b/api/src/utils/field-attacher.utils.ts index dafcb36f..40459d52 100644 --- a/api/src/utils/field-attacher.utils.ts +++ b/api/src/utils/field-attacher.utils.ts @@ -3,7 +3,7 @@ import ContentTypesMapperModelLowdb from "../models/contentTypesMapper-lowdb.js" import FieldMapperModel from "../models/FieldMapper.js"; import { contenTypeMaker } from "./content-type-creator.utils.js"; -export const fieldAttacher = async ({ projectId, orgId }: any) => { +export const fieldAttacher = async ({ projectId, orgId, destinationStackId }: any) => { await ProjectModelLowdb.read(); const projectData = ProjectModelLowdb.chain.get("projects").find({ id: projectId, @@ -27,7 +27,7 @@ export const fieldAttacher = async ({ projectId, orgId }: any) => { return field; }) } - await contenTypeMaker({ contentType }) + await contenTypeMaker({ contentType, destinationStackId }) contentTypes?.push?.(contentType); } } diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 15e6ea61..9404b302 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -1,9 +1,11 @@ +import fs from 'fs-extra'; /** * Throws an error with a custom message and status code. * @param message - The error message. * @param statusCode - The HTTP status code associated with the error. * @throws {Error} - The error object with the specified message and status code. */ + export const throwError = (message: string, statusCode: number) => { throw Object.assign(new Error(message), { statusCode }); }; @@ -37,6 +39,7 @@ export const safePromise = (promise: Promise): Promise => * @param error - The error object. Optional. * @returns The log message object. */ + export const getLogMessage = ( methodName: string, message: string, @@ -50,3 +53,24 @@ export const getLogMessage = ( ...(error && { error }), }; }; + +/* + * Recursively copies a directory from source to destination + * @param srcDir - Source directory path + * @param destDir - Destination directory path +*/ + +export async function copyDirectory(srcDir: string, destDir: string): Promise { + try { + // Ensure the destination directory exists, if not, create it + await fs.ensureDir(destDir); + + // Copy the source directory to the destination + await fs.copy(srcDir, destDir); + + console.info(`Directory copied from ${srcDir} to ${destDir}`); + + } catch (error) { + console.error(`Error copying directory: ${error}`); + } +} \ No newline at end of file diff --git a/api/src/utils/test-folder-creator.utils.ts b/api/src/utils/test-folder-creator.utils.ts new file mode 100644 index 00000000..98cff864 --- /dev/null +++ b/api/src/utils/test-folder-creator.utils.ts @@ -0,0 +1,176 @@ +import path from 'path'; +import fs from 'fs'; +import read from 'fs-readdir-recursive'; + + +async function writeOneFile(indexPath: string, fileMeta: any) { + fs.writeFile(indexPath, JSON.stringify(fileMeta), (err) => { + if (err) { + console.error('Error writing file: 3', err); + } + }); +} + +async function writeFiles(entryPath: string, fileMeta: any, entryLocale: any, locale: string) { + try { + const indexPath = path.join(entryPath, 'index.json'); + const localePath = path.join(entryPath, `${locale}.json`); + fs.access(entryPath, async (err) => { + if (err) { + fs.mkdir(entryPath, { recursive: true }, async (err) => { + if (err) { + console.error('Error writing file: 2', err); + } else { + await writeOneFile(indexPath, fileMeta) + await writeOneFile(localePath, entryLocale) + } + }); + } else { + await writeOneFile(indexPath, fileMeta) + await writeOneFile(localePath, entryLocale) + } + }); + } catch (error) { + console.error('Error writing files:', error); + } +} + +const saveContent = async (ct: any, contentSave: string) => { + try { + // Check if the directory exists + await fs.promises.access(contentSave).catch(async () => { + // If the directory doesn't exist, create it + await fs.promises.mkdir(contentSave, { recursive: true }); + }); + // Write the individual content to its own file + const filePath = path.join(process.cwd(), contentSave, `${ct?.uid}.json`); + await fs.promises.writeFile(filePath, JSON.stringify(ct)); + // Append the content to schema.json + const schemaFilePath = path.join(process.cwd(), contentSave, 'schema.json'); + let schemaData = []; + try { + // Read existing schema.json file if it exists + const schemaFileContent = await fs.promises.readFile(schemaFilePath, 'utf8'); + schemaData = JSON.parse(schemaFileContent); + } catch (readError: any) { + if (readError?.code !== 'ENOENT') { + throw readError; // rethrow if it's not a "file not found" error + } + } + // Append new content to schemaData + schemaData.push(ct); + // Write the updated schemaData back to schema.json + await fs.promises.writeFile(schemaFilePath, JSON.stringify(schemaData, null, 2)); + + } catch (err) { + console.error("Error:", err); + } + +} + + +async function cleanDirectory(folderPath: string, foldersToKeep: any[]): Promise { + try { + // Ensure we're only working with the first 10 folders to keep + const foldersToKeepLimited = foldersToKeep.map((item: any) => item?.uid); + + // Read all items (files and folders) in the directory + const itemsInDirectory = await fs.promises.readdir(folderPath, { withFileTypes: true }); + + // Loop through all items in the directory + for (const item of itemsInDirectory) { + const itemPath = path.join(folderPath, item?.name); + + // Check if the item is a directory and if it's not in the list of folders to keep + if (item?.isDirectory() && !foldersToKeepLimited?.includes(item?.name)) { + // Delete the folder and its contents + await fs.promises.rm(itemPath, { recursive: true, force: true }); + console.info(`Deleted folder: ${item.name}`); + } + } + console.info("Cleanup completed!"); + } catch (err) { + console.error(`Error while cleaning directory: ${(err as Error).message}`); + } +} + + +async function deleteFolderAsync(folderPath: string): Promise { + try { + await fs.promises.rm(folderPath, { recursive: true, force: true }); + console.info(`Folder ${folderPath} deleted successfully.`); + } catch (err) { + console.error(`Error while deleting folder: ${(err as Error).message}`); + } +} + + + +const sortAssets = async (baseDir: string) => { + const assetsPath = path.join(process.cwd(), baseDir, 'assets'); + const assetsFilesPath = path.join(assetsPath, 'files'); + const assetsJson = JSON.parse(await fs.promises.readFile(path.join(assetsPath, 'index.json'), 'utf8')); + const sortAsset = Object?.values?.(assetsJson)?.slice(0, 10); + const assetsMeta: any = {}; + sortAsset?.forEach((item: any) => { + assetsMeta[item?.uid] = item; + }) + await cleanDirectory(assetsFilesPath, sortAsset); + await fs.promises.writeFile(path.join(assetsPath, 'index.json'), JSON?.stringify?.(assetsMeta)); +} + +const sortContentType = async (baseDir: string, finalData: any) => { + const contentTypePath: string = path.join(process.cwd(), baseDir, 'content_types'); + const contentSave = path.join(baseDir, 'content_types'); + const ctData = await JSON.parse(await fs.promises.readFile(path.join(contentTypePath, 'schema.json'), 'utf8')); + const contentTypes: any = []; + finalData?.forEach((ct: any) => { + const findCtData = ctData?.find((ele: any) => ele?.uid === ct?.contentType) + contentTypes?.push(findCtData); + }) + await deleteFolderAsync(contentTypePath); + for await (const ctItem of contentTypes) { + await saveContent(ctItem, contentSave); + } +} + +export const testFolderCreator = async ({ destinationStackId }: any) => { + const baseDir = path.join('sitecoreMigrationData', destinationStackId); + const entryDelete = path.join(process.cwd(), baseDir, 'entries'); + const entrySave = path.join(baseDir, 'entries'); + const entriesPath = path.join(process.cwd(), baseDir, 'entries'); + const allData = []; + for await (const filePath of read(entriesPath)) { + if (!filePath?.endsWith('index.json')) { + const entryData = await JSON.parse(await fs.promises.readFile(path.join(entriesPath, filePath), 'utf8')); + if (Object?.keys?.(entryData)?.length) { + const ct = filePath?.split?.('/')?.[0]; + const locale = filePath?.split?.('/')?.[1]; + allData?.push({ contentType: ct, count: Object?.keys?.(entryData)?.length, entryData, filePath, locale }) + } + } + } + const sortData = allData.sort((a, b) => b?.count - a?.count).slice?.(1, 4); + const finalData: any = []; + sortData.forEach((et: any) => { + const entryObj: any = {}; + const ctData = Object?.values?.(et?.entryData)?.splice?.(0, 5); + ctData?.forEach((entItem: any) => { + entryObj[entItem?.uid] = entItem; + }) + finalData?.push({ contentType: et?.contentType, entryObj, locale: et?.locale }); + }); + await sortAssets(baseDir); + await sortContentType(baseDir, finalData); + await deleteFolderAsync(entryDelete); + for await (const entry of finalData) { + const fileMeta = { "1": `${entry?.locale}.json` }; + const entryPath = path.join( + process.cwd(), + entrySave, + entry?.contentType, + entry?.locale + ); + await writeFiles(entryPath, fileMeta, entry?.entryObj, entry?.locale); + } +} \ No newline at end of file diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 00000000..5db4308b --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,21 @@ +node_modules +.todo +.env +.dccache +logs +contents +lerna-debug.log +.DS_Store +contentTest +build +_backup* +oclif.manifest.json +.vscode +.nyc_output +contentstack-cli-logs +packages/**/package-lock.json +.dccache +yarn.lock +contents-* +*.http +*.todo diff --git a/cli/.npmrc b/cli/.npmrc new file mode 100644 index 00000000..a0134a1a --- /dev/null +++ b/cli/.npmrc @@ -0,0 +1 @@ +# shamefully-hoist=true \ No newline at end of file diff --git a/cli/.prettierignore b/cli/.prettierignore new file mode 100644 index 00000000..70988e21 --- /dev/null +++ b/cli/.prettierignore @@ -0,0 +1 @@ +**/README.md \ No newline at end of file diff --git a/cli/.prettierrc b/cli/.prettierrc new file mode 100644 index 00000000..ba93fc77 --- /dev/null +++ b/cli/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 2 +} \ No newline at end of file diff --git a/cli/.talismanrc b/cli/.talismanrc new file mode 100644 index 00000000..ed3ff8da --- /dev/null +++ b/cli/.talismanrc @@ -0,0 +1,112 @@ +fileignoreconfig: +- filename: packages/contentstack-export/.env + checksum: b057ae11234ac9411fe5dfa54ac3b029a71db51b4c334dce8677ab6fc1e41ecf +- filename: packages/contentstack-export/test/integration/environments.test.js + checksum: fb3760705545f4d672efcd86640441ba6ddc42b69ecf1aef2db9f6c9fd545aa3 +- filename: packages/contentstack-import/.env.sample + checksum: 0d6b520e59241f40e3999047dbf308905b0ce0c933865cac8f3453f464d6ddc1 +- filename: packages/contentstack-import/test/integration/auth-token-modules/environments.test.js + checksum: f2f4e81e754a37d597bf7d432c397f19c10204dd253f427f9b633147ef8b4fa0 +- filename: packages/contentstack-import/test/integration/environments.test.js + checksum: 0f66f696dbb0bd328532398a559df0e4eb1b4e395e5457cd1d55761f55fa9d4b +- filename: packages/contentstack-export/test/integration/clean-up.test.js + checksum: 51d9127d98972ba890d77a3ce25e2e3afc5963e9547bcd96648af0daf42e5d91 +- filename: packages/contentstack-export/test/integration/assets.test.js + checksum: b0551e2667693d9800ea37873a018e74a9de34d00e888a5c77bb25f237d0390a +- filename: packages/contentstack-export/test/integration/content-types.test.js + checksum: e546611b8ca3b91eef3cdcf0def7a407706dbfa328dfab75298adc5e99cd9126 +- filename: packages/contentstack-export/test/integration/init.test.js + checksum: acf2f842960d0ac14b8ea7dfe29e921aca1ffddd814520526c5a527ed7c207e9 +- filename: packages/contentstack-export/test/integration/entries.test.js + checksum: 6d79a28d9fe40b197b9aba0c2b175c5bac2719330e36c4029efcfd99c0b753bc +- filename: packages/contentstack-export/test/integration/extensions.test.js + checksum: 12c729d89a44961ea1291e45d5b35446d298770c7529eef1b54e9a381a5c0b95 +- filename: packages/contentstack-export/test/integration/custom-roles.test.js + checksum: 8d55673098b48885707c1e6c76712cce2b3f4290b701e47b06b3f19c97cd9cce +- filename: packages/contentstack-export/test/integration/labels.test.js + checksum: c222ffab43134f6a946ddd1299cfb51a5814e4b2973cd3f46c7bafc5e54deeb8 +- filename: packages/contentstack-export/test/integration/locales.test.js + checksum: 09704b368fc294d272ec534057f255be7f2163dccf7c0fb633eab2c90747f73b +- filename: packages/contentstack-export/test/integration/global-fields.test.js + checksum: 88916475ed109bf556164a061907c1458c8732ff84a7ac799b25d1818b684ce2 +- filename: packages/contentstack-export/test/integration/marketplace-apps.test.js + checksum: 44c0bec62c7a6595684ade6fbf345b86a4e2ba62123a5d1c5efa87d798fb3b3e +- filename: packages/contentstack-export/test/integration/webhooks.test.js + checksum: af5d0f6f6d6f5a5e57dc9dd4c9a6415cc6f87ea94c523a4456292204cf94df7d +- filename: packages/contentstack-import/test/integration/locales.test.js + checksum: 535c19c2f72d335df20dd6c39beecd7791af0e931cfb3c3bfb9b32897ba9984f +- filename: packages/contentstack-import/test/integration/management-token.test.js + checksum: 38f4a5015e525d13b4782fbeff726103b30fc718d5b3e38f544310aec9b442b7 +- filename: packages/contentstack-export/test/integration/workflows.test.js + checksum: fc86976f89c6d5d51943929fcafc030560fca2a868b742e43f64cde25bf77ad9 +- filename: packages/contentstack-export/test/integration/utils/helper.js + checksum: f187f9f5b0f6b3ceb6c22f0ec93d08c68d38aff87a49c1de8803792eadd8ba8f +- filename: packages/contentstack-import/test/integration/webhooks.test.js + checksum: 172adffdde8575609a7782b3374d071bd4ee11774b4019beded0ea1a97900c42 +- filename: packages/contentstack-utilities/types/index.d.ts + checksum: aed7069f6bff928bcdf84c773ef33e883db89d768f83b791d430eaff543ba33b +- filename: packages/contentstack-import/test/integration/workflows.test.js + checksum: 9c37be03cad2630706df1199350e17bbbadba19c63d3736e25e3d32d2ec89c60 +- filename: packages/contentstack-import/test/integration/utils/helper.js + checksum: eed26f2fb07b3369dc8e474f9fbe45dd4f4aa60f63ec7b779e6061a863e9ea17 +- filename: packages/contentstack-import/test/integration/auth-token-modules/assets.test.js + checksum: 5502c031c4d4a024fccf4fb1289d5983c27c1972629f8adc136bc6d33d11bdbc +- filename: packages/contentstack-import/test/integration/assets.test.js + checksum: 547e601910f8eeb094e51425a881af6bb59cb18a23f6dad16c79c1294e2182cd +- filename: packages/contentstack-import/test/integration/auth-token-modules/content-types.test.js + checksum: 022b6fc5fb5a2044c42d82bc51d83ab2b3cb315cf402dc223f2ea0a7a1b81cc9 +- filename: packages/contentstack-import/test/integration/auth-token-modules/custom-roles.test.js + checksum: 9c973633d4927ffcbe45712760d32c4299d5731376dff6b8335c745fef4ce307 +- filename: packages/contentstack-import/test/integration/auth-token-modules/entries.test.js + checksum: 11a5fbd92e17f064b55989c4c5c80d6144f1e0348c1b32c899fe92d72dfdd437 +- filename: packages/contentstack-import/test/integration/content-types.test.js + checksum: a2f426ff6f0e11e8a56c6cb85ff9e78a1db717506dc59ccba86730bdbdff7fc0 +- filename: packages/contentstack-import/test/integration/extensions.test.js + checksum: 1cb29b60cb629372a782a3f19fdb26f29563e99c0e571e24bd4f81f1655ef8c5 +- filename: packages/contentstack-import/test/integration/auth-token-modules/extensions.test.js + checksum: 901e5e702103056c503176cdd8366f83d0720128a72145188401d13c312d995c +- filename: packages/contentstack-import/test/integration/entries.test.js + checksum: 9058a902d097577734fd658e41d3e5c3383cc7891a6efc76614a3d7cba287c09 +- filename: packages/contentstack-import/test/integration/auth-token-modules/global-fields.test.js + checksum: 546cb9cd32bcda685f81b0b20263aa589ac69684fd6f1a9423e25f0856d0f9f7 +- filename: packages/contentstack-import/test/integration/auth-token-modules/workflows.test.js + checksum: 69d6115078c805ea933d815d6032de843618497ca1281bb24c486b2e95002f5c +- filename: packages/contentstack-import/test/integration/clean-up.test.js + checksum: 827dc2102b03b4c1a0ef77fa12b821ed69e549f360a35bcb6aa4010d3fbe4cf0 +- filename: packages/contentstack-import/test/integration/init.test.js + checksum: acad0ab08cfd3a6ceb7569fdce23586ddb36249e3597da8970a8c9ebe68a3500 +- filename: packages/contentstack-import/test/integration/custom-roles.test.js + checksum: 694056f3b237e31b54745297f46e7b1c363377d1868572b109588963f6be2c9b +- filename: packages/contentstack-import/test/integration/auth-token-modules/locales.test.js + checksum: 142276d6e683c11d6c68ba212ab9dc8f0ba74175d7e4b914266e888770fdd70d +- filename: packages/contentstack-import/test/integration/auth-token.test.js + checksum: 1d9e3c40d10b8f0ebc117c3b8a5bd3a4616472ff5ebbeb19b5a5cc0ba90a829b +- filename: packages/contentstack-import/test/integration/global-fields.test.js + checksum: 1c1f05989f22d06b600fba7456c1935a385997292c770d573165b9a07e1a3b8a +- filename: packages/contentstack-import/test/integration/auth-token-modules/webhooks.test.js + checksum: f41c4c354482293e4d6e52923d451266b150ba675e61681e21ba83ebf5dd8382 +- filename: pnpm-lock.yaml + checksum: 8bc914084e311432154f0e3bd05e2bbb60516460e09b9f8a6356c69d335b38b2 +- filename: package-lock.json + checksum: 64cca4648257efc253c34babd3d857eadc2e4a42c8e84959f2005ec4dae0ed8a +- filename: packages/contentstack-launch/src/commands/launch/environments.ts + checksum: a9f5fc3fad1915ca8812f1159c4019b957db59f7b4c8e2da0d214e45b8835ef7 +- filename: packages/contentstack-launch/tsconfig.tsbuildinfo + checksum: 0c363e5b14b8e5c3b696d81aeeac4b8ae3cf98bbe255d90600c4c1f4757f33a2 +- filename: packages/contentstack-launch/src/adapters/file-upload.ts + checksum: e17a1112b724b51ce3e3c4a1eed19c4f84b5c2f9cb70a9a49c075ed08f2fef97 +- filename: packages/contentstack-launch/src/adapters/github.ts + checksum: b82c72f8d1dc52dc099075905b038cea23e3547edcc2938eefcac7129c0656a4 +- filename: packages/contentstack-launch/src/commands/launch/base-command.ts + checksum: b3cafb6f98c27abf4024639a3ca029861de8f80789dc98754dd694500782d968 +- filename: packages/contentstack-launch/src/adapters/base-class.ts + checksum: d95e179bf489e552faa4d6cebb05473a2f92ceaccb9284a429ebf5ab61ec5ca0 +- filename: packages/contentstack-launch/src/util/cloud-function/errors/cloud-function.errors.ts + checksum: e2da74b8b0f25ff58639aab329da77b3408838136f5bcd16c671bc113057a036 +- filename: packages/contentstack-utilities/src/auth-handler.ts + checksum: b1f05a05bd25543ba3bbd9941b0d498e3291bf320574d9676f154716530fc552 +- filename: packages/contentstack-utilities/src/config-handler.ts + checksum: e345a745f027c76081df71e4fe9872c1f4adac076689b036e195b84041807b59 +- filename: packages/contentstack-launch/src/util/create-git-meta.ts + checksum: 8cbd32dbbd2989c7c082f8a0b7615916125d211bce25531e9a882b8ebd5674af +version: "" \ No newline at end of file diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md new file mode 100644 index 00000000..9b83054c --- /dev/null +++ b/cli/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +Please refer to the Contentstack Command-line Interface release notes [here](https://www.contentstack.com/docs/developers/cli/cli-changelog). diff --git a/cli/CODEOWNERS b/cli/CODEOWNERS new file mode 100644 index 00000000..07739234 --- /dev/null +++ b/cli/CODEOWNERS @@ -0,0 +1 @@ +* @contentstack/security-admin \ No newline at end of file diff --git a/cli/LICENSE b/cli/LICENSE new file mode 100644 index 00000000..ffb4ad01 --- /dev/null +++ b/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Contentstack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 00000000..60eca7d5 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,57 @@ +# Contentstack CLI + + +Contentstack is a headless CMS with an API-first approach that puts content at the centre. It is designed to simplify the process of publication by separating code from content. + +CLI supports content management scripts through which you can perform the following tasks: + +- Bulk publish content +- Export content +- Import content +- Clone Stack +- Seed Stack from GitHub +- Perform Launch operations +- Migrate content +- Migrate HTML RTE to JSON RTE content +- Change Master Locale +- Use Bootstrap plugin +- Use Tsgen plugin + + +## Installing CLI +### Prerequisites +Contentstack account +Node.js version 16 or above + +### Installation +To install CLI on your system, run the below command in your terminal: + +``` +npm install -g @contentstack/cli +``` + +To verify the installation, run `csdx` in the command window. + +## Usage +After the successful installation of CLI, use the `--help` parameter to display the help section of the CLI. You can even combine this parameter with a specific command to get the help section of that command. + +```shell +$ csdx --help +``` + +## Namespaces +**auth**: To perform [authentication-related](/packages/contentstack-auth) activities + +**cm**: To perform content management activities such as [bulk publish](/packages/contentstack-bulk-publish), [import](/packages/contentstack-import), and [export](/packages/contentstack-export), [export-to-csv] (/packages/contentstack-export-to-csv), [seed] (/packages/contentstack-seed) + +**help**: To list the helpful commands in CLI + +**config**: To set regions and customize them + +## Documentation + +To get a more detailed documentation for every command, visit the [CLI section](https://www.contentstack.com/docs/developers/cli) in our docs. + +## Useful Plugins + +- [Generate TypeScript typings from a Stack](https://github.com/Contentstack-Solutions/contentstack-cli-tsgen) diff --git a/cli/SECURITY.md b/cli/SECURITY.md new file mode 100644 index 00000000..1f44e342 --- /dev/null +++ b/cli/SECURITY.md @@ -0,0 +1,27 @@ +## Security + +Contentstack takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any Contentstack-owned repository, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Send email to [security@contentstack.com](mailto:security@contentstack.com). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +[https://www.contentstack.com/trust/](https://www.contentstack.com/trust/) diff --git a/cli/install.sh b/cli/install.sh new file mode 100755 index 00000000..ca06650e --- /dev/null +++ b/cli/install.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +echo 'Installing Contentstack CLI' +npm run setup-repo || { echo -e '\e[31mFailed to install root dependencies.' && exit 1; } +echo 'Installtion, Done!!!' +cd packages/contentstack || { echo -e '\e[31mCould not found the working directory.' && exit 1; } +./bin/run --help \ No newline at end of file diff --git a/cli/package.json b/cli/package.json new file mode 100755 index 00000000..c3fe63cb --- /dev/null +++ b/cli/package.json @@ -0,0 +1,26 @@ +{ + "name": "csdx", + "version": "1.0.0", + "devDependencies": { + "pnpm": "^7.33.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "private": true, + "scripts": { + "cache:clear": "pnpm store prune", + "clean": "pnpm --filter './packages/*' -w clean", + "bootstrap": "pnpm install --no-frozen-lockfile", + "prepack": "pnpm --filter \"./packages/*\" -w prepack", + "package-lock-only": "npm i --package-lock-only --workspaces", + "setup-repo-old": "npm i && pnpm package-lock-only && pnpm clean && pnpm install --no-frozen-lockfile && pnpm prepack", + "clean-repo": "rm -rf ./package-lock.json ./node_modules ./packages/**/node_modules ./packages/**/.nyc_output ./packages/**/package-lock.json", + "preinstall-clean": "npm run clean-repo && npm cache clean --force && npx pnpm store prune", + "setup-repo": "npm run preinstall-clean && npm i && npm run package-lock-only && npm run clean && pnpm install --no-frozen-lockfile && npm run prepack" + }, + "license": "MIT", + "workspaces": [ + "packages/*" + ] +} diff --git a/cli/packages/contentstack-audit/.eslintignore b/cli/packages/contentstack-audit/.eslintignore new file mode 100644 index 00000000..9b1c8b13 --- /dev/null +++ b/cli/packages/contentstack-audit/.eslintignore @@ -0,0 +1 @@ +/dist diff --git a/cli/packages/contentstack-audit/.eslintrc b/cli/packages/contentstack-audit/.eslintrc new file mode 100644 index 00000000..7b846193 --- /dev/null +++ b/cli/packages/contentstack-audit/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "oclif", + "oclif-typescript" + ] +} diff --git a/cli/packages/contentstack-audit/.gitignore b/cli/packages/contentstack-audit/.gitignore new file mode 100644 index 00000000..74daa1ef --- /dev/null +++ b/cli/packages/contentstack-audit/.gitignore @@ -0,0 +1,21 @@ +*-debug.log +*-error.log +/.nyc_output +/dist +/lib +/package-lock.json +/tmp +/yarn.lock +node_modules +oclif.manifest.json +.env +*.log +tsconfig.tsbuildinfo +dependabot.yml +.vscode +*.todo +/bkp +.editorconfig +oclif.manifest.json +*.env +.vscode/ \ No newline at end of file diff --git a/cli/packages/contentstack-audit/.mocharc.json b/cli/packages/contentstack-audit/.mocharc.json new file mode 100644 index 00000000..4a09d144 --- /dev/null +++ b/cli/packages/contentstack-audit/.mocharc.json @@ -0,0 +1,12 @@ +{ + "require": [ + "test/helpers/init.js", + "ts-node/register" + ], + "watch-extensions": [ + "ts" + ], + "recursive": true, + "reporter": "spec", + "timeout": 60000 +} diff --git a/cli/packages/contentstack-audit/LICENSE b/cli/packages/contentstack-audit/LICENSE new file mode 100644 index 00000000..f99e9ee5 --- /dev/null +++ b/cli/packages/contentstack-audit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Contentstack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/cli/packages/contentstack-audit/README.md b/cli/packages/contentstack-audit/README.md new file mode 100644 index 00000000..4d874d45 --- /dev/null +++ b/cli/packages/contentstack-audit/README.md @@ -0,0 +1,537 @@ + + + +# @contentstack/cli-audit + +Audit plugin + +## How to install this plugin + +```shell +$ csdx plugins:install @contentstack/cli-audit +``` + +## How to use this plugin + + +```sh-session +$ npm install -g @contentstack/cli-audit +$ csdx COMMAND +running command... +$ csdx (--version|-v) +@contentstack/cli-audit/1.6.5 darwin-arm64 node-v21.6.1 +$ csdx --help [COMMAND] +USAGE + $ csdx COMMAND +... +``` + + +# Commands + + +* [`csdx audit`](#csdx-audit) +* [`csdx audit:fix`](#csdx-auditfix) +* [`csdx cm:stacks:audit`](#csdx-cmstacksaudit) +* [`csdx cm:stacks:audit:fix`](#csdx-cmstacksauditfix) +* [`csdx help [COMMANDS]`](#csdx-help-commands) +* [`csdx plugins`](#csdx-plugins) +* [`csdx plugins:add PLUGIN`](#csdx-pluginsadd-plugin) +* [`csdx plugins:inspect PLUGIN...`](#csdx-pluginsinspect-plugin) +* [`csdx plugins:install PLUGIN`](#csdx-pluginsinstall-plugin) +* [`csdx plugins:link PATH`](#csdx-pluginslink-path) +* [`csdx plugins:remove [PLUGIN]`](#csdx-pluginsremove-plugin) +* [`csdx plugins:reset`](#csdx-pluginsreset) +* [`csdx plugins:uninstall [PLUGIN]`](#csdx-pluginsuninstall-plugin) +* [`csdx plugins:unlink [PLUGIN]`](#csdx-pluginsunlink-plugin) +* [`csdx plugins:update`](#csdx-pluginsupdate) + +## `csdx audit` + +Perform audits and find possible errors in the exported Contentstack data + +``` +USAGE + $ csdx audit [--report-path ] [--modules content-types|global-fields|entries|extensions|workflows] + [--columns | ] [--sort ] [--filter ] [--csv | --no-truncate] + +FLAGS + --modules=