oak-cli/src/template.ts

744 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { execSync } from 'child_process';
import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { PackageJsonInput } from './interface';
import { transformFileSync } from '@babel/core';
import * as t from '@babel/types';
import generate from "@babel/generator";
import assert from 'assert';
/**
* 利用npm info获得相应库的最新版本
* @param name
* @returns
*/
function getPackageLatestVersion(name: string) {
const result = execSync(`npm info ${name}`).toString('utf-8');
const data = result.match(/latest: \d+\.\d+\.\d+\s*published/g);
const versionStr = data![0];
const version = versionStr.slice(8, versionStr.indexOf('published')).replace(/[^0-9.]/g, '');
return version;
}
export function packageJsonContent({
name,
version,
description,
cliName,
cliBinName,
isDev,
isModule,
dependencies
}: PackageJsonInput) {
let oakDependencyStr = '';
let oakDevDependencyStr = '';
if (isDev) {
oakDependencyStr = `
"oak-domain": "file:../oak-domain",
"oak-external-sdk": "file:../oak-external-sdk",
"oak-frontend-base": "file:../oak-frontend-base",`;
if (dependencies?.length) {
dependencies?.forEach(
(dep) => {
if (existsSync(join(process.cwd(), '..', dep))) {
oakDependencyStr += `\n"${dep}": "file:../${dep}",`;
}
else {
oakDependencyStr += `\n"${dep}": "^${getPackageLatestVersion(dep)}"`;
}
}
);
}
oakDevDependencyStr = `"${cliName}": "file:../oak-cli",`
}
else {
// todo这里从npmjs.org上获取最新版本
oakDependencyStr = `
"oak-domain": "~${getPackageLatestVersion('oak-domain')}",
"oak-external-sdk": "~${getPackageLatestVersion('oak-external-sdk')}",
"oak-frontend-base": "~${getPackageLatestVersion('oak-frontend-base')}",`;
if (dependencies?.length) {
dependencies?.forEach(
(dep) => {
oakDependencyStr += `\n"${dep}": "~${getPackageLatestVersion(dep)}",`;
}
);
}
oakDevDependencyStr = `"${cliName}": "~${getPackageLatestVersion(cliName)}",`
}
const serverInitScript = isDev ? "cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node scripts/initServer.js" : "cross-env OAK_PLATFORM=server node scripts/initServer.js";
// const serverStartScript = isDev ? "cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node scripts/startServer.js" : "cross-env OAK_PLATFORM=server node scripts/startServer.js";
const serverStartWatchScript = isDev ? "cross-env ENABLE_TRACE=true cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/watchServer.js" : "cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/watchServer.js";
return `{
"name": "${name}",
"version": "${version}",
"description": "${description}",
"scripts": {
"make:domain": "${isModule
? `cross-env COMPLING_AS_LIB=yes ${cliBinName}`
: cliBinName
} make:domain",
"make:locale": "${cliBinName} make:locale ${isModule ? '-m' : ''}",
"make:dep": "${cliBinName} make:dependency",
"clean:cache": "rimraf node_modules/.cache",
"copy-config-json": "copyfiles -u 1 src/config/*.json lib/",
"start:mp": "${cliBinName} start --target mp --mode development --devMode frontend",
"start:mp:server": "${cliBinName} start --target mp --mode development",
"build:mp:staging": "${cliBinName} build --target mp --mode staging",
"build-analyze:mp:staging": "${cliBinName} build --target mp --mode staging --analyze",
"build:mp": "${cliBinName} build --target mp --mode production",
"build-analyze:mp": "${cliBinName} build --target mp --mode production --analyze",
"build:watch": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json && npm run server:start:watch",
"start:web": "${cliBinName} start --target web --mode development --devMode frontend",
"start:web:server": "${cliBinName} start --target web --mode development",
"start:native": "${cliBinName} start --target rn --mode development --devMode frontend",
"start:native:server": "${cliBinName} start --target rn --mode development",
"build:web:staging": "${cliBinName} build --target web --mode staging",
"build-sourcemap:web:staging": "${cliBinName} build --target web --mode staging --sourcemap",
"build-analyze:web:staging": "${cliBinName} build --target web --mode staging --analyze",
"build:web": "${cliBinName} build --target web --mode production",
"build-sourcemap:web": "${cliBinName} build --target web --mode production --sourcemap",
"build-analyze:web": "${cliBinName} build --target web --mode production --analyze",
"build-sourcemap-analyze:web": "${cliBinName} build --target web --mode production --sourcemap --analyze",
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json",
"prebuild": "npm run make:locale",
"run:ios": "oak-cli run -p ios",
"run:android": "oak-cli run -p android",
"server:init": "${serverInitScript}",
"server:start": "${serverStartWatchScript}",
"server:ana": "cross-env ENABLE_TRACE=true cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/analysis.js",
"postinstall": "npm run make:dep"
},
"keywords": [],
"author": "",
"license": "",
"typings": "typings/index.d.ts",
"dependencies": {
"@ant-design/cssinjs": "^1.16.2",
"@ant-design/icons": "^5.2.6",
"@bytemd/plugin-breaks": "^1.21.0",
"@bytemd/plugin-frontmatter": "^1.21.0",
"@bytemd/plugin-gemoji": "^1.21.0",
"@bytemd/plugin-gfm": "^1.21.0",
"@bytemd/plugin-highlight": "^1.21.0",
"@bytemd/plugin-math": "^1.21.0",
"@bytemd/plugin-medium-zoom": "^1.21.0",
"@bytemd/plugin-mermaid": "^1.21.0",
"@bytemd/react": "^1.21.0",
"react-beautiful-dnd": "^13.1.1",
"@react-native-async-storage/async-storage": "^1.19.8",
"@react-native-masked-view/masked-view": "^0.3.0",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/stack": "^6.3.20",
"@wangeditor/basic-modules": "^1.1.3",
"@wangeditor/editor": "^5.1.14",
"@wangeditor/editor-for-react": "^1.0.4",
"antd": "^5.8.3",
"antd-mobile": "^5.32.0",
"antd-mobile-icons": "^0.3.0",
"classnames": "^2.3.1",
"crypto-browserify": "^3.12.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.5",
"echarts": "^5.3.0",
"echarts-for-react": "^3.0.2",
"history": "^5.3.0",
"hmacsha1": "^1.0.0",
"js-base64": "^3.7.2",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
${oakDependencyStr}
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-native": "^0.75.0",
"react-native-exception-handler": "^2.10.10",
"react-native-gesture-handler": "^2.14.0",
"react-native-localize": "^3.0.4",
"react-native-quick-base64": "^2.0.7",
"react-native-quick-crypto": "^0.6.1",
"react-native-reanimated": "^3.5.4",
"react-native-safe-area-context": "^4.7.4",
"react-native-screens": "^3.27.0",
"react-native-url-polyfill": "^2.0.0",
"react-responsive": "^9.0.0-beta.10",
"react-router-dom": "^6.4.0",
"react-slick": "^0.29.0",
"rmc-pull-to-refresh": "^1.0.13",
"uuid": "^8.3.2"
},
"devDependencies": {
"@babel/cli": "^7.12.13",
"@babel/core": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.13",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.12.13",
"@babel/preset-typescript": "^7.12.13",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
"@react-native/metro-config": "^0.72.11",
"@svgr/webpack": "^5.5.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"@tsconfig/react-native": "^3.0.0",
"@types/assert": "^1.5.6",
"@types/crypto-js": "^4.1.1",
"@types/fs-extra": "^9.0.13",
"@types/isomorphic-fetch": "^0.0.36",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.179",
"@types/luxon": "^2.3.2",
"@types/mocha": "^8.2.0",
"@types/node": "^20.10.2",
"@types/nprogress": "^0.2.0",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@types/react-image-gallery": "^1.2.0",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/shelljs": "^0.8.11",
"@types/urlsafe-base64": "^1.0.28",
"@types/uuid": "^8.3.0",
"@types/wechat-miniprogram": "^3.4.0",
${oakDevDependencyStr}
"assert": "^2.0.0",
"babel-jest": "^27.4.2",
"babel-loader": "^8.2.3",
"babel-plugin-named-asset-import": "^0.3.8",
"babel-plugin-module-resolver": "^5.0.0",
"babel-preset-react-app": "^10.0.1",
"bfj": "^7.0.2",
"browserify-zlib": "^0.2.0",
"browserslist": "^4.18.1",
"camelcase": "^6.2.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"chalk": "^4.1.2",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"css-loader": "^6.6.0",
"css-minimizer-webpack-plugin": "^3.2.0",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
"dotenv-webpack": "^7.1.0",
"ensure-posix-path": "^1.1.1",
"eslint": "^8.3.0",
"eslint-config-react-app": "^7.0.1",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.0",
"globby": "^11.1.0",
"html-webpack-plugin": "^5.5.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.4.3",
"jest-resolve": "^27.4.2",
"jest-watch-typeahead": "^1.0.0",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"metro-react-native-babel-preset": "0.76.8",
"mini-css-extract-plugin": "^2.5.3",
"miniprogram-api-typings": "^3.4.5",
"mocha": "^8.2.1",
"postcss": "^8.4.4",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-less": "^6.0.0",
"postcss-loader": "^6.2.1",
"postcss-normalize": "^10.0.1",
"postcss-preset-env": "^7.0.1",
"progress": "^2.0.3",
"progress-bar-webpack-plugin": "^2.1.0",
"prompts": "^2.4.2",
"react-app-polyfill": "^3.0.0",
"react-dev-utils": "^12.0.1",
"react-refresh": "^0.11.0",
"required-path": "^1.0.1",
"resolve": "^1.20.0",
"resolve-url-loader": "^4.0.0",
"sass-loader": "^12.3.0",
"semver": "^7.3.5",
"shelljs": "^0.8.5",
"source-map-loader": "^3.0.0",
"style-loader": "^3.3.1",
"stylelint": "^14.4.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-webpack-plugin": "^3.1.1",
"tailwindcss": "^3.0.2",
"terser-webpack-plugin": "^5.2.5",
"ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"tsc-alias": "^1.8.2",
"tslib": "^2.4.0",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.1.0",
"ui-extract-webpack-plugin": "^1.0.0",
"web-vitals": "^2.1.4",
"webpack": "^5.86.0",
"webpack-dev-server": "^4.15.1",
"webpack-manifest-plugin": "^4.0.2",
"workbox-webpack-plugin": "^6.4.1",
"chokidar": "^4.0.1"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"staging": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"copyWebpack": [],
"overrides": {
"crypto-browserify": {
"readable-stream": "3.6.2"
}
},
"pnpm": {
"overrides": {
"readable-stream": "3.6.2"
}
},
"resolutions": {
"readable-stream": "3.6.2"
}
}
`;
}
export function tsConfigJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"jsx": "react-jsx",
"module": "commonjs",
"target": "es5",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"strict": true,
"importHelpers": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"plugins": [
{
"name": "typescript-plugin-css-modules",
}
],
//"outDir": "lib", /* Redirect output structure to the directory. */
//"rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"types": [
"node",
"wechat-miniprogram"
],
"resolveJsonModule": true
},
"include": [
"./src/**/*.js",
"./src/**/*.ts",
"./src/**/*.tsx",
"./web/src/**/*.ts",
"./web/src/**/*.tsx",
"./wechatMp/src/**/*.js",
"./wechatMp/src/**/*.ts",
"./typings/*.d.ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts",
"test",
"scripts",
"lib"
]
}`;
}
export function tsConfigBuildJsonContent() {
return `{
"extends": "./tsconfig.build.paths.json",
"compilerOptions": {
"jsx": "react-jsx",
"module": "commonjs",
"target": "esnext",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"strict": true,
"importHelpers": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"outDir": "lib", /* Redirect output structure to the directory. */
"rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "types": [
// "node",
// "wechat-miniprogram"
// ],
"resolveJsonModule": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts",
"test",
"src/pages/**/*",
"src/components/**/*"
]
}`;
}
export function tsConfigPathsJsonContent(deps: string[]) {
const paths = {
"@project/*": [
"src/*"
],
"@oak-app-domain": [
"src/oak-app-domain/index"
],
"@oak-app-domain/*": [
"src/oak-app-domain/*"
],
"@oak-frontend-base/*": [
"node_modules/oak-frontend-base/lib/*"
],
};
deps.forEach(
(ele) => {
Object.assign(paths, {
[`@${ele}/*`]: [`node_modules/${ele}/lib/*`],
});
}
);
return JSON.stringify({
compilerOptions: {
baseUrl: "./",
paths,
typeRoots: ["./typings"]
}
}, null, '\t');
}
export function tsConfigMpJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"module": "ESNext",
"target": "ESNext",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"strict": true,
"downlevelIteration": true,
"importHelpers": true,
"moduleResolution": "Node",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
// "outDir": "lib", /* Redirect output structure to the directory. */
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"types": [
"node",
"wechat-miniprogram"
],
"resolveJsonModule": true,
"jsx": "react"
},
"include": [
"./src/**/*.js",
"./src/**/*.ts",
"./wechatMp/src/**/*.js",
"./wechatMp/src/**/*.ts",
"./typings/*.d.ts"
],
"exclude": [
"node_modules",
"scripts",
"test",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.test.tsx",
"./web",
"./native"
]
}`;
}
export function tsConfigWebJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"module": "ESNext",
"target": "ESNext",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"importHelpers": true,
"strict": true,
"moduleResolution": "Node",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
// "outDir": "lib", /* Redirect output structure to the directory. */
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"types": [
"node",
"wechat-miniprogram",
"react"
],
"resolveJsonModule": true,
"jsx": "react"
},
"include": [
"./src/**/*.js",
"./src/**/*.ts",
"./src/**/*.tsx",
"./web/src/**/*.js",
"./web/src/**/*.ts",
"./web/src/**/*.tsx",
"./typings/*.d.ts"
],
"exclude": [
"node_modules",
"scripts",
"test",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.test.tsx",
"./wechatMp",
"./native"
]
}`;
}
export function projectConfigContentWithWeChatMp(
oakConfigName: string,
projectname: string,
miniVersion: string
) {
return `{
"description": "项目配置文件",
"packOptions": {
"ignore": [{
"type": "file",
"value": "${oakConfigName}"
}]
},
"setting": {
"urlCheck": true,
"es6": true,
"enhance": true,
"postcss": true,
"preloadBackgroundData": false,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": true,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useIsolateContext": true,
"useCompilerModule": false,
"userConfirmedUseCompilerModuleSwitch": false
},
"compileType": "miniprogram",
"libVersion": "${miniVersion}",
"appid": "wx2f8e15297d3e032c",
"projectname": "${projectname}",
"debugOptions": {
"hidedInDevtools": []
},
"scripts": "",
"isGameTourist": false,
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {
"search": {
"current": -1,
"list": []
},
"conversation": {
"current": -1,
"list": []
},
"game": {
"current": -1,
"list": []
},
"plugin": {
"current": -1,
"list": []
},
"gamePlugin": {
"current": -1,
"list": []
},
"miniprogram": {
"current": -1,
"list": []
}
}
}`;
}
export function appJsonContentWithWeChatMp(isDev: boolean) {
const pages = [
'@project/pages/store/list/index',
'@project/pages/store/upsert/index',
'@project/pages/store/detail/index',
];
return `{
"pages":${JSON.stringify(pages, null, 4)},
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle":"black",
"enablePullDownRefresh": true
},
"usingComponents": {
"oak-message": "@oak-frontend-base/components/message/index",
"oak-debugPanel": "@oak-frontend-base/components/func/debugPanel/index"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}`;
}
export function oakConfigContentWithWeChatMp() {
return `{
"theme": {
}
}`;
}
export function oakConfigContentWithWeb() {
return `{
"theme": {
}
}`;
}
export function updateCompilerJsContent(directory: string, deps: string[]) {
const compilerJsPath = join(directory, 'configuration', 'compiler.js');
assert(existsSync(compilerJsPath));
if (deps.length > 0) {
const { ast } = transformFileSync(compilerJsPath, { ast: true })!;
const { program } = ast!;
const { body } = program!;
const moduleExportStmt = body[body.length - 1];
/**
* 导出语句:
module.exports = CreateCompilerConfig({
webpack: {
extraOakModules: [],
resolve: {
alias: {
'@oak-frontend-base': oakFrontendBasePath,
'@project': path.resolve('src'),
}
}
}
});
*/
assert(t.isExpressionStatement(moduleExportStmt));
const { expression } = moduleExportStmt;
assert(t.isAssignmentExpression(expression));
const { left, right } = expression;
assert(t.isMemberExpression(left));
const { property, object } = left;
assert(t.isIdentifier(object) && object.name === 'module');
assert(t.isIdentifier(property) && property.name === 'exports');
assert(t.isCallExpression(right));
const [webPackExpr] = right.arguments;
assert(t.isObjectExpression(webPackExpr));
const [ webPackProp ] = webPackExpr.properties;
assert(t.isObjectProperty(webPackProp));
const { key, value } = webPackProp;
assert(t.isIdentifier(key) && key.name === 'webpack');
const [ modulesProp, resolveProp ] = (<t.ObjectExpression>value).properties;
assert(t.isObjectProperty(modulesProp));
const { value: value2 } = modulesProp;
assert(t.isArrayExpression(value2));
value2.elements.push(
...deps.map(
(ele) => t.regExpLiteral(ele)
)
);
assert(t.isObjectProperty(resolveProp));
const { value: value3 } = resolveProp;
assert(t.isObjectExpression(value3));
const [ aliasProp ] = value3.properties;
assert(t.isObjectProperty(aliasProp));
const { value: value4 } = aliasProp;
assert(t.isObjectExpression(value4));
const { properties: aliasProperties } = value4;
assert(aliasProperties.length === 2); // @project和@oak-frontend-base
aliasProperties.push(
...deps.map(
(ele) => t.objectProperty(
t.stringLiteral(`@${ele}`),
t.callExpression(
t.memberExpression(
t.identifier('path'),
t.identifier('resolve')
),
[
t.stringLiteral(`node_modules/${ele}/es`)
]
)
)
)
);
const { code } = generate(ast!);
writeFileSync(compilerJsPath, code);
}
}