tachybase_todo/packages/core/cli/src/util.js
chenos 7779cd79ac
refactor: optimize the command line (#3339)
* fix: perform load action on boot main app

* feat: add dataType option in collection duplicator

* chore: reset optional dumpable config

* chore: dump command

* chore: dump & restore command

* chore: delay restore

* fix: dump test

* chore: restore command

* chore: dump command action

* chore: dumpable collection api

* chore: client collection option

* feat: backup& restore client

* chore: content disposition header in dump response

* chore: download backup field

* feat: collection origin option

* fix: test

* chore: collection manager collection origin

* chore: upload  backup field

* chore: upload restore file

* chore: upload restore file

* fix: test

* chore: backup and restore support learn more

* refactor: upload restore file

* refactor: upload restore file

* fix: test

* fix: test

* chore: dumpable collection with title

* chore: pg only test

* chore: test

* fix: test

* chore: test sleep

* style: locale improve

* refactor: download backup file

* refactor: start restore

* fix: restore key name

* refactor: start restore

* refactor: start restore

* refactor: start restore

* refactor: start restore

* refactor: start restore

* refactor: start restore

* chore: unify duplicator option

* fix: dump empty collection

* chore: test

* chore: test

* style: style improve

* refactor: locale improve

* chore: dumpalbe collection orders

* style: style improve

* style: style improve

* style: icon adjust

* chore: nginx body size

* chore: get file status

* feat: run dump task

* feat: download api

* chore: backup files resourcer

* feat: restore destroy api

* chore: backup files resoucer

* feat: list backup files action

* chore: get collection meta from dumped file

* fix: dump file name

* fix: test

* chore: backup and restore ui

* chore: swagger api for backup & restore

* chore: api doc

* chore: api doc

* chore: api doc

* chore: backup and restore ui

* chore: backup and restore ui

* chore: backup and restore ui

* chore: backup and restore ui

* chore: backup and restore ui

* fix: restore values

* style: style improve

* fix: download field respontype

* fix: restore form local file

* refactor: local improve

* refactor: delete backup file

* fix: in progress status

* refactor: locale improve

* refactor: locale improve

* refactor: style improve

* refactor: style improve

* refactor: style improve

* test: dump collection table attribute

* chore: dump collection with table attributes

* chore: test

* chore: create new table in restore

* fix: import error

* chore: restore table from backup file

* chore: sync collection after restore collections

* fix: restore json data

* style: style improve

* chore: restore with fields

* chore: test

* fix: test

* fix: test with underscored

* style: style improve

* fix: lock file state

* chore: add test file

* refactor: backup & restore plugin

* fix: mysql test

* chore: skip import view collection

* chore: restore collection with inherits topo order

* fix: import

* style: style improve

* fix: restore sequence fields

* fix: themeConfig collection duplicator option

* fix: restore with dialectOnly meta

* fix: throw error

* fix: restore

* fix: import backup file created in postgres into mysql

* fix: repeated items in inherits

* chore: upgrade after restore

* feat: check database env before restore

* feat: handle autoincr val in postgres

* chore: sqlite & mysql queryInterface

* chore: test

* fix: test

* chore: test

* fix: build

* fix: pg test

* fix: restore with date field

* chore: theme-config collection

* chore: chage import collections method to support collection origin

* chore: fallback get autoincr value in mysql

* fix: dataType normalize

* chore: delay restore

* chore: test

* fix: build

* feat: collectin onDump

* feat: collection onDump interface

* chore: dump with view collection

* chore: sync in restore

* refactor: locale improve

* refactor: code improve

* fix: test

* fix: data sync

* chore: rename backup & restore plugin

* chore: skip test

* style: style improve

* style: style improve

* style: style improve

* style: style improve

* chore: import version check

* chore: backup file dir

* chore: build

* fix: bugs

* fix: error

* fix: pageSize

* fix: import origin

* fix: improve code

* fix: remove namespace

* chore: dump rules config

* fix: dump custom collection

* chore: version

* fix: test

* fix: test

* fix: test

* fix: test

* chore: test

* fix: load custom collection

* fix: client

* fix: translation

* chore: code

* fix: bug

* fix:  support shared option

* fix: roles collection dumpRules

* chore: test

* fix: define collections

* chore: collection group

* fix: translation

* fix: translation

* fix: restore options

* chore: restore command

* refactor: optimize the command line

* chore: dump error

* fix: test error

* fix:  test error

* fix: test error

* fix: test error

* fix: test error

* fix: skip cli test cases

* fix: test error

* fix: too many open files

* fix: update migration version

* fix: migrations

* fix: upgrade

* fix: error

* fix: migration error

* fix: upgrade

* fix: test error

* fix: timeout

* fix: width

* feat: auto load collections

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: test error

* fix: ipc error

* fix: test error

---------

Co-authored-by: Chareice <chareice@live.com>
Co-authored-by: katherinehhh <katherine_15995@163.com>
2024-01-08 19:05:14 +08:00

318 lines
8.8 KiB
JavaScript

const net = require('net');
const chalk = require('chalk');
const execa = require('execa');
const fg = require('fast-glob');
const { dirname, join, resolve, sep } = require('path');
const { readFile, writeFile } = require('fs').promises;
const { existsSync, mkdirSync, cpSync, writeFileSync } = require('fs');
const dotenv = require('dotenv');
const fs = require('fs');
exports.isPackageValid = (pkg) => {
try {
require.resolve(pkg);
return true;
} catch (error) {
return false;
}
};
exports.hasCorePackages = () => {
const coreDir = resolve(process.cwd(), 'packages/core/build');
return existsSync(coreDir);
};
exports.hasTsNode = () => {
return exports.isPackageValid('ts-node/dist/bin');
};
exports.isDev = function isDev() {
if (process.env.APP_ENV === 'production') {
return false;
}
return exports.hasTsNode();
};
const isProd = () => {
const { APP_PACKAGE_ROOT } = process.env;
const file = `${APP_PACKAGE_ROOT}/lib/index.js`;
if (!existsSync(resolve(process.cwd(), file))) {
console.log('For production environment, please build the code first.');
console.log();
console.log(chalk.yellow('$ yarn build'));
console.log();
process.exit(1);
}
return true;
};
exports.isProd = isProd;
exports.nodeCheck = () => {
if (!exports.hasTsNode()) {
console.log('Please install all dependencies');
console.log(chalk.yellow('$ yarn install'));
process.exit(1);
}
};
exports.run = (command, args, options = {}) => {
if (command === 'tsx') {
command = 'node';
args = ['./node_modules/tsx/dist/cli.mjs'].concat(args || []);
}
return execa(command, args, {
shell: true,
stdio: 'inherit',
...options,
env: {
...process.env,
...options.env,
},
});
};
exports.isPortReachable = async (port, { timeout = 1000, host } = {}) => {
const promise = new Promise((resolve, reject) => {
const socket = new net.Socket();
const onError = () => {
socket.destroy();
reject();
};
socket.setTimeout(timeout);
socket.once('error', onError);
socket.once('timeout', onError);
socket.connect(port, host, () => {
socket.end();
resolve();
});
});
try {
await promise;
return true;
} catch (_) {
return false;
}
};
exports.postCheck = async (opts) => {
const port = opts.port || process.env.APP_PORT;
const result = await exports.isPortReachable(port);
if (result) {
console.error(chalk.red(`post already in use ${port}`));
process.exit(1);
}
};
exports.runInstall = async () => {
const { APP_PACKAGE_ROOT, SERVER_TSCONFIG_PATH } = process.env;
if (exports.isDev()) {
const argv = [
'--tsconfig',
SERVER_TSCONFIG_PATH,
'-r',
'tsconfig-paths/register',
`${APP_PACKAGE_ROOT}/src/index.ts`,
'install',
'-s',
];
await exports.run('tsx', argv);
} else if (isProd()) {
const file = `${APP_PACKAGE_ROOT}/lib/index.js`;
const argv = [file, 'install', '-s'];
await exports.run('node', argv);
}
};
exports.runAppCommand = async (command, args = []) => {
const { APP_PACKAGE_ROOT, SERVER_TSCONFIG_PATH } = process.env;
if (exports.isDev()) {
const argv = [
'--tsconfig',
SERVER_TSCONFIG_PATH,
'-r',
'tsconfig-paths/register',
`${APP_PACKAGE_ROOT}/src/index.ts`,
command,
...args,
];
await exports.run('tsx', argv);
} else if (isProd()) {
const argv = [`${APP_PACKAGE_ROOT}/lib/index.js`, command, ...args];
await exports.run('node', argv);
}
};
exports.promptForTs = () => {
console.log(chalk.green('WAIT: ') + 'TypeScript compiling...');
};
exports.updateJsonFile = async (target, fn) => {
const content = await readFile(target, 'utf-8');
const json = JSON.parse(content);
await writeFile(target, JSON.stringify(fn(json), null, 2), 'utf-8');
};
exports.getVersion = async () => {
const { stdout } = await execa('npm', ['v', '@nocobase/app-server', 'versions']);
const versions = new Function(`return (${stdout})`)();
return versions[versions.length - 1];
};
exports.generateAppDir = function generateAppDir() {
const appPkgPath = dirname(dirname(require.resolve('@nocobase/app/src/index.ts')));
const appDevDir = resolve(process.cwd(), './storage/.app-dev');
if (exports.isDev() && !exports.hasCorePackages() && appPkgPath.includes('node_modules')) {
if (!existsSync(appDevDir)) {
mkdirSync(appDevDir, { force: true, recursive: true });
cpSync(appPkgPath, appDevDir, {
recursive: true,
force: true,
});
}
process.env.APP_PACKAGE_ROOT = appDevDir;
} else {
process.env.APP_PACKAGE_ROOT = appPkgPath;
}
};
exports.genTsConfigPaths = function genTsConfigPaths() {
try {
fs.unlinkSync(resolve(process.cwd(), 'node_modules/.bin/tsx'));
fs.symlinkSync(
resolve(process.cwd(), 'node_modules/tsx/dist/cli.mjs'),
resolve(process.cwd(), 'node_modules/.bin/tsx'),
'file',
);
} catch (error) {
//
}
const cwd = process.cwd();
const cwdLength = cwd.length;
const paths = {
'@@/*': ['.dumi/tmp/*'],
};
const packages = fg.sync(['packages/*/*/package.json', 'packages/*/*/*/package.json'], {
absolute: true,
onlyFiles: true,
});
packages.forEach((packageFile) => {
const packageJsonName = require(packageFile).name;
const packageDir = dirname(packageFile);
const relativePath = packageDir
.slice(cwdLength + 1)
.split(sep)
.join('/');
paths[`${packageJsonName}/client`] = [`${relativePath}/src/client`];
paths[`${packageJsonName}/package.json`] = [`${relativePath}/package.json`];
paths[packageJsonName] = [`${relativePath}/src`];
if (packageJsonName === '@nocobase/test') {
paths[`${packageJsonName}/server`] = [`${relativePath}/src/server`];
paths[`${packageJsonName}/e2e`] = [`${relativePath}/src/e2e`];
}
if (packageJsonName === '@nocobase/plugin-workflow-test') {
paths[`${packageJsonName}/e2e`] = [`${relativePath}/src/e2e`];
}
});
const tsConfigJsonPath = join(cwd, './tsconfig.paths.json');
const content = { compilerOptions: { paths } };
writeFileSync(tsConfigJsonPath, JSON.stringify(content, null, 2), 'utf-8');
return content;
};
function generatePlaywrightPath(clean = false) {
try {
const playwright = resolve(process.cwd(), 'storage/playwright/tests');
if (clean && fs.existsSync(playwright)) {
fs.rmSync(dirname(playwright), { force: true, recursive: true });
}
if (!fs.existsSync(playwright)) {
const testPkg = require.resolve('@nocobase/test/package.json');
fs.cpSync(resolve(dirname(testPkg), 'playwright/tests'), playwright, { recursive: true });
}
} catch (error) {
// empty
}
}
exports.generatePlaywrightPath = generatePlaywrightPath;
function parseEnv(name) {
if (name === 'DB_UNDERSCORED') {
if (process.env.DB_UNDERSCORED === 'true') {
return 'true';
}
if (process.env.DB_UNDERSCORED) {
return 'true';
}
return 'false';
}
}
exports.initEnv = function initEnv() {
const env = {
APP_ENV: 'development',
APP_KEY: 'test-jwt-secret',
APP_PORT: 13000,
API_BASE_PATH: '/api/',
DB_DIALECT: 'sqlite',
DB_STORAGE: 'storage/db/nocobase.sqlite',
DB_TIMEZONE: '+00:00',
DB_UNDERSCORED: parseEnv('DB_UNDERSCORED'),
DEFAULT_STORAGE_TYPE: 'local',
LOCAL_STORAGE_DEST: 'storage/uploads',
PLUGIN_STORAGE_PATH: resolve(process.cwd(), 'storage/plugins'),
MFSU_AD: 'none',
NODE_MODULES_PATH: resolve(process.cwd(), 'node_modules'),
PM2_HOME: resolve(process.cwd(), './storage/.pm2'),
PLUGIN_PACKAGE_PREFIX: '@nocobase/plugin-,@nocobase/plugin-sample-,@nocobase/preset-',
SERVER_TSCONFIG_PATH: './tsconfig.server.json',
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
};
if (
!process.env.APP_ENV_PATH &&
process.argv[2] &&
['test', 'test:client', 'test:server'].includes(process.argv[2])
) {
if (fs.existsSync(resolve(process.cwd(), '.env.test'))) {
process.env.APP_ENV_PATH = '.env.test';
}
}
if (!process.env.APP_ENV_PATH && process.argv[2] === 'e2e') {
// 用于存放 playwright 自动生成的相关的文件
generatePlaywrightPath();
if (!fs.existsSync('.env.e2e') && fs.existsSync('.env.e2e.example')) {
const env = fs.readFileSync('.env.e2e.example');
fs.writeFileSync('.env.e2e', env);
}
if (!fs.existsSync('.env.e2e')) {
throw new Error('Please create .env.e2e file first!');
}
process.env.APP_ENV_PATH = '.env.e2e';
}
dotenv.config({
path: resolve(process.cwd(), process.env.APP_ENV_PATH || '.env'),
});
if (process.argv[2] === 'e2e' && !process.env.APP_BASE_URL) {
process.env.APP_BASE_URL = `http://127.0.0.1:${process.env.APP_PORT}`;
}
for (const key in env) {
if (!process.env[key]) {
process.env[key] = env[key];
}
}
};