import path from 'path' import isPlainObject from 'lodash/isPlainObject' import { PackageJson } from 'type-fest' import { DefaultPcTheme, DefaultConePlatformTheme, DefaultMobileTheme, } from './theme' import { pathExists, readdir, stat } from '../fs' import { loadJson } from '../json' import { formatJson } from '../prettier' import { getPackageVersion } from '../tnpm' import { Obj, ProjectFileProcessor, PkgJsonPatch } from '../type' import log from '../log' import { getNodeModulesPkgFilepath, loadNodeModulesPkgJson, } from '../package-json' const prefix = 'project-pkg-json' type PkgMaterialType = 'page' | 'component' export function getPkgAssetFilepath({ cwd, pkg, type, }: { cwd: string pkg: string type: PkgMaterialType }) { return getNodeModulesPkgFilepath(cwd, pkg, `src/${type}s`) } interface MaterialMeta extends Obj { name: string } async function getPkgAssetMeta({ pkg, version, materialDir, dir, }: { pkg: string version?: string materialDir: string dir: string }): Promise { const dirpath = path.join(materialDir, dir) const stats = await stat(dirpath) let meta = null if (stats.isDirectory()) { const metaExists = await pathExists(path.join(dirpath, 'meta.json')) if (metaExists) { meta = await loadJson(dirpath, 'meta.json') const lowcode = await pathExists(path.join(dirpath, 'schema.json')) return { title: '', thumbnail: '', author: '', // 开发者定义的 meta 放在最前面, 他们定义的值有可能是不对的, 甚至是跟标准冲突的 ...meta, // 跟 vscode 插件约定的字段名是 packageName packageName: pkg, pkg, name: dir, version, // for inquirer value: dirpath, lowcode, } } } return meta } export async function getPkgAsset({ cwd, pkg, type, dir, }: { cwd: string pkg: string type: PkgMaterialType dir: string }) { const { version } = await loadNodeModulesPkgJson(cwd, pkg) const materialDir = getPkgAssetFilepath({ cwd, pkg, type }) return getPkgAssetMeta({ pkg, version, materialDir, dir }) } export async function getPkgAssets({ cwd, pkg, type, }: { cwd: string pkg: string type: PkgMaterialType }) { const { version } = await loadNodeModulesPkgJson(cwd, pkg) const materialDir = getPkgAssetFilepath({ cwd, pkg, type }) const files = await readdir(materialDir) const arr: MaterialMeta[] = [] await Promise.all( files.map(async (dir) => { const meta = await getPkgAssetMeta({ pkg, version, materialDir, dir, }) meta && arr.push(meta) }), ) arr.sort((a, b) => { return a.name < b.name ? -1 : 1 }) return arr } export async function patchPackageJson({ obj, pkgJsonPatch, depsMap, deps, devDeps, }: { obj: PackageJson pkgJsonPatch: PkgJsonPatch depsMap: { dependencies: PackageJson.Dependency devDependencies: PackageJson.Dependency } deps: (string | string[] | undefined)[] devDeps: (string | string[] | undefined)[] }) { async function updateDeps( names: (string | string[] | undefined)[], isDev: boolean, ) { await Promise.all( names.map(async (pkgs) => { if (!pkgs) { return } const arr: string[] = Array.isArray(pkgs) ? pkgs : pkgs.split(',') await Promise.all( arr.map(async (name) => { if (!name) { return } let version = await getPackageVersion(name) if (!version) { return } version = '^' + version const key = isDev ? 'devDependencies' : 'dependencies' if (depsMap[key]) { depsMap[key][name] = version } }), ) }), ) } await updateDeps(deps, false) await updateDeps(devDeps, true) // sort deps ;(['dependencies', 'devDependencies'] as const).forEach((key) => { // merge deps const unsorted = depsMap[key] const patchKey = key === 'dependencies' ? 'deps' : 'devDeps' if (pkgJsonPatch && isPlainObject(pkgJsonPatch[patchKey])) { Object.assign(unsorted, pkgJsonPatch[patchKey]) } // sort deps const sorted: typeof unsorted = {} Object.keys(unsorted) .sort((a, b) => (a < b ? -1 : 1)) .forEach((pkg) => { sorted[pkg] = unsorted[pkg] }) depsMap[key] = sorted }) obj.devDependencies = depsMap.devDependencies obj.dependencies = depsMap.dependencies // scripts if (pkgJsonPatch?.scripts) { Object.assign(obj.scripts, pkgJsonPatch.scripts) } } export const updatePackageJson: ProjectFileProcessor< PackageJson & Obj > = async ({ obj, props, pkgJsonPatch }) => { pkgJsonPatch = pkgJsonPatch || {} const { mobile, oneCode, lowcode, useArms, layoutPackageName, themePackageName, buildPluginPackageName, coneCliPluginPackageName, coneMaterialTemplatePackageName, } = props // deps const depsMap: { dependencies: PackageJson.Dependency devDependencies: PackageJson.Dependency } = { dependencies: obj.dependencies || {}, devDependencies: obj.devDependencies || {}, } // next/meet if (oneCode) { Object.assign(depsMap.dependencies, { 'cn-meet-react': '^2.x', 'cn-next': '^1.x', }) } else if (mobile) { Object.assign(depsMap.dependencies, { 'cn-meet-react': '^2.x' }) } else { // Object.assign(depsMap.dependencies, { 'cn-next': '^1.x' }) } // lowcode if (lowcode) { Object.assign(depsMap.dependencies, { '@ali/vc-coll-charts': '^1.0.12', '@ali/vu-actions': '^1.4.8', '@ali/vu-dataSource': '1.0.3', '@ali/vu-formatter': '2.0.0', '@ali/vu-fusion': '2.0.0', '@ali/vu-legao-builtin': '1.0.6', '@ali/vu-toolkit': '1.0.4', '@alife/cone-render-engine': '^0.1.0', '@alife/toffee-api': '0.0.3', '@alife/toffee-router': '0.0.6', }) // project lowcode components 所需要的低码 prototype 依赖 Object.assign(depsMap.devDependencies, { '@ali/visualengine': '^5.4.1', '@ali/visualengine-utils': '^5.1.6', '@ali/vu-events-property': '^3.0.2', '@ali/vu-style-property': '^3.0.0', '@ali/vu-uuid-property': '^1.2.1', }) if (oneCode) { Object.assign(depsMap.dependencies, { '@alife/tc-base': '^1.0.0', '@alife/tc-meet': '^0.1.1', '@alife/tc-fusion': '^1.0.0', }) } else if (mobile) { Object.assign(depsMap.dependencies, { '@alife/tc-base': '^1.0.0', '@alife/tc-meet': '^0.1.1', }) } else { Object.assign(depsMap.dependencies, { '@alife/tc-base': '^1.0.0', '@alife/tc-fusion': '^1.0.0', }) } } if (useArms) { Object.assign(depsMap.dependencies, { '@alife/cone-arms': '^0.1.0' }) } // un-decided deps // theme const pcThemePkgName = !mobile || oneCode ? themePackageName : '' let mobileThemePkgName = '' if (oneCode) { // 一码多端暂未开放配置移动端主题的入口 mobileThemePkgName = DefaultMobileTheme } else if (mobile) { mobileThemePkgName = themePackageName === DefaultPcTheme || themePackageName === DefaultConePlatformTheme ? DefaultMobileTheme : themePackageName || '' } if (pcThemePkgName) { log.info('theme', 'pc', pcThemePkgName) } if (mobileThemePkgName) { log.info('theme', 'mobile', mobileThemePkgName) } const deps = [layoutPackageName, pcThemePkgName, mobileThemePkgName] if (Array.isArray(pkgJsonPatch.deps)) { deps.push(...pkgJsonPatch.deps) } const devDeps = [ buildPluginPackageName, coneCliPluginPackageName, coneMaterialTemplatePackageName, ] if (Array.isArray(pkgJsonPatch.devDeps)) { devDeps.push(...pkgJsonPatch.devDeps) } await patchPackageJson({ obj, pkgJsonPatch, depsMap, deps, devDeps, }) return obj }