mirror of
https://github.com/nocobase/nocobase.git
synced 2026-08-29 02:03:53 +08:00
feat(cli): resolve storage path from env (#9147)
* feat(cli): resolve storage path from env * fix: storage path * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: improve storage path * fix: storage path --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
import { Registry } from '@nocobase/utils';
|
||||
import { Registry, storagePathJoin } from '@nocobase/utils';
|
||||
import { Auth, AuthExtend } from './auth';
|
||||
import { JwtOptions, JwtService } from './base/jwt-service';
|
||||
import { ITokenBlacklistService } from './base/token-blacklist-service';
|
||||
@@ -155,7 +155,7 @@ export class AuthManager {
|
||||
if (process.env.UNSAFE_USE_DEFAULT_JWT_SECRET === 'true') {
|
||||
return process.env.APP_KEY;
|
||||
}
|
||||
const jwtSecretPath = path.resolve(process.cwd(), 'storage', 'apps', 'main', 'jwt_secret.dat');
|
||||
const jwtSecretPath = storagePathJoin('apps', 'main', 'jwt_secret.dat');
|
||||
const jwtSecretExists = fs.existsSync(jwtSecretPath);
|
||||
if (jwtSecretExists) {
|
||||
const key = fs.readFileSync(jwtSecretPath);
|
||||
|
||||
@@ -77,4 +77,13 @@ export const getCjsPackages = (packages: Package[]) =>
|
||||
|
||||
// tar
|
||||
export const tarIncludesFiles = ['package.json', 'README.md', 'LICENSE', 'dist', '!node_modules'];
|
||||
export const TAR_OUTPUT_DIR = process.env.TAR_PATH ? process.env.TAR_PATH : path.join(ROOT_PATH, 'storage', 'tar');
|
||||
|
||||
function resolveStorageRoot(): string {
|
||||
const raw = process.env.STORAGE_PATH;
|
||||
if (raw) {
|
||||
return path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
||||
}
|
||||
return path.join(ROOT_PATH, 'storage');
|
||||
}
|
||||
|
||||
export const TAR_OUTPUT_DIR = process.env.TAR_PATH || path.join(resolveStorageRoot(), 'tar');
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
*/
|
||||
|
||||
const { resolve, posix } = require('path');
|
||||
const { storagePathJoin, resolvePublicPath, resolveV2PublicPath } = require('../util');
|
||||
const { Command } = require('commander');
|
||||
const { readFileSync, writeFileSync } = require('fs');
|
||||
const { resolvePublicPath, resolveV2PublicPath } = require('../util');
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -47,7 +47,7 @@ module.exports = (cli) => {
|
||||
.replace(/\{\{v2PublicPathNoTrailingSlash\}\}/g, v2PublicPathWithoutTrailingSlash)
|
||||
.replace(/\{\{apiPort\}\}/g, process.env.APP_PORT)
|
||||
.replace(/\{\{otherLocation\}\}/g, otherLocation);
|
||||
const targetFile = resolve(process.cwd(), 'storage', 'nocobase.conf');
|
||||
const targetFile = storagePathJoin('nocobase.conf');
|
||||
writeFileSync(targetFile, replaced);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { storagePathJoin } = require('./util');
|
||||
const winston = require('winston');
|
||||
require('winston-daily-rotate-file');
|
||||
|
||||
@@ -62,7 +63,7 @@ function createSystemLogger({ dirname, filename, defaultMeta = {} }) {
|
||||
}
|
||||
|
||||
const getLoggerFilePath = (...paths) => {
|
||||
return path.resolve(process.env.LOGGER_BASE_PATH || path.resolve(process.cwd(), 'storage', 'logs'), ...paths);
|
||||
return path.resolve(process.env.LOGGER_BASE_PATH || storagePathJoin('logs'), ...paths);
|
||||
};
|
||||
|
||||
const logger = createSystemLogger({
|
||||
|
||||
@@ -297,7 +297,7 @@ exports.genTsConfigPaths = function genTsConfigPaths() {
|
||||
|
||||
function generatePlaywrightPath(clean = false) {
|
||||
try {
|
||||
const playwright = resolve(process.cwd(), 'storage/playwright/tests');
|
||||
const playwright = storagePathJoin('playwright', 'tests');
|
||||
if (clean && fs.existsSync(playwright)) {
|
||||
fs.rmSync(dirname(playwright), { force: true, recursive: true });
|
||||
}
|
||||
@@ -399,6 +399,39 @@ function areTimeZonesEqual(timeZone1, timeZone2) {
|
||||
return moment.tz(timeZone1).format('Z') === moment.tz(timeZone2).format('Z');
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute application storage root (same rules as `@nocobase/utils` `resolveStorageRoot`).
|
||||
* Resolves STORAGE_PATH for initEnv; after initEnv, `process.env.STORAGE_PATH` is set to this absolute path.
|
||||
*/
|
||||
function resolveStorageRoot() {
|
||||
if (process.env.STORAGE_PATH) {
|
||||
if (isAbsolute(process.env.STORAGE_PATH)) {
|
||||
return process.env.STORAGE_PATH;
|
||||
}
|
||||
return resolve(process.cwd(), process.env.STORAGE_PATH);
|
||||
}
|
||||
return resolve(process.cwd(), 'storage');
|
||||
}
|
||||
|
||||
/** Join segments under the storage root (same semantics as `@nocobase/utils` `storagePathJoin`). */
|
||||
function storagePathJoin(...segments) {
|
||||
return join(resolveStorageRoot(), ...segments);
|
||||
}
|
||||
|
||||
exports.resolveStorageRoot = resolveStorageRoot;
|
||||
exports.storagePathJoin = storagePathJoin;
|
||||
/** @deprecated Use resolveStorageRoot — kept for backward compatibility */
|
||||
exports.generateStoragePath = resolveStorageRoot;
|
||||
|
||||
/** Align with server `getPluginStoragePath()`: `PLUGIN_STORAGE_PATH` first, else `<STORAGE_PATH>/plugins`. */
|
||||
function resolvePluginStoragePath() {
|
||||
if (process.env.PLUGIN_STORAGE_PATH) {
|
||||
const p = process.env.PLUGIN_STORAGE_PATH;
|
||||
return isAbsolute(p) ? p : resolve(process.cwd(), p);
|
||||
}
|
||||
return storagePathJoin('plugins');
|
||||
}
|
||||
|
||||
function generateGatewayPath() {
|
||||
if (process.env.SOCKET_PATH) {
|
||||
if (isAbsolute(process.env.SOCKET_PATH)) {
|
||||
@@ -409,7 +442,7 @@ function generateGatewayPath() {
|
||||
if (process.env.NOCOBASE_RUNNING_IN_DOCKER === 'true') {
|
||||
return resolve(os.homedir(), '.nocobase', 'gateway.sock');
|
||||
}
|
||||
return resolve(process.cwd(), 'storage/gateway.sock');
|
||||
return storagePathJoin('gateway.sock');
|
||||
}
|
||||
|
||||
function generatePm2Home() {
|
||||
@@ -422,7 +455,7 @@ function generatePm2Home() {
|
||||
if (process.env.NOCOBASE_RUNNING_IN_DOCKER === 'true') {
|
||||
return resolve(os.homedir(), '.nocobase', 'pm2');
|
||||
}
|
||||
return resolve(process.cwd(), './storage/.pm2');
|
||||
return storagePathJoin('.pm2');
|
||||
}
|
||||
|
||||
exports.initEnv = function initEnv() {
|
||||
@@ -435,12 +468,10 @@ exports.initEnv = function initEnv() {
|
||||
API_CLIENT_SHARE_TOKEN: 'false',
|
||||
API_CLIENT_STORAGE_TYPE: 'localStorage',
|
||||
// DB_DIALECT: 'sqlite',
|
||||
DB_STORAGE: 'storage/db/nocobase.sqlite',
|
||||
// DB_STORAGE, LOCAL_STORAGE_DEST, PLUGIN_STORAGE_PATH, etc. are set after dotenv from STORAGE_PATH
|
||||
// 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',
|
||||
MAKO_AD: 'none',
|
||||
WS_PATH: '/ws',
|
||||
@@ -449,17 +480,14 @@ exports.initEnv = function initEnv() {
|
||||
NODE_MODULES_PATH: resolve(process.cwd(), 'node_modules'),
|
||||
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'),
|
||||
CACHE_DEFAULT_STORE: 'memory',
|
||||
CACHE_MEMORY_MAX: 2000,
|
||||
BROWSERSLIST_IGNORE_OLD_DATA: true,
|
||||
PLUGIN_STATICS_PATH: '/static/plugins/',
|
||||
LOGGER_BASE_PATH: 'storage/logs',
|
||||
APP_SERVER_BASE_URL: '',
|
||||
APP_BASE_URL: '',
|
||||
CDN_BASE_URL: '',
|
||||
APP_PUBLIC_PATH: '/',
|
||||
WATCH_FILE: resolve(process.cwd(), 'storage/app.watch.ts'),
|
||||
ESM_CDN_BASE_URL: 'https://esm.sh',
|
||||
ESM_CDN_SUFFIX: '',
|
||||
};
|
||||
@@ -491,6 +519,17 @@ exports.initEnv = function initEnv() {
|
||||
path: resolve(process.cwd(), process.env.APP_ENV_PATH || '.env'),
|
||||
});
|
||||
|
||||
const storagePath = resolveStorageRoot();
|
||||
process.env.STORAGE_PATH = storagePath; // absolute; other modules may use process.env.STORAGE_PATH after this
|
||||
Object.assign(env, {
|
||||
DB_STORAGE: storagePathJoin('db', 'nocobase.sqlite'),
|
||||
LOCAL_STORAGE_DEST: storagePathJoin('uploads'),
|
||||
PLUGIN_STORAGE_PATH: storagePathJoin('plugins'),
|
||||
PLAYWRIGHT_AUTH_FILE: storagePathJoin('playwright', '.auth', 'admin.json'),
|
||||
LOGGER_BASE_PATH: storagePathJoin('logs'),
|
||||
WATCH_FILE: storagePathJoin('app.watch.ts'),
|
||||
});
|
||||
|
||||
if (process.argv[2] === 'e2e' && !process.env.APP_BASE_URL) {
|
||||
process.env.APP_BASE_URL = `http://127.0.0.1:${process.env.APP_PORT}`;
|
||||
}
|
||||
@@ -555,7 +594,7 @@ exports.initEnv = function initEnv() {
|
||||
'@nocobase/plugin-workflow-response-message',
|
||||
];
|
||||
for (const pkg of pkgs) {
|
||||
const pkgDir = resolve(process.cwd(), 'storage/plugins', pkg);
|
||||
const pkgDir = join(resolvePluginStoragePath(), pkg);
|
||||
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"directory": "packages/logger"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "2.1.0-alpha.18",
|
||||
"chalk": "^4",
|
||||
"lodash": "^4.17.21",
|
||||
"triple-beam": "^1.4.1",
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
|
||||
export const getLoggerLevel = () =>
|
||||
process.env.LOGGER_LEVEL || (process.env.APP_ENV === 'development' ? 'debug' : 'info');
|
||||
|
||||
export const getLoggerFilePath = (...paths: string[]): string => {
|
||||
return path.resolve(process.env.LOGGER_BASE_PATH || path.resolve(process.cwd(), 'storage', 'logs'), ...paths);
|
||||
return path.resolve(process.env.LOGGER_BASE_PATH || storagePathJoin('logs'), ...paths);
|
||||
};
|
||||
|
||||
export const getLoggerTransport = (): ('console' | 'file' | 'dailyRotateFile')[] =>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { randomUUID } from 'node:crypto';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
|
||||
import { MockServer, createMockServer, sleep } from '@nocobase/test';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import { Plugin } from '../plugin';
|
||||
|
||||
class MockPlugin extends Plugin {
|
||||
@@ -324,7 +325,7 @@ describe('memory queue adapter', () => {
|
||||
describe('storage', () => {
|
||||
test('graceful shutdown, will create storage', async () => {
|
||||
const mockListener = vi.fn();
|
||||
const queueFile = path.resolve(process.cwd(), 'storage', 'apps', app.name, 'event-queue.json');
|
||||
const queueFile = storagePathJoin('apps', app.name, 'event-queue.json');
|
||||
await expect(fs.stat(queueFile)).rejects.toThrowError();
|
||||
await app.eventQueue.subscribe('test1', {
|
||||
idle: () => true,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
import path, { resolve } from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import Application from './application';
|
||||
|
||||
export class AesEncryptor {
|
||||
@@ -75,12 +76,12 @@ export class AesEncryptor {
|
||||
}
|
||||
|
||||
static async getKeyPath(appName: string) {
|
||||
const appKeyPath = path.resolve(process.cwd(), 'storage', 'apps', appName, 'aes_key.dat');
|
||||
const appKeyPath = storagePathJoin('apps', appName, 'aes_key.dat');
|
||||
const appKeyExists = await fs.exists(appKeyPath);
|
||||
if (appKeyExists) {
|
||||
return appKeyPath;
|
||||
}
|
||||
const envKeyPath = path.resolve(process.cwd(), 'storage', 'environment-variables', appName, 'aes_key.dat');
|
||||
const envKeyPath = storagePathJoin('environment-variables', appName, 'aes_key.dat');
|
||||
const envKeyExists = await fs.exists(envKeyPath);
|
||||
if (envKeyExists) {
|
||||
return envKeyPath;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import fg from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import { FlexSearchIndex } from '@nocobase/ai';
|
||||
import type Application from '../application';
|
||||
import { findAllPlugins } from '../plugin-manager/findPackageNames';
|
||||
@@ -33,7 +34,7 @@ type DirectoryChildren = Map<
|
||||
}
|
||||
>;
|
||||
|
||||
const DOCS_STORAGE_DIR = path.resolve(process.cwd(), 'storage/ai/docs');
|
||||
const DOCS_STORAGE_DIR = storagePathJoin('ai', 'docs');
|
||||
const REFERENCE_START = '<!-- docs:references:start -->';
|
||||
const REFERENCE_END = '<!-- docs:references:end -->';
|
||||
const SPLIT_REFERENCE_START = '<!-- docs:splits:start -->';
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/* istanbul ignore file -- @preserve */
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import Application from '../application';
|
||||
import { createDocsIndex } from '../ai/create-docs-index';
|
||||
import { ApplicationNotInstall } from '../errors/application-not-install';
|
||||
@@ -23,7 +23,7 @@ export default (app: Application) => {
|
||||
.option('--quickstart')
|
||||
.action(async (...cliArgs) => {
|
||||
const [options] = cliArgs;
|
||||
const file = resolve(process.cwd(), 'storage/.upgrading');
|
||||
const file = storagePathJoin('.upgrading');
|
||||
const upgrading = await fs.exists(file);
|
||||
if (upgrading) {
|
||||
if (!process.env.VITEST) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import fs from 'fs/promises';
|
||||
|
||||
import Application from './application';
|
||||
import { SystemLogger } from '@nocobase/logger';
|
||||
import { sleep } from '@nocobase/utils';
|
||||
import { sleep, storagePathJoin } from '@nocobase/utils';
|
||||
|
||||
export const QUEUE_DEFAULT_INTERVAL = 250;
|
||||
export const QUEUE_DEFAULT_CONCURRENCY = 1;
|
||||
@@ -87,7 +87,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
}
|
||||
|
||||
private get storagePath() {
|
||||
return path.resolve(process.cwd(), 'storage', 'apps', this.options.appName, 'event-queue.json');
|
||||
return storagePathJoin('apps', this.options.appName, 'event-queue.json');
|
||||
}
|
||||
|
||||
listen = (channel: string) => {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { createSystemLogger, getLoggerFilePath, SystemLogger } from '@nocobase/logger';
|
||||
import { Registry, Toposort, ToposortOptions, uid } from '@nocobase/utils';
|
||||
import { Registry, storagePathJoin, Toposort, ToposortOptions, uid } from '@nocobase/utils';
|
||||
import { lockdownSes } from '@nocobase/utils';
|
||||
import { createStoragePluginsSymlink } from '@nocobase/utils/plugin-symlink';
|
||||
import { Command } from 'commander';
|
||||
@@ -19,6 +19,7 @@ import fs from 'fs';
|
||||
import http, { IncomingMessage, ServerResponse } from 'http';
|
||||
import compose from 'koa-compose';
|
||||
import { promisify } from 'node:util';
|
||||
import { homedir } from 'node:os';
|
||||
import { extname, isAbsolute, resolve } from 'path';
|
||||
import qs from 'qs';
|
||||
import handler from 'serve-handler';
|
||||
@@ -74,14 +75,16 @@ function normalizeBasePath(path = '') {
|
||||
return normalized || '/';
|
||||
}
|
||||
|
||||
/** Align with cli-v1 `generateGatewayPath()` / `process.env.SOCKET_PATH` after initEnv. */
|
||||
function getSocketPath() {
|
||||
const { SOCKET_PATH } = process.env;
|
||||
|
||||
if (isAbsolute(SOCKET_PATH)) {
|
||||
return SOCKET_PATH;
|
||||
const socketPath = process.env.SOCKET_PATH;
|
||||
if (socketPath) {
|
||||
return isAbsolute(socketPath) ? socketPath : resolve(process.cwd(), socketPath);
|
||||
}
|
||||
|
||||
return resolve(process.cwd(), SOCKET_PATH);
|
||||
if (process.env.NOCOBASE_RUNNING_IN_DOCKER === 'true') {
|
||||
return resolve(homedir(), '.nocobase', 'gateway.sock');
|
||||
}
|
||||
return storagePathJoin('gateway.sock');
|
||||
}
|
||||
|
||||
export class Gateway extends EventEmitter {
|
||||
@@ -98,7 +101,7 @@ export class Gateway extends EventEmitter {
|
||||
loggers = new Registry<SystemLogger>();
|
||||
private port: number = process.env.APP_PORT ? parseInt(process.env.APP_PORT) : null;
|
||||
private host = '0.0.0.0';
|
||||
private socketPath = resolve(process.cwd(), 'storage', 'gateway.sock');
|
||||
private socketPath = getSocketPath();
|
||||
private v2IndexTemplateCache: { file: string; mtimeMs: number; html: string } | null = null;
|
||||
private terminating = false;
|
||||
|
||||
@@ -136,7 +139,6 @@ export class Gateway extends EventEmitter {
|
||||
private constructor() {
|
||||
super();
|
||||
this.reset();
|
||||
this.socketPath = getSocketPath();
|
||||
process.once('SIGTERM', this.onTerminate);
|
||||
process.once('SIGINT', this.onTerminate);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
export const APP_NAME = 'nocobase';
|
||||
/** Relative default under cwd when STORAGE_PATH / PLUGIN_STORAGE_PATH are unset; prefer `getPluginStoragePath()`. */
|
||||
export const DEFAULT_PLUGIN_STORAGE_PATH = 'storage/plugins';
|
||||
export const DEFAULT_PLUGIN_PATH = 'packages/plugins/';
|
||||
export const pluginPrefix = (
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { uid } from '@nocobase/utils';
|
||||
import { storagePathJoin, uid } from '@nocobase/utils';
|
||||
import fs from 'fs';
|
||||
import fse from 'fs-extra';
|
||||
import path from 'path';
|
||||
@@ -145,13 +145,13 @@ export default {
|
||||
}
|
||||
app.runAsCLI(['pm', 'add', values.packageName, ...args], { from: 'user' });
|
||||
} else if (ctx.file) {
|
||||
const tmpDir = path.resolve(process.cwd(), 'storage', 'tmp');
|
||||
const tmpDir = storagePathJoin('tmp');
|
||||
try {
|
||||
await fs.promises.mkdir(tmpDir, { recursive: true });
|
||||
} catch (error) {
|
||||
// empty
|
||||
}
|
||||
const tempFile = path.join(process.cwd(), 'storage/tmp', uid() + path.extname(ctx.file.originalname));
|
||||
const tempFile = path.join(tmpDir, uid() + path.extname(ctx.file.originalname));
|
||||
await fs.promises.writeFile(tempFile, ctx.file.buffer, 'binary');
|
||||
app.runAsCLI(['pm', 'add', tempFile], { from: 'user' });
|
||||
} else if (values.compressedFileUrl) {
|
||||
@@ -181,13 +181,13 @@ export default {
|
||||
// }
|
||||
if (ctx.file) {
|
||||
values.packageName = ctx.request.body.packageName;
|
||||
const tmpDir = path.resolve(process.cwd(), 'storage', 'tmp');
|
||||
const tmpDir = storagePathJoin('tmp');
|
||||
try {
|
||||
await fs.promises.mkdir(tmpDir, { recursive: true });
|
||||
} catch (error) {
|
||||
// empty
|
||||
}
|
||||
const tempFile = path.join(process.cwd(), 'storage/tmp', uid() + path.extname(ctx.file.originalname));
|
||||
const tempFile = path.join(tmpDir, uid() + path.extname(ctx.file.originalname));
|
||||
await fs.promises.writeFile(tempFile, ctx.file.buffer, 'binary');
|
||||
// args.push(`--url=${tempFile}`);
|
||||
values.compressedFileUrl = tempFile;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import Topo from '@hapi/topo';
|
||||
import { CleanOptions, Collection, SyncOptions } from '@nocobase/database';
|
||||
import { importModule, isURL } from '@nocobase/utils';
|
||||
import { importModule, isURL, storagePathJoin } from '@nocobase/utils';
|
||||
import execa from 'execa';
|
||||
import fg from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
@@ -832,7 +832,7 @@ export class PluginManager {
|
||||
if (process.env.VITEST) {
|
||||
return;
|
||||
}
|
||||
const file = resolve(process.cwd(), 'storage/.upgrading');
|
||||
const file = storagePathJoin('.upgrading');
|
||||
this.app.log.debug('pending upgrade');
|
||||
await fs.writeFile(file, 'upgrading');
|
||||
};
|
||||
@@ -940,7 +940,7 @@ export class PluginManager {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const file = resolve(process.cwd(), 'storage/app-upgrading');
|
||||
const file = storagePathJoin('app-upgrading');
|
||||
await fs.writeFile(file, '', 'utf-8');
|
||||
// await this.app.upgrade();
|
||||
await tsxRerunning();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
/* istanbul ignore next -- @preserve */
|
||||
|
||||
import { importModule, isURL, requireResolve } from '@nocobase/utils';
|
||||
import { importModule, isURL, requireResolve, storagePathJoin } from '@nocobase/utils';
|
||||
import { createStoragePluginSymLink } from '@nocobase/utils/plugin-symlink';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import decompress from 'decompress';
|
||||
@@ -21,15 +21,7 @@ import os from 'os';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
import { getDepPkgPath, getPackageDir, getPackageFilePathWithExistCheck } from './clientStaticUtils';
|
||||
import {
|
||||
APP_NAME,
|
||||
DEFAULT_PLUGIN_PATH,
|
||||
DEFAULT_PLUGIN_STORAGE_PATH,
|
||||
EXTERNAL,
|
||||
importRegex,
|
||||
pluginPrefix,
|
||||
requireRegex,
|
||||
} from './constants';
|
||||
import { APP_NAME, DEFAULT_PLUGIN_PATH, EXTERNAL, importRegex, pluginPrefix, requireRegex } from './constants';
|
||||
import deps from './deps';
|
||||
import { PluginManagerRepository } from './plugin-manager-repository';
|
||||
import { PluginData } from './types';
|
||||
@@ -48,9 +40,13 @@ export async function getTempDir() {
|
||||
return path.join(temporaryDirectory, APP_NAME);
|
||||
}
|
||||
|
||||
/** Storage plugins directory: always prefer `PLUGIN_STORAGE_PATH` (absolute or relative to cwd). */
|
||||
export function getPluginStoragePath() {
|
||||
const pluginStoragePath = process.env.PLUGIN_STORAGE_PATH || DEFAULT_PLUGIN_STORAGE_PATH;
|
||||
return path.isAbsolute(pluginStoragePath) ? pluginStoragePath : path.join(process.cwd(), pluginStoragePath);
|
||||
if (process.env.PLUGIN_STORAGE_PATH) {
|
||||
const pluginStoragePath = process.env.PLUGIN_STORAGE_PATH;
|
||||
return path.isAbsolute(pluginStoragePath) ? pluginStoragePath : path.join(process.cwd(), pluginStoragePath);
|
||||
}
|
||||
return storagePathJoin('plugins');
|
||||
}
|
||||
|
||||
export function getLocalPluginPackagesPathArr(): string[] {
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"./vitest.mjs": "./vitest.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "2.1.0-alpha.18",
|
||||
"@faker-js/faker": "8.1.0",
|
||||
"@nocobase/server": "2.1.0-alpha.18",
|
||||
"@playwright/test": "^1.45.3",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createMockServer } from '@nocobase/test';
|
||||
import { CollectionRepository } from '@nocobase/plugin-data-source-main';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
@@ -7,7 +8,7 @@ import ExcelJS from 'exceljs';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import request from 'superagent';
|
||||
|
||||
const storagePath = path.resolve(process.cwd(), 'storage', 'perf', 'importDataTest');
|
||||
const storagePath = storagePathJoin('perf', 'importDataTest');
|
||||
|
||||
export default async function main() {
|
||||
const app = await createMockServer({
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import Path from 'node:path';
|
||||
import { spawn, ChildProcess } from 'node:child_process';
|
||||
|
||||
import { getPortPromise } from 'portfinder';
|
||||
import { uid } from '@nocobase/utils';
|
||||
import { storagePathJoin, uid } from '@nocobase/utils';
|
||||
import { createMockServer } from './mock-server';
|
||||
|
||||
type IsolatedClusterOptions = {
|
||||
@@ -49,8 +48,8 @@ export class MockIsolatedCluster {
|
||||
...this.options.env,
|
||||
APP_PORT: `${port}`,
|
||||
APPEND_PRESET_BUILT_IN_PLUGINS: (this.options.plugins ?? []).join(','),
|
||||
SOCKET_PATH: `storage/tests/gateway-cluster-${uid()}.sock`,
|
||||
PM2_HOME: Path.resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
|
||||
SOCKET_PATH: storagePathJoin('tests', `gateway-cluster-${uid()}.sock`),
|
||||
PM2_HOME: storagePathJoin('tests', `.pm2-${uid()}`),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
|
||||
import { mockDatabase } from '@nocobase/database';
|
||||
import { Application, ApplicationOptions, AppSupervisor, Gateway, PluginManager } from '@nocobase/server';
|
||||
import { uid } from '@nocobase/utils';
|
||||
import { storagePathJoin, uid } from '@nocobase/utils';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import qs from 'qs';
|
||||
import supertest from 'supertest';
|
||||
import { SuperAgent, SuperAgentRequest } from 'superagent';
|
||||
import { MemoryPubSubAdapter } from './memory-pub-sub-adapter';
|
||||
import { MockDataSource } from './mock-data-source';
|
||||
import path from 'path';
|
||||
import process from 'node:process';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
@@ -344,7 +343,7 @@ export async function createMockCluster({
|
||||
|
||||
export async function createMockServer(options: MockServerOptions = {}): Promise<MockServer> {
|
||||
// clean cache directory
|
||||
const cachePath = path.join(process.cwd(), 'storage', 'cache');
|
||||
const cachePath = storagePathJoin('cache');
|
||||
try {
|
||||
await fs.rm(cachePath, { recursive: true, force: true });
|
||||
await fs.mkdir(cachePath, { recursive: true });
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
const { resolve } = require('path');
|
||||
const path = require('path');
|
||||
const { resolve } = path;
|
||||
const fs = require('fs-extra');
|
||||
|
||||
/** Align with server `getPluginStoragePath()`: `PLUGIN_STORAGE_PATH` first, else `<STORAGE_PATH>/plugins`. */
|
||||
function resolvePluginStoragePath() {
|
||||
if (process.env.PLUGIN_STORAGE_PATH) {
|
||||
const p = process.env.PLUGIN_STORAGE_PATH;
|
||||
return path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
||||
}
|
||||
return path.join(process.env.STORAGE_PATH || path.resolve(process.cwd(), 'storage'), 'plugins');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively get plugin names from a directory
|
||||
* @param {string} target - Target directory to scan
|
||||
@@ -136,7 +146,7 @@ async function createPluginSymLink(pluginName, sourcePath, nodeModulesPath, plug
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function createStoragePluginSymLink(pluginName) {
|
||||
const storagePluginsPath = resolve(process.cwd(), 'storage/plugins');
|
||||
const storagePluginsPath = resolvePluginStoragePath();
|
||||
const nodeModulesPath = process.env.NODE_MODULES_PATH;
|
||||
await createPluginSymLink(pluginName, storagePluginsPath, nodeModulesPath, 'storage');
|
||||
}
|
||||
@@ -146,7 +156,7 @@ async function createStoragePluginSymLink(pluginName) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function createStoragePluginsSymlink() {
|
||||
const storagePluginsPath = resolve(process.cwd(), 'storage/plugins');
|
||||
const storagePluginsPath = resolvePluginStoragePath();
|
||||
if (!(await fs.pathExists(storagePluginsPath))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,5 +47,6 @@ export * from './wrap-middleware';
|
||||
export * from './run-sql';
|
||||
export * from './liquidjs';
|
||||
export * from './server-request';
|
||||
export * from './storage-path';
|
||||
export { lodash };
|
||||
//
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Absolute path to the application storage root (same rules as CLI `resolveStorageRoot` in `cli-v1/src/util.js`).
|
||||
*/
|
||||
export function resolveStorageRoot(): string {
|
||||
const raw = process.env.STORAGE_PATH;
|
||||
if (raw) {
|
||||
return path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
||||
}
|
||||
return path.resolve(process.cwd(), 'storage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Join path segments under the application storage root.
|
||||
* Resolution matches CLI `resolveStorageRoot()` / `initEnv`: use `STORAGE_PATH` when set
|
||||
* (absolute or relative to cwd), otherwise `<cwd>/storage`.
|
||||
*
|
||||
* @example storagePathJoin('tmp')
|
||||
* @example storagePathJoin('cache', 'apps', appName)
|
||||
*/
|
||||
export function storagePathJoin(...segments: string[]): string {
|
||||
return path.join(resolveStorageRoot(), ...segments);
|
||||
}
|
||||
+2
-5
@@ -23,6 +23,7 @@ import os from 'os';
|
||||
import { Logger } from '@nocobase/logger';
|
||||
import _ from 'lodash';
|
||||
import { Field, RelationField } from '@nocobase/database';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
|
||||
export type ExportOptions = {
|
||||
collectionManager: ICollectionManager;
|
||||
@@ -277,11 +278,7 @@ abstract class BaseExporter<T extends ExportOptions = ExportOptions> extends Eve
|
||||
return value;
|
||||
}
|
||||
|
||||
public generateOutputPath(
|
||||
prefix = 'export',
|
||||
ext = '',
|
||||
destination = path.join(process.cwd(), 'storage', 'tmp'),
|
||||
): string {
|
||||
public generateOutputPath(prefix = 'export', ext = '', destination = storagePathJoin('tmp')): string {
|
||||
const fileName = `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`;
|
||||
return path.join(destination, fileName);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
|
||||
import { z } from 'zod';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import fs from 'fs-extra';
|
||||
import fg from 'fast-glob';
|
||||
import { Index as FlexSearchIndex } from 'flexsearch';
|
||||
import { ToolsOptions } from '@nocobase/ai';
|
||||
|
||||
const DEFAULT_DOCS_DIR = path.resolve(process.cwd(), 'storage/ai/docs');
|
||||
const DEFAULT_DOCS_DIR = storagePathJoin('ai', 'docs');
|
||||
|
||||
type DocsIndexMeta = {
|
||||
key: string;
|
||||
|
||||
@@ -15,6 +15,7 @@ import fse from 'fs-extra';
|
||||
import fsPromises from 'fs/promises';
|
||||
import { default as _, default as lodash } from 'lodash';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import * as process from 'process';
|
||||
import stream from 'stream';
|
||||
import util from 'util';
|
||||
@@ -165,13 +166,10 @@ export class Dumper extends AppMigrator {
|
||||
}
|
||||
|
||||
backUpStorageDir() {
|
||||
const paths = [process.cwd(), 'storage', 'backups'];
|
||||
|
||||
if (this.app.name !== 'main') {
|
||||
paths.push(this.app.name);
|
||||
return storagePathJoin('backups', this.app.name);
|
||||
}
|
||||
|
||||
return path.resolve(...paths);
|
||||
return storagePathJoin('backups');
|
||||
}
|
||||
|
||||
async allBackUpFilePaths(options?: { includeInProgress?: boolean; dir?: string }) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import decompress from 'decompress';
|
||||
import fs from 'fs';
|
||||
import fsPromises from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import { AppMigrator, AppMigratorOptions } from './app-migrator';
|
||||
import { readLines } from './utils';
|
||||
import { Application } from '@nocobase/server';
|
||||
@@ -100,7 +101,7 @@ export class Restorer extends AppMigrator {
|
||||
if (path.isAbsolute(backUpFilePath)) {
|
||||
this.backUpFilePath = backUpFilePath;
|
||||
} else if (path.basename(backUpFilePath) === backUpFilePath) {
|
||||
const dirname = path.resolve(process.cwd(), 'storage', 'duplicator');
|
||||
const dirname = storagePathJoin('duplicator');
|
||||
this.backUpFilePath = path.resolve(dirname, backUpFilePath);
|
||||
} else {
|
||||
this.backUpFilePath = path.resolve(process.cwd(), backUpFilePath);
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import { Model, Transaction } from '@nocobase/database';
|
||||
import { SequelizeCollectionManager } from '@nocobase/data-source-manager';
|
||||
import { setCurrentRole } from '@nocobase/plugin-acl';
|
||||
import { Application } from '@nocobase/server';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import PluginDataSourceManagerServer from '../plugin';
|
||||
import { DataSourcesRolesModel } from './data-sources-roles-model';
|
||||
|
||||
@@ -110,7 +110,7 @@ export class DataSourceModel extends Model {
|
||||
logger: app.logger.child({ dataSourceKey }),
|
||||
sqlLogger: app.sqlLogger.child({ dataSourceKey }),
|
||||
cache: app.cache,
|
||||
storagePath: path.join(process.cwd(), 'storage', 'cache', 'apps', app.name),
|
||||
storagePath: storagePathJoin('cache', 'apps', app.name),
|
||||
databaseInstance,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { isURL } from '@nocobase/utils';
|
||||
import { isURL, resolveStorageRoot, storagePathJoin } from '@nocobase/utils';
|
||||
import fsSync from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import multer from 'multer';
|
||||
@@ -21,10 +21,14 @@ import { diskFilenameGetter } from '../utils';
|
||||
const DEFAULT_BASE_URL = '/storage/uploads';
|
||||
|
||||
export function getDocumentRoot(storage): string {
|
||||
const { documentRoot = process.env.LOCAL_STORAGE_DEST || path.join(process.cwd(), 'storage', 'uploads') } =
|
||||
storage.options || {};
|
||||
// TODO(feature): 后面考虑以字符串模板的方式使用,可注入 req/action 相关变量,以便于区分文件夹
|
||||
return path.resolve(path.isAbsolute(documentRoot) ? documentRoot : path.join(process.cwd(), documentRoot));
|
||||
const storageRoot = resolveStorageRoot();
|
||||
const raw = storage?.options?.documentRoot ?? process.env.LOCAL_STORAGE_DEST ?? storagePathJoin('uploads');
|
||||
|
||||
if (path.isAbsolute(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return path.resolve(process.cwd(), raw);
|
||||
}
|
||||
|
||||
export function resolveSafePath(documentRoot: string, filePath?: string, filename?: string) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import fs from 'node:fs';
|
||||
import inject from 'light-my-request';
|
||||
import { createHash } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import { createDbAdapter } from './db-adapter';
|
||||
import { normalizeBasePath } from './utils';
|
||||
|
||||
@@ -325,7 +326,7 @@ export class IdpOauthService {
|
||||
}
|
||||
|
||||
private getDefaultJwksPath(appName: string) {
|
||||
return path.resolve(process.cwd(), 'storage', 'apps', appName, 'idp_oauth_jwks.json');
|
||||
return storagePathJoin('apps', appName, 'idp_oauth_jwks.json');
|
||||
}
|
||||
|
||||
private async getProviderSigningJwks(appName: string) {
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export async function getInstanceId() {
|
||||
const dir = path.resolve(process.cwd(), 'storage/.license');
|
||||
const dir = storagePathJoin('.license');
|
||||
const filePath = path.resolve(dir, 'instance-id');
|
||||
await createInstanceId(true);
|
||||
const id = fs.readFileSync(filePath, 'utf-8');
|
||||
@@ -32,7 +33,7 @@ export async function createInstanceId(force = false) {
|
||||
}
|
||||
|
||||
export async function isLicenseKeyExists() {
|
||||
const dir = path.resolve(process.cwd(), 'storage/.license');
|
||||
const dir = storagePathJoin('.license');
|
||||
const filePath = path.resolve(dir, 'license-key');
|
||||
return fs.existsSync(filePath);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { keyDecrypt } from '@nocobase/license-kit';
|
||||
import { Context } from 'koa';
|
||||
import path from 'path';
|
||||
import { storagePathJoin } from '@nocobase/utils';
|
||||
import fs from 'fs';
|
||||
import { KeyData } from './interface';
|
||||
import { CACHE_KEY } from './interface';
|
||||
@@ -63,7 +64,7 @@ export async function request(
|
||||
}
|
||||
|
||||
export async function saveLicenseKey(licenseKey: string, ctx?: any) {
|
||||
const dir = path.resolve(process.cwd(), 'storage/.license');
|
||||
const dir = storagePathJoin('.license');
|
||||
const filePath = path.resolve(dir, 'license-key');
|
||||
await fs.promises.writeFile(filePath, licenseKey);
|
||||
|
||||
@@ -74,7 +75,7 @@ export async function saveLicenseKey(licenseKey: string, ctx?: any) {
|
||||
}
|
||||
|
||||
export async function getLocalKeyData() {
|
||||
const keyFile = path.resolve(process.cwd(), 'storage/.license/license-key');
|
||||
const keyFile = storagePathJoin('.license', 'license-key');
|
||||
try {
|
||||
const key = (await fs.promises.readFile(keyFile, 'utf8')).trim();
|
||||
return JSON.parse(keyDecrypt(key));
|
||||
@@ -85,7 +86,7 @@ export async function getLocalKeyData() {
|
||||
|
||||
export async function getKey(ctx?: Context): Promise<string> {
|
||||
let key: string | undefined;
|
||||
const keyFile = path.resolve(process.cwd(), 'storage/.license/license-key');
|
||||
const keyFile = storagePathJoin('.license', 'license-key');
|
||||
|
||||
if (ctx?.cache) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user