/* eslint-disable no-console */ import output from '@/output' import { createReadmeContent, initGlobalPluginConfig, initPluginYaml, initProjectLocalYaml, } from '@/scripts' import { hasWorkspacesInPackageJson } from '@/scripts' import { installProjectNpmDependencies } from '@/scripts/shared' import { BACKEND_DIR_NAME, DEFAULT_ABILITY_PACKAGE_NAME, DEFAULT_TEMPLATE_PACKAGE_NAME, GLOBAL_CLI_PLUGIN_CONFIG_FILE_PATH, LOCAL_CONFIG_RELATIVE_PATH, PLUGIN_CONFIG_RELATIVE_PATH, WEB_DIR_NAME, checkFileExists, checkProjectFilesExists, getPackageJsonContent, getNpm, getPackageInNodeModulesDir, getYamlConfigContent, mergeFieldsIntoPackageJson, mergeTemplateJsonIntoPackageJson, renameHiddenTemplateFiles, } from '@ones-open/cli-utils' import type { ProjectCreatingCLIOptionSchema, ProjectCreatingParamsSchema, ProjectPluginConfig, } from '@ones-open/cli-utils' import { generateOpConfigFile } from '@ones-open/cli-utils' import { execa } from 'execa' import { copy, remove } from 'fs-extra' import Listr from 'listr' import type { ListrTask } from 'listr' import { join } from 'path' import { cwd } from 'process' interface CreatePluginProjectParams { currentWorkingDirectory: string projectCreatingParams: ProjectCreatingParamsSchema options: ProjectCreatingCLIOptionSchema } async function downloadProjectTemplate( currentWorkingDirectory: string, installLocalDependencies = false, ) { const installCommand = getNpm() const templatePackages = [DEFAULT_TEMPLATE_PACKAGE_NAME, DEFAULT_ABILITY_PACKAGE_NAME] const { optionalDependencies } = await getPackageJsonContent(import.meta.url) // Install template packages and save packages to package.json const templateInstallArgs = [ installLocalDependencies ? 'link' : 'install', ...templatePackages.map( (packageName) => `${packageName}@${optionalDependencies?.[packageName] || 'latest'}`, ), '--save-dev', ] await execa(installCommand, templateInstallArgs, { cwd: currentWorkingDirectory, stdio: 'ignore', }) try { await Promise.all( templatePackages.map((packageName) => { const packagePath = getPackageInNodeModulesDir(currentWorkingDirectory, packageName) return checkFileExists(join(packagePath, 'package.json')) }), ) } catch (error) { throw new Error(`Template download failed`) } } interface CopyTemplateFilesParams { currentWorkingDirectory?: string pluginTemplatePackage?: string abilityTemplatePackage?: string } async function copyTemplateFiles({ currentWorkingDirectory = cwd(), pluginTemplatePackage = DEFAULT_TEMPLATE_PACKAGE_NAME, }: CopyTemplateFilesParams) { const templatePackagePath = getPackageInNodeModulesDir( currentWorkingDirectory, pluginTemplatePackage, ) const templateJson = join(templatePackagePath, 'template.json') const templateFolder = join(templatePackagePath, 'template') await copy(templateFolder, currentWorkingDirectory) await copy(templateJson, join(currentWorkingDirectory, 'template.json')) } async function processTemplateFiles(currentWorkingDirectory: string) { const BACKEND_DIR_PATH = join(currentWorkingDirectory, BACKEND_DIR_NAME) const WEB_DIR_PATH = join(currentWorkingDirectory, WEB_DIR_NAME) await Promise.all([ renameHiddenTemplateFiles(currentWorkingDirectory), renameHiddenTemplateFiles(BACKEND_DIR_PATH), renameHiddenTemplateFiles(WEB_DIR_PATH), ]) const templateJsonPath = join(currentWorkingDirectory, 'template.json') const packageJsonPath = join(currentWorkingDirectory, 'package.json') await mergeTemplateJsonIntoPackageJson(templateJsonPath, packageJsonPath) // Remove template.json after merge await remove(templateJsonPath) } async function updatePackageJsonFields( currentWorkingDirectory: string, fields: Record, ) { const packageJsonPath = join(currentWorkingDirectory, 'package.json') await mergeFieldsIntoPackageJson(packageJsonPath, fields) } async function initializationPluginConfig( currentWorkingDirectory: string, task: Listr.ListrTaskWrapper, initialParams: Required[1]>, ) { const pluginConfigPath = join(currentWorkingDirectory, PLUGIN_CONFIG_RELATIVE_PATH) const pluginConfigContent = await getYamlConfigContent(pluginConfigPath) const pluginAppID = pluginConfigContent?.service?.app_id if (pluginAppID) { task.skip(`${pluginConfigPath} already initialization`) return Promise.resolve() } await initPluginYaml(pluginConfigPath, initialParams) } async function initializationLocalConfig(currentWorkingDirectory: string) { const localConfigPath = join(currentWorkingDirectory, LOCAL_CONFIG_RELATIVE_PATH) // Force create local config file for now await initProjectLocalYaml(localConfigPath) } function installWebAndBackendNpmDependencies(currentWorkingDirectory: string) { const BACKEND_DIR_PATH = join(currentWorkingDirectory, BACKEND_DIR_NAME) const WEB_DIR_PATH = join(currentWorkingDirectory, WEB_DIR_NAME) const installCommand = getNpm() const installArgs = ['install', '--force'] const dirs = [BACKEND_DIR_PATH, WEB_DIR_PATH] const subTasks: ListrTask[] = dirs.map((dir) => ({ title: `Installing dependencies on ${dir}`, task: async () => { await checkFileExists(dir) return execa(installCommand, installArgs, { cwd: dir }) }, })) return new Listr(subTasks, { concurrent: true, exitOnError: false }) } function getPluginProjectInitTasks({ currentWorkingDirectory = cwd() }) { const pluginProjectInitTasks: ListrTask[] = [ { title: 'Create global Plugin CLI config', skip: async () => { const isConfigExists = await checkFileExists(GLOBAL_CLI_PLUGIN_CONFIG_FILE_PATH, { ignoreError: true, }) if (isConfigExists) return 'cli-plugin.yaml already exists' }, task: () => initGlobalPluginConfig(), }, { title: 'Config initialization: config/local.yaml', skip: async () => { const isConfigExists = await checkFileExists( join(currentWorkingDirectory, LOCAL_CONFIG_RELATIVE_PATH), { ignoreError: true, }, ) if (isConfigExists) return 'config/local.yaml already exists' }, task: () => initializationLocalConfig(currentWorkingDirectory), }, { title: 'Install project dependencies', skip: async () => await hasWorkspacesInPackageJson(), task: () => installWebAndBackendNpmDependencies(currentWorkingDirectory), }, ] return pluginProjectInitTasks } function getPluginProjectCreatingTasks({ currentWorkingDirectory = cwd(), projectCreatingParams, options, }: CreatePluginProjectParams) { const { name: projectName, description: projectDescription } = projectCreatingParams const { installLocalDependencies, pluginType, policy } = options const taskForProjectCreating: ListrTask[] = [ { title: 'Validate project files', task: () => checkProjectFilesExists(currentWorkingDirectory, 'package.json'), }, { title: 'Download project template', task: () => downloadProjectTemplate(currentWorkingDirectory, installLocalDependencies), }, { title: 'Copy template files', task: () => copyTemplateFiles({ currentWorkingDirectory }), }, { title: 'Process template files', task: () => processTemplateFiles(currentWorkingDirectory), }, { title: 'Config initialization: config/plugin.yaml', task: (ctx, task) => { const pluginConfigInitialParams = { name: projectName, description: projectDescription, pluginType, policy, appID: projectCreatingParams.appID, } return initializationPluginConfig(currentWorkingDirectory, task, pluginConfigInitialParams) }, }, { title: 'Generate op.config.mjs', task: () => { return generateOpConfigFile(options, currentWorkingDirectory) }, }, { title: 'Modify project package.json', task: () => updatePackageJsonFields(currentWorkingDirectory, { name: projectName, description: projectDescription, workspaces: [WEB_DIR_NAME, BACKEND_DIR_NAME], }), }, { title: 'Create project README.md', task: () => createReadmeContent(currentWorkingDirectory, projectName), }, ...getPluginProjectInitTasks({ currentWorkingDirectory }), { title: 'Install project dependencies', task: () => installProjectNpmDependencies(currentWorkingDirectory), }, ] return taskForProjectCreating } async function init() { try { const currentWorkingDirectory = cwd() const isOnProjectRoot = await checkProjectFilesExists(currentWorkingDirectory, [ BACKEND_DIR_NAME, WEB_DIR_NAME, 'package.json', ]) if (!isOnProjectRoot) throw new Error(`please run this command on project root`) const pluginProjectInitTasks = getPluginProjectInitTasks({ currentWorkingDirectory }) const initProject = new Listr(pluginProjectInitTasks, { exitOnError: false }) await initProject.run() } catch (error) { output.error(error, 'Project initialization') } } export { init, getPluginProjectCreatingTasks, getPluginProjectInitTasks }