import { getCheckAbilityRelateModuleTask } from '@/scripts/relate-module' import { getPluginConfigContent } from '@/scripts/shared' import { getUpdateMinSystemVersionTask } from '@/scripts/shared' import { BACKEND_DIR_DIST_PATH, RELATIVE_FILE_PATH_FOR_PACK_UP, RELATIVE_FILE_PATH_FOR_VALIDATE, RELATIVE_OPTIONAL_FILE_PATH_FOR_PACK_UP, WEB_DIR_DIST_PATH, BACKEND_DIR_NAME, BACKEND_DEPENDENCIES_DIR_NAME, batchValidateAbilityConstraints, MANIFEST_FILE_NAME, } from '@ones-open/cli-utils' import { getNpm, checkProjectFilesExists, getPluginAbilityTypes, getPluginModuleTypes, } from '@ones-open/cli-utils' import type { ManifestSchema, ProjectPluginConfig } from '@ones-open/cli-utils' import { batchValidateModuleConstraints } from '@ones-open/cli-utils' import { isString } from '@senojs/lodash' import { execa } from 'execa' import fse from 'fs-extra' import { dump } from 'js-yaml' import type { ListrTask } from 'listr' import { join, sep } from 'path' import tar from 'tar' import { hasWorkspacesInPackageJson } from '../check-workspaces' import { wrapperActionWithDevAppID } from '../shared/dev-app-id' import { buildPluginProjectBackEnd } from './backend' import { buildPluginProjectFrontEnd } from './frontend' import { generateManifestContent } from './manifest' import { cwd } from 'process' const { remove, existsSync, copy } = fse function cleanProjectDistDirs(currentWorkingDirectory = cwd()) { const cleanTasks = [WEB_DIR_DIST_PATH, BACKEND_DIR_DIST_PATH].map((relativePath) => { const absolutePath = join(currentWorkingDirectory, relativePath) return remove(absolutePath) }) return Promise.all(cleanTasks) } async function checkFileDependenciesBeforeProjectPackUp(currentWorkingDirectory = cwd()) { await checkProjectFilesExists(currentWorkingDirectory, RELATIVE_FILE_PATH_FOR_VALIDATE) return true } async function installNpmDependencies(path: string) { const installCommand = getNpm() const installArgs = ['ci', '--production'] await execa(installCommand, installArgs, { cwd: path }) } async function buildOpkFile( outputFileName: string, extraFilePaths?: string[], currentWorkingDirectory = cwd(), ) { const packupPath = extraFilePaths?.length ? [...RELATIVE_FILE_PATH_FOR_PACK_UP, ...extraFilePaths] : RELATIVE_FILE_PATH_FOR_PACK_UP if (!(await hasWorkspacesInPackageJson())) { await tar.c( { gzip: true, file: outputFileName, }, packupPath, ) return outputFileName } const tempPath = join(currentWorkingDirectory, 'temp') // remove temp if (existsSync(tempPath)) { await remove(tempPath) } for (const item of packupPath) { if (item !== BACKEND_DEPENDENCIES_DIR_NAME) { await copy(join(currentWorkingDirectory, item), join(tempPath, item)) continue } const BACKEND_PATH = join(currentWorkingDirectory, BACKEND_DIR_NAME) const BACKEND_PACKAGE_JSON_PATH = join(BACKEND_PATH, 'package.json') const BACKEND_NPMRC_PATH = join(BACKEND_PATH, '.npmrc') const PACKAGE_LOCK_JSON_PATH = join(currentWorkingDirectory, 'package-lock.json') const TEMP_BACKEND_PATH = join(tempPath, BACKEND_DIR_NAME) const TEMP_BACKEND_PACKAGE_JSON_PATH = join(TEMP_BACKEND_PATH, 'package.json') const TEMP_BACKEND_NPMRC_PATH = join(TEMP_BACKEND_PATH, '.npmrc') const TEMP_BACKEND_PACKAGE_LOCK_JSON_PATH = join(TEMP_BACKEND_PATH, 'package-lock.json') await copy(BACKEND_PACKAGE_JSON_PATH, TEMP_BACKEND_PACKAGE_JSON_PATH) await copy(BACKEND_NPMRC_PATH, TEMP_BACKEND_NPMRC_PATH) await copy(PACKAGE_LOCK_JSON_PATH, TEMP_BACKEND_PACKAGE_LOCK_JSON_PATH) await installNpmDependencies(TEMP_BACKEND_PATH) } await tar.c( { gzip: true, file: outputFileName, cwd: tempPath, }, packupPath, ) await remove(tempPath) return outputFileName } function getPluginProjectPackUpTasks(fileName?: string, isReleaseVersion?: boolean) { const packupTasks: ListrTask<{ extraFilePaths: string[] pluginConfig: ProjectPluginConfig opkFileName: string manifest: { content: ManifestSchema filePath: string } }>[] = [ { title: 'Validating project directory structure or files status', task: async (ctx) => { const currentWorkingDirectory = cwd() await checkFileDependenciesBeforeProjectPackUp(currentWorkingDirectory) const optionalPathCheckResults = await Promise.all( RELATIVE_OPTIONAL_FILE_PATH_FOR_PACK_UP.map((relativePath) => checkProjectFilesExists(currentWorkingDirectory, relativePath, { ignoreError: true }), ), ) const extraFilePaths = RELATIVE_OPTIONAL_FILE_PATH_FOR_PACK_UP.filter( (_p, pIndex) => optionalPathCheckResults[pIndex], ) ctx.extraFilePaths = extraFilePaths }, }, { title: 'Checking constraints', task: async (ctx) => { const pluginConfig = await getPluginConfigContent() ctx.pluginConfig = pluginConfig const validationCtx = { pluginConfig, mode: 'build' as const, } const [, moduleValidationError] = await batchValidateModuleConstraints( getPluginModuleTypes(pluginConfig), validationCtx, ) const [, abilityValidationError] = await batchValidateAbilityConstraints( getPluginAbilityTypes(pluginConfig), validationCtx, ) if (moduleValidationError || abilityValidationError) { const errorMessage = [ 'Constraints validation failed:\n', moduleValidationError, abilityValidationError, ] .filter((item) => !!item) .join('\n') .concat('\n\nPlease check config/plugin.yaml') throw new Error(errorMessage) } }, }, { title: 'check event', task: async (ctx) => { const pluginConfig = ctx.pluginConfig ?? (await getPluginConfigContent()) const { events = [] } = pluginConfig const eventTypes = events.map((event) => event.eventType) const existedEventTypeSet = new Set() for (const eventType of eventTypes) { if (existedEventTypeSet.has(eventType)) { throw new Error(`Duplicate event type: ${eventType}`) } existedEventTypeSet.add(eventType) } }, }, getCheckAbilityRelateModuleTask(), getUpdateMinSystemVersionTask(), { title: 'Cleaning web & backend dist folders', task: () => cleanProjectDistDirs(), }, { title: 'Building project Back-End dist files', task: () => buildPluginProjectBackEnd(), }, { title: 'Building project Front-End dist files', task: () => buildPluginProjectFrontEnd(), }, { title: 'Generate manifest.yaml', task: async (ctx) => { const content = await generateManifestContent() const filePath = MANIFEST_FILE_NAME await fse.outputFile(filePath, dump(content), 'utf-8') ctx.manifest = { content, filePath, } ctx.extraFilePaths.push(filePath) }, }, { title: 'Packing up project to `.opk` file', task: async (ctx) => { const { pluginConfig, extraFilePaths, manifest: { filePath: manifestFilePath }, } = ctx let outputFileName = '' const trimmedFileName = fileName?.trim() if (isString(trimmedFileName) && trimmedFileName.length > 0) { outputFileName = trimmedFileName } else { const { service: { name: rawPluginName }, } = pluginConfig const cwdName = cwd().split(sep).pop() || JSON.stringify(rawPluginName) const pluginName = isString(rawPluginName) ? rawPluginName : cwdName outputFileName = pluginName } outputFileName = `${outputFileName}.opk` await wrapperActionWithDevAppID(async () => { const opkFileName = await buildOpkFile(outputFileName, extraFilePaths) await remove(manifestFilePath) ctx.opkFileName = opkFileName }, !!isReleaseVersion) }, }, ] return packupTasks } export * from './backend' export * from './frontend' export { buildOpkFile, cleanProjectDistDirs, checkFileDependenciesBeforeProjectPackUp, getPluginProjectPackUpTasks, }