diff --git a/cli/bin/reactpress.js b/cli/bin/reactpress.js
index b18b28f..67829e1 100755
--- a/cli/bin/reactpress.js
+++ b/cli/bin/reactpress.js
@@ -8,12 +8,14 @@
const { Command } = require('commander');
const path = require('path');
const chalk = require('chalk');
-const { ensureOriginalCwd, getMonorepoRoot } = require('../lib/root');
+const { brand, divider } = require('../ui/theme');
+const { ensureOriginalCwd } = require('../lib/root');
const { ensureProjectEnvironment, initMonorepoProject } = require('../lib/bootstrap');
const { runDev } = require('../lib/dev');
const { runApiDev } = require('../lib/api-dev');
const { runLifecycleCommand } = require('../lib/lifecycle');
const { runDockerCommand } = require('../lib/docker');
+const { runNginxCommand } = require('../lib/nginx');
const { printUnifiedStatus } = require('../lib/status');
const { runDoctor } = require('../lib/doctor');
const { runDbBackup } = require('../lib/db-backup');
@@ -24,7 +26,7 @@ const { getClientBin } = require('../lib/paths');
const { runInteractiveLoop } = require('../ui/interactive');
const { t } = require('../lib/i18n');
-const rootPkg = require(path.join(getMonorepoRoot(), 'package.json'));
+const rootPkg = require(path.join(__dirname, '..', 'package.json'));
const program = new Command();
@@ -176,6 +178,102 @@ dockerCmd
await runDockerCommand('logs', ensureOriginalCwd(), service ? [service] : []);
});
+const nginxCmd = program.command('nginx').description(t('cli.nginx.description'));
+
+function nginxActionOptions(cmd) {
+ return cmd.option('--prod', t('cli.nginx.prod')).option('-f, --force', t('cli.nginx.force'));
+}
+
+nginxActionOptions(nginxCmd.command('ensure').description(t('cli.nginx.ensure'))).action(async (options) => {
+ try {
+ await runNginxCommand('ensure', ensureOriginalCwd(), [], options);
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
+nginxActionOptions(nginxCmd.command('up').description(t('cli.nginx.up'))).action(async (options) => {
+ try {
+ await runNginxCommand('up', ensureOriginalCwd(), [], options);
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
+nginxCmd
+ .command('down')
+ .alias('stop')
+ .description(t('cli.nginx.down'))
+ .option('--prod', t('cli.nginx.prod'))
+ .action(async (options) => {
+ try {
+ await runNginxCommand('down', ensureOriginalCwd(), [], options);
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+ });
+
+nginxActionOptions(nginxCmd.command('restart').description(t('cli.nginx.restart'))).action(async (options) => {
+ try {
+ await runNginxCommand('restart', ensureOriginalCwd(), [], options);
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
+nginxCmd
+ .command('status')
+ .description(t('cli.nginx.status'))
+ .option('--prod', t('cli.nginx.prod'))
+ .action(async (options) => {
+ try {
+ await runNginxCommand('status', ensureOriginalCwd(), [], options);
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+ });
+
+nginxCmd.command('logs').description(t('cli.nginx.logs')).action(async () => {
+ try {
+ await runNginxCommand('logs', ensureOriginalCwd());
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
+nginxCmd.command('test').description(t('cli.nginx.test')).action(async () => {
+ try {
+ await runNginxCommand('test', ensureOriginalCwd());
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
+nginxCmd.command('reload').description(t('cli.nginx.reload')).action(async () => {
+ try {
+ await runNginxCommand('reload', ensureOriginalCwd());
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
+nginxCmd.command('open').description(t('cli.nginx.open')).action(async () => {
+ try {
+ await runNginxCommand('open', ensureOriginalCwd());
+ } catch (err) {
+ console.error(chalk.red('[reactpress]'), err.message || err);
+ process.exit(1);
+ }
+});
+
program
.command('status')
.description(t('cli.status.description'))
@@ -212,19 +310,16 @@ program
.option('--build', t('cli.publish.build'))
.option('--publish', t('cli.publish.publish'))
.action(async (options) => {
- const prev = process.argv.slice();
- const args = [process.argv[0], process.argv[1]];
- if (options.build) args.push('--build');
- else if (options.publish) args.push('--publish');
- else args.push('--publish');
- process.argv = args;
try {
- await require('../lib/publish').main();
+ const publish = require('../lib/publish');
+ if (options.build) {
+ await publish.buildPackages();
+ return;
+ }
+ await publish.publishPackages();
} catch (err) {
console.error(chalk.red('[reactpress]'), err.message || err);
process.exit(1);
- } finally {
- process.argv = prev;
}
});
@@ -233,9 +328,14 @@ program
.description(t('cli.start.description'))
.action(async () => {
const projectRoot = ensureOriginalCwd();
- const { spawn } = require('child_process');
+ const { hasClient } = require('../lib/project-type');
const code = await runLifecycleCommand('start', projectRoot);
if (code !== 0) process.exit(code);
+ if (!hasClient(projectRoot)) {
+ console.log(t('dev.standaloneHint'));
+ return;
+ }
+ const { spawn } = require('child_process');
const child = spawn('pnpm', ['run', '--dir', './client', 'start'], {
stdio: 'inherit',
shell: true,
@@ -246,16 +346,23 @@ program
program.on('--help', () => {
console.log('');
- console.log(chalk.gray(t('cli.help.examples')));
- console.log(t('cli.help.interactive'));
- console.log(t('cli.help.dev'));
- console.log(t('cli.help.init'));
- console.log(t('cli.help.server'));
- console.log(t('cli.help.status'));
- console.log(t('cli.help.doctor'));
- console.log(t('cli.help.docker'));
- console.log(t('cli.help.build'));
- console.log(t('cli.help.publish'));
+ console.log(brand.bold(t('cli.help.examples')));
+ console.log(divider(40));
+ const lines = [
+ t('cli.help.interactive'),
+ t('cli.help.dev'),
+ t('cli.help.init'),
+ t('cli.help.server'),
+ t('cli.help.status'),
+ t('cli.help.doctor'),
+ t('cli.help.docker'),
+ t('cli.help.nginx'),
+ t('cli.help.build'),
+ t('cli.help.publish'),
+ ];
+ for (const line of lines) {
+ console.log(brand.dim(line));
+ }
console.log('');
});
diff --git a/cli/lib/api-dev.js b/cli/lib/api-dev.js
index d6434ad..cac3fc4 100644
--- a/cli/lib/api-dev.js
+++ b/cli/lib/api-dev.js
@@ -1,8 +1,14 @@
-const { spawn, spawnSync } = require('child_process');
+const { spawn } = require('child_process');
const path = require('path');
const { ensureProjectEnvironment } = require('./bootstrap');
-const { isUsingMonorepoServer } = require('./paths');
-const { getMonorepoRoot } = require('./root');
+const {
+ getServerBin,
+ getServerDir,
+ isUsingMonorepoServer,
+ canStartLocalApi,
+} = require('./paths');
+const { stopApi } = require('./lifecycle');
+const { ensureOriginalCwd } = require('./root');
const { t } = require('./i18n');
let apiChild;
@@ -11,16 +17,13 @@ function stopApiDev(projectRoot) {
if (apiChild && !apiChild.killed) {
apiChild.kill('SIGTERM');
}
- if (!isUsingMonorepoServer()) {
- spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], {
- cwd: projectRoot,
- stdio: 'inherit',
- });
+ if (!isUsingMonorepoServer(projectRoot)) {
+ stopApi(projectRoot);
}
}
function startApiDev(projectRoot) {
- if (isUsingMonorepoServer()) {
+ if (isUsingMonorepoServer(projectRoot)) {
console.log(t('apiDev.modeServer'));
apiChild = spawn('pnpm', ['run', '--dir', './server', 'dev'], {
cwd: projectRoot,
@@ -31,18 +34,19 @@ function startApiDev(projectRoot) {
REACTPRESS_ORIGINAL_CWD: projectRoot,
},
});
- } else {
- console.log(t('apiDev.modeCli'));
- const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], {
- cwd: projectRoot,
+ } else if (canStartLocalApi(projectRoot)) {
+ console.log(t('apiDev.modeBundled'));
+ apiChild = spawn(process.execPath, [getServerBin(projectRoot)], {
+ cwd: getServerDir(projectRoot),
stdio: 'inherit',
+ env: {
+ ...process.env,
+ REACTPRESS_ORIGINAL_CWD: projectRoot,
+ },
});
- if (start.status !== 0) {
- process.exit(start.status ?? 1);
- }
- console.log(t('apiDev.startedByCli'));
- process.stdin.resume();
- return;
+ } else {
+ console.error(t('lifecycle.noServerAvailable'));
+ process.exit(1);
}
if (apiChild) {
@@ -54,7 +58,7 @@ function startApiDev(projectRoot) {
}
}
-async function runApiDev(projectRoot = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) {
+async function runApiDev(projectRoot = ensureOriginalCwd()) {
try {
await ensureProjectEnvironment(projectRoot);
} catch (err) {
diff --git a/cli/lib/bootstrap.js b/cli/lib/bootstrap.js
index 909a2c9..9d5dd7b 100644
--- a/cli/lib/bootstrap.js
+++ b/cli/lib/bootstrap.js
@@ -1,7 +1,7 @@
const fs = require('fs');
const path = require('path');
const { pathToFileURL } = require('url');
-const { getMonorepoRoot, isMonorepoCheckout } = require('./root');
+const { ensureOriginalCwd, isMonorepoCheckout } = require('./root');
const { getCliPackageRoot } = require('./paths');
const { t } = require('./i18n');
@@ -68,7 +68,7 @@ async function initMonorepoProject(projectRoot, { force = false } = {}) {
};
}
-async function ensureProjectEnvironment(projectRoot = getMonorepoRoot()) {
+async function ensureProjectEnvironment(projectRoot = ensureOriginalCwd()) {
const root = path.resolve(projectRoot);
const { setProjectCwd } = await importCliModule('utils/cli-context.js');
setProjectCwd(root);
diff --git a/cli/lib/build.js b/cli/lib/build.js
index 6036c42..ee70bc1 100644
--- a/cli/lib/build.js
+++ b/cli/lib/build.js
@@ -1,4 +1,7 @@
-const chalk = require('chalk');
+const fs = require('fs');
+const path = require('path');
+const ora = require('ora');
+const { brand, icon, ok, warn, label, chip } = require('../ui/theme');
const { runSync } = require('./spawn');
const { ensureOriginalCwd } = require('./root');
const { t } = require('./i18n');
@@ -22,6 +25,64 @@ const TARGETS = Object.keys(BUILD_STEPS);
const buildChildEnv = { REACTPRESS_BUILD_ACTIVE: '1' };
+function readPackageScripts(packageJsonPath) {
+ try {
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
+ return pkg.scripts || {};
+ } catch {
+ return {};
+ }
+}
+
+/** Prefer workspace package scripts over root package.json aliases. */
+function resolveBuildInvocation(script, projectRoot) {
+ const root = path.resolve(projectRoot);
+
+ if (script === 'build:toolkit') {
+ const toolkitDir = path.join(root, 'toolkit');
+ if (fs.existsSync(path.join(toolkitDir, 'package.json'))) {
+ return { command: 'pnpm', args: ['run', 'build'], cwd: toolkitDir };
+ }
+ const rootScripts = readPackageScripts(path.join(root, 'package.json'));
+ if (rootScripts['build:toolkit']) {
+ return { command: 'pnpm', args: ['run', 'build:toolkit'], cwd: root };
+ }
+ return null;
+ }
+
+ if (script === 'build:server') {
+ const serverDir = path.join(root, 'server');
+ if (fs.existsSync(path.join(serverDir, 'package.json'))) {
+ return { command: 'pnpm', args: ['run', 'build'], cwd: serverDir };
+ }
+ }
+
+ if (script === 'build:client') {
+ const clientDir = path.join(root, 'client');
+ if (fs.existsSync(path.join(clientDir, 'package.json'))) {
+ return { command: 'pnpm', args: ['run', 'build'], cwd: clientDir };
+ }
+ }
+
+ if (script === 'build:docs') {
+ const docsDir = path.join(root, 'docs');
+ if (fs.existsSync(path.join(docsDir, 'package.json'))) {
+ return { command: 'pnpm', args: ['run', 'build'], cwd: docsDir };
+ }
+ }
+
+ const rootScripts = readPackageScripts(path.join(root, 'package.json'));
+ if (rootScripts[script]) {
+ return { command: 'pnpm', args: ['run', script], cwd: root };
+ }
+
+ return null;
+}
+
+function stepBadge(current, total) {
+ return chip(`${current}/${total}`, brand.primary);
+}
+
async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
if (process.env.REACTPRESS_BUILD_ACTIVE === '1') {
throw new Error(t('build.recursive'));
@@ -40,8 +101,10 @@ async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
const total = steps.length;
const buildStarted = Date.now();
+ console.log('');
if (total > 1) {
- console.log(t('build.plan', { total }));
+ console.log(label(t('build.plan', { total })));
+ console.log('');
}
for (let i = 0; i < steps.length; i++) {
@@ -51,25 +114,49 @@ async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
}
const current = i + 1;
- const label = t(labelKey);
+ const stepLabel = t(labelKey);
const stepStarted = Date.now();
+ const badge = stepBadge(current, total);
+
+ const invocation = resolveBuildInvocation(script, projectRoot);
+ if (!invocation) {
+ console.log(` ${badge} ${warn(t('build.stepSkipped', { label: stepLabel }))}`);
+ continue;
+ }
+
+ const spinner = ora({
+ text: `${badge} ${t('build.step', { current, total, label: stepLabel })}`,
+ color: 'magenta',
+ spinner: 'dots',
+ }).start();
- console.log(t('build.step', { current, total, label }));
try {
- runSync('pnpm', ['run', script], { cwd: projectRoot, env: buildChildEnv });
+ runSync(invocation.command, invocation.args, {
+ cwd: invocation.cwd,
+ env: buildChildEnv,
+ });
} catch (err) {
- console.error(chalk.red(t('build.stepFailed', { current, total, label })));
+ spinner.fail(`${badge} ${t('build.stepFailed', { current, total, label: stepLabel })}`);
throw err;
}
const seconds = ((Date.now() - stepStarted) / 1000).toFixed(1);
- console.log(chalk.green(t('build.stepDone', { current, total, label, seconds })));
+ spinner.succeed(
+ `${badge} ${ok(t('build.stepDone', { current, total, label: stepLabel, seconds }))}`
+ );
}
if (total > 1) {
const totalSeconds = ((Date.now() - buildStarted) / 1000).toFixed(1);
- console.log(chalk.green(t('build.done', { seconds: totalSeconds })));
+ console.log('');
+ console.log(` ${icon.spark} ${ok(t('build.done', { seconds: totalSeconds }))}`);
}
+ console.log('');
}
-module.exports = { runBuild, TARGETS, BUILD_STEPS };
+module.exports = {
+ runBuild,
+ TARGETS,
+ BUILD_STEPS,
+ resolveBuildInvocation,
+};
diff --git a/cli/lib/db-backup.js b/cli/lib/db-backup.js
index 23152f0..729cb5f 100644
--- a/cli/lib/db-backup.js
+++ b/cli/lib/db-backup.js
@@ -3,6 +3,18 @@ const path = require('path');
const { execSync } = require('child_process');
const chalk = require('chalk');
const { t } = require('./i18n');
+const { mysqldumpFromDbContainer } = require('./docker');
+
+function isLocalDbHost(host) {
+ const h = String(host || '').toLowerCase();
+ return h === '127.0.0.1' || h === 'localhost' || h === '::1' || h === '';
+}
+
+function isMysqldumpNotFoundError(err) {
+ const msg = `${err && err.message ? err.message : ''}\n${err && err.stderr ? err.stderr : ''}`;
+ if (err && err.status === 127) return true;
+ return /command not found|not recognized as an internal or external command/i.test(msg);
+}
function parseEnv(projectRoot) {
const envPath = path.join(projectRoot, '.env');
@@ -38,6 +50,15 @@ async function runDbBackup(projectRoot, outputPath) {
console.log(chalk.green('[reactpress]'), t('db.backup.done'));
return out;
} catch (err) {
+ if (isMysqldumpNotFoundError(err) && isLocalDbHost(host)) {
+ const via = mysqldumpFromDbContainer(projectRoot, { user, password, database });
+ if (via.ok) {
+ console.log(chalk.cyan('[reactpress]'), t('db.backup.viaDocker'));
+ fs.writeFileSync(out, via.stdout, 'utf8');
+ console.log(chalk.green('[reactpress]'), t('db.backup.done'));
+ return out;
+ }
+ }
console.error(chalk.red('[reactpress]'), t('db.backup.fail'));
throw err;
}
diff --git a/cli/lib/dev-banner.js b/cli/lib/dev-banner.js
index a683f02..876f720 100644
--- a/cli/lib/dev-banner.js
+++ b/cli/lib/dev-banner.js
@@ -1,4 +1,15 @@
-const chalk = require('chalk');
+const {
+ brand,
+ icon,
+ ok,
+ divider,
+ padRight,
+ terminalWidth,
+ gradientText,
+ palette,
+ pulseBar,
+ statusLights,
+} = require('../ui/theme');
const {
loadClientSiteUrl,
loadServerSiteUrl,
@@ -20,20 +31,41 @@ function getDevUrls(projectRoot) {
};
}
+function urlLine(key, url, { underline = true } = {}) {
+ const keyCol = brand.muted(padRight(key, 10));
+ const value = underline ? brand.accent.underline(url) : brand.dim(url);
+ return ` ${brand.accent('▸ ')}${keyCol} ${value}`;
+}
+
function printDevReadyBanner(projectRoot, { apiOnly = false } = {}) {
const urls = getDevUrls(projectRoot);
+ const w = Math.min(terminalWidth() - 4, 56);
+
console.log('');
- console.log(chalk.bold.green(t('devBanner.ready')));
- console.log(chalk.gray(' ─────────────────────────────────────────'));
+ console.log(
+ ` ${icon.ok} ${gradientText(t('devBanner.ready'), [palette.green, palette.accent], { bold: true })} ${statusLights('online')}`
+ );
+ console.log(` ${brand.primary('╔' + '═'.repeat(w) + '╗')}`);
+
if (!apiOnly) {
- console.log(` ${chalk.cyan(t('devBanner.site'))} ${chalk.underline(urls.site)}`);
- console.log(` ${chalk.cyan(t('devBanner.admin'))} ${chalk.underline(urls.admin)}`);
+ console.log(urlLine(t('devBanner.site'), urls.site));
+ console.log(urlLine(t('devBanner.admin'), urls.admin));
+ }
+ console.log(urlLine(t('devBanner.api'), urls.api));
+ console.log(urlLine(t('devBanner.swagger'), urls.swagger));
+ console.log(urlLine(t('devBanner.health'), urls.health, { underline: false }));
+
+ const pulseWidth = Math.min(20, w - 4);
+ if (pulseWidth > 6) {
+ console.log(
+ ` ${brand.muted(' ')}${pulseBar(pulseWidth, pulseWidth)} ${brand.success(t('devBanner.allSystemsGo'))}`
+ );
}
- console.log(` ${chalk.cyan('API')} ${chalk.underline(urls.api)}`);
- console.log(` ${chalk.cyan('Swagger')} ${chalk.underline(urls.swagger)}`);
- console.log(` ${chalk.cyan(t('devBanner.health'))} ${urls.health}`);
- console.log(chalk.gray(' ─────────────────────────────────────────'));
- console.log(chalk.gray(t('devBanner.hint')));
+
+ console.log(` ${brand.primary('╚' + '═'.repeat(w) + '╝')}`);
+ console.log(
+ ` ${brand.dim(t('devBanner.hint'))} ${brand.muted('·')} ${brand.dim(t('devBanner.shortcuts'))}`
+ );
console.log('');
}
diff --git a/cli/lib/dev.js b/cli/lib/dev.js
index c8b701b..838585e 100644
--- a/cli/lib/dev.js
+++ b/cli/lib/dev.js
@@ -1,18 +1,24 @@
const { spawn } = require('child_process');
const path = require('path');
+const ora = require('ora');
const { runBuild } = require('./build');
const { ensureProjectEnvironment } = require('./bootstrap');
const { loadServerSiteUrl, loadClientSiteUrl, waitForHttp } = require('./http');
const { printDevReadyBanner } = require('./dev-banner');
const { ensureOriginalCwd } = require('./root');
+const { detectProjectType, hasClient, hasToolkit } = require('./project-type');
const { t } = require('./i18n');
const CLIENT_READY_TIMEOUT_MS = 120_000;
-
const API_READY_TIMEOUT_MS = 180_000;
function formatDevFailureHint() {
- return [t('dev.nextSteps'), t('dev.nextDoctor'), t('dev.nextDocker'), t('dev.nextEnv')].join('\n');
+ return [
+ t('dev.nextSteps'),
+ t('dev.nextDoctor'),
+ t('dev.nextDocker'),
+ t('dev.nextEnv'),
+ ].join('\n');
}
let apiChild;
@@ -22,22 +28,17 @@ let shuttingDown = false;
function shutdown(signal = 'SIGINT') {
if (shuttingDown) return;
shuttingDown = true;
- if (webChild && !webChild.killed) {
- webChild.kill(signal);
- }
- if (apiChild && !apiChild.killed) {
- apiChild.kill(signal);
- }
+ if (webChild && !webChild.killed) webChild.kill(signal);
+ if (apiChild && !apiChild.killed) apiChild.kill(signal);
}
async function buildToolkit(projectRoot) {
+ if (!hasToolkit(projectRoot)) return;
await runBuild('toolkit', projectRoot);
}
-async function startDevStack(projectRoot) {
- const serverUrl = loadServerSiteUrl(projectRoot);
+function spawnApi(projectRoot) {
const apiDevRunner = path.join(__dirname, 'api-dev-runner.js');
-
console.log(t('dev.startingApi'));
apiChild = spawn(process.execPath, [apiDevRunner], {
stdio: 'inherit',
@@ -53,25 +54,28 @@ async function startDevStack(projectRoot) {
process.exit(code ?? 0);
return;
}
- if (webChild && !webChild.killed) {
- webChild.kill('SIGINT');
- }
+ if (webChild && !webChild.killed) webChild.kill('SIGINT');
process.exit(code ?? 1);
});
+}
- console.log(t('dev.waitingApi', { url: serverUrl }));
+async function waitForApiReady(projectRoot) {
+ const serverUrl = loadServerSiteUrl(projectRoot);
+ const spinner = ora({
+ text: t('dev.waitingApi', { url: serverUrl }),
+ color: 'magenta',
+ spinner: 'dots',
+ }).start();
const ready = await waitForHttp(serverUrl, API_READY_TIMEOUT_MS);
if (!ready) {
- console.error(
- t('dev.apiTimeout', { seconds: API_READY_TIMEOUT_MS / 1000 }),
- );
+ spinner.fail(t('dev.apiTimeout', { seconds: API_READY_TIMEOUT_MS / 1000 }));
shutdown('SIGINT');
process.exit(1);
}
+ spinner.succeed(t('dev.apiReady'));
+}
- printDevReadyBanner(projectRoot, { apiOnly: true });
-
- console.log(t('dev.apiReady'));
+function spawnClient(projectRoot) {
webChild = spawn('pnpm', ['run', '--dir', './client', 'dev'], {
stdio: 'inherit',
shell: true,
@@ -87,28 +91,38 @@ async function startDevStack(projectRoot) {
t('dev.clientSlow', {
seconds: CLIENT_READY_TIMEOUT_MS / 1000,
url: clientUrl,
- }),
+ })
);
}
});
webChild.on('close', (code) => {
- if (!shuttingDown) {
- shutdown('SIGINT');
- }
+ if (!shuttingDown) shutdown('SIGINT');
process.exit(code ?? 0);
});
}
+async function startDevStack(projectRoot) {
+ const includeClient = hasClient(projectRoot);
+
+ spawnApi(projectRoot);
+ await waitForApiReady(projectRoot);
+ printDevReadyBanner(projectRoot, { apiOnly: !includeClient });
+
+ if (!includeClient) {
+ console.log(t('dev.standaloneHint'));
+ return;
+ }
+ spawnClient(projectRoot);
+}
+
async function runDev(projectRoot = ensureOriginalCwd()) {
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
try {
const result = await ensureProjectEnvironment(projectRoot);
- if (result.message) {
- console.log(`[reactpress] ${result.message}`);
- }
+ if (result.message) console.log(`[reactpress] ${result.message}`);
} catch (err) {
console.error(t('dev.envFailed'), err.message || err);
console.error(formatDevFailureHint());
@@ -119,4 +133,9 @@ async function runDev(projectRoot = ensureOriginalCwd()) {
await startDevStack(projectRoot);
}
-module.exports = { runDev, buildToolkit, startDevStack };
+module.exports = {
+ runDev,
+ buildToolkit,
+ startDevStack,
+ detectProjectType,
+};
diff --git a/cli/lib/docker.js b/cli/lib/docker.js
index ad6ac2d..cf4932b 100644
--- a/cli/lib/docker.js
+++ b/cli/lib/docker.js
@@ -1,6 +1,9 @@
-const { spawn, execSync } = require('child_process');
+const fs = require('fs');
const path = require('path');
+const { spawn, execSync, spawnSync } = require('child_process');
+const ora = require('ora');
const { ensureOriginalCwd } = require('./root');
+const { detectProjectType, hasClient } = require('./project-type');
const { t } = require('./i18n');
function isDockerRunning() {
@@ -12,18 +15,58 @@ function isDockerRunning() {
}
}
+function pickDockerComposeCommand() {
+ const v2 = spawnSync('docker', ['compose', 'version'], { stdio: 'ignore' });
+ if (v2.status === 0) return { command: 'docker', baseArgs: ['compose'] };
+
+ const v1 = spawnSync('docker-compose', ['version'], { stdio: 'ignore' });
+ if (v1.status === 0) return { command: 'docker-compose', baseArgs: [] };
+
+ return { command: 'docker', baseArgs: ['compose'] };
+}
+
+/**
+ * Resolve which docker-compose file to use for the current project.
+ *
+ * - Monorepo checkouts use `docker-compose.dev.yml` at the repo root.
+ * - Standalone projects use `.reactpress/docker-compose.yml` (managed by init).
+ *
+ * @returns {{ composeFile: string, cwd: string, type: 'monorepo' | 'standalone' }}
+ */
+function resolveComposeContext(projectRoot) {
+ const type = detectProjectType(projectRoot);
+ if (type === 'monorepo') {
+ const composeFile = path.join(projectRoot, 'docker-compose.dev.yml');
+ if (fs.existsSync(composeFile)) {
+ return { composeFile, cwd: projectRoot, type };
+ }
+ }
+ const standaloneCompose = path.join(projectRoot, '.reactpress', 'docker-compose.yml');
+ if (fs.existsSync(standaloneCompose)) {
+ return { composeFile: standaloneCompose, cwd: path.dirname(standaloneCompose), type: 'standalone' };
+ }
+ const fallback = path.join(projectRoot, 'docker-compose.dev.yml');
+ return { composeFile: fallback, cwd: projectRoot, type };
+}
+
+function runCompose(args, ctx, options = {}) {
+ const { command, baseArgs } = pickDockerComposeCommand();
+ return spawnSync(
+ command,
+ [...baseArgs, '-f', ctx.composeFile, ...args],
+ { stdio: options.stdio ?? 'inherit', cwd: ctx.cwd, ...options }
+ );
+}
+
function stopDockerServices(projectRoot) {
console.log(t('docker.stopping'));
- try {
- execSync('docker-compose -f docker-compose.dev.yml down', {
- stdio: 'inherit',
- cwd: projectRoot,
- });
- console.log(t('docker.stopped'));
- } catch (error) {
- console.error(t('docker.stopFailed'), error.message);
- throw error;
+ const ctx = resolveComposeContext(projectRoot);
+ const result = runCompose(['down'], ctx);
+ if (result.status !== 0) {
+ console.error(t('docker.stopFailed'));
+ throw new Error(t('docker.stopFailed'));
}
+ console.log(t('docker.stopped'));
}
function startDockerServices(projectRoot) {
@@ -31,42 +74,86 @@ function startDockerServices(projectRoot) {
if (!isDockerRunning()) {
throw new Error(t('docker.notRunning'));
}
- execSync('docker-compose -f docker-compose.dev.yml up -d', {
- stdio: 'inherit',
- cwd: projectRoot,
- });
+ try {
+ const { ensureNginxConfig } = require('./nginx');
+ const { configPath, created } = ensureNginxConfig(projectRoot, { mode: 'dev' });
+ if (created) {
+ console.log(t('nginx.configCreated', { path: configPath }));
+ }
+ } catch (err) {
+ console.warn(t('nginx.ensureWarn', { message: err.message || err }));
+ }
+ const ctx = resolveComposeContext(projectRoot);
+ const result = runCompose(['up', '-d'], ctx);
+ if (result.status !== 0) {
+ throw new Error(t('docker.notRunning'));
+ }
console.log(t('docker.started'));
}
-async function waitForMysql(maxAttempts = 30) {
- console.log(t('docker.waitingMysql'));
+function resolveDbContainerName(ctx, projectRoot) {
+ if (ctx.type === 'standalone') return 'reactpress_cli_db';
+ return 'reactpress_db';
+}
+
+function resolveDbCredentialsFromEnv(projectRoot) {
+ const envPath = path.join(projectRoot, '.env');
+ let user = 'reactpress';
+ let password = 'reactpress';
+ try {
+ const content = fs.readFileSync(envPath, 'utf8');
+ const u = content.match(/^DB_USER=(.+)$/m);
+ const p = content.match(/^(DB_PASSWD|DB_PASSWORD)=(.+)$/m);
+ if (u) user = u[1].trim().replace(/^['"]|['"]$/g, '');
+ if (p) password = p[2].trim().replace(/^['"]|['"]$/g, '');
+ } catch {
+ // ignore
+ }
+ return { user, password };
+}
+
+async function waitForMysql(projectRoot, maxAttempts = 30) {
+ const ctx = resolveComposeContext(projectRoot);
+ const container = resolveDbContainerName(ctx, projectRoot);
+ const { user, password } = resolveDbCredentialsFromEnv(projectRoot);
+
+ const spinner = ora({
+ text: t('docker.waitingMysql'),
+ color: 'magenta',
+ spinner: 'dots',
+ }).start();
+
let attempts = 0;
while (attempts < maxAttempts) {
- try {
- execSync('docker exec reactpress_db mysql -u reactpress -preactpress -e "SELECT 1"', {
- stdio: 'ignore',
- });
- console.log(t('docker.mysqlReady'));
+ const probe = spawnSync(
+ 'docker',
+ ['exec', container, 'mysql', `-u${user}`, `-p${password}`, '-e', 'SELECT 1'],
+ { stdio: 'ignore' }
+ );
+ if (probe.status === 0) {
+ spinner.succeed(t('docker.mysqlReady'));
return true;
- } catch {
- attempts += 1;
- if (attempts % 5 === 0) {
- console.log(t('docker.waitingMysqlProgress', { attempts, max: maxAttempts }));
- }
- await new Promise((r) => setTimeout(r, 1000));
}
+ attempts += 1;
+ spinner.text = t('docker.waitingMysqlProgress', { attempts, max: maxAttempts });
+ await new Promise((r) => setTimeout(r, 1000));
}
- console.error(t('docker.mysqlTimeout'));
+ spinner.fail(t('docker.mysqlTimeout'));
return false;
}
async function dockerStartWithDev(projectRoot) {
startDockerServices(projectRoot);
- const ready = await waitForMysql();
+ const ready = await waitForMysql(projectRoot);
if (!ready) {
throw new Error(t('docker.mysqlNotReady'));
}
+ if (!hasClient(projectRoot)) {
+ console.log(t('dev.standaloneHint'));
+ return;
+ }
+
const { buildToolkit } = require('./dev');
await buildToolkit(projectRoot);
@@ -108,39 +195,67 @@ async function dockerStartWithDev(projectRoot) {
});
}
+/**
+ * Run mysqldump inside the compose `db` container (MySQL image ships mysqldump).
+ * Used when the host has no `mysqldump` binary but Docker DB is running.
+ *
+ * @returns {{ ok: true, stdout: string } | { ok: false, stderr: string }}
+ */
+function mysqldumpFromDbContainer(projectRoot, { user, password, database }) {
+ const ctx = resolveComposeContext(projectRoot);
+ if (!fs.existsSync(ctx.composeFile)) {
+ return { ok: false, stderr: 'compose file missing' };
+ }
+ if (!isDockerRunning()) {
+ return { ok: false, stderr: 'docker not running' };
+ }
+ const container = resolveDbContainerName(ctx, projectRoot);
+ const res = spawnSync(
+ 'docker',
+ ['exec', container, 'mysqldump', `-u${user}`, `-p${password}`, database],
+ { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 }
+ );
+ if (res.error) {
+ return { ok: false, stderr: res.error.message };
+ }
+ if (res.status !== 0) {
+ return { ok: false, stderr: res.stderr || res.stdout || `exit ${res.status}` };
+ }
+ return { ok: true, stdout: res.stdout };
+}
+
async function runDockerCommand(command, projectRoot = ensureOriginalCwd(), extraArgs = []) {
+ const ctx = resolveComposeContext(projectRoot);
switch (command) {
case 'up':
startDockerServices(projectRoot);
- await waitForMysql();
+ await waitForMysql(projectRoot);
return;
case 'down':
+ case 'stop':
stopDockerServices(projectRoot);
return;
case 'start':
await dockerStartWithDev(projectRoot);
return;
- case 'stop':
- stopDockerServices(projectRoot);
- return;
case 'restart':
stopDockerServices(projectRoot);
await new Promise((r) => setTimeout(r, 2000));
startDockerServices(projectRoot);
- await waitForMysql();
+ await waitForMysql(projectRoot);
return;
- case 'status':
- execSync('docker-compose -f docker-compose.dev.yml ps', {
- stdio: 'inherit',
- cwd: projectRoot,
- });
+ case 'status': {
+ const res = runCompose(['ps'], ctx);
+ if (res.status !== 0) {
+ throw new Error(t('docker.unknownCommand', { command: 'ps' }));
+ }
return;
+ }
case 'logs': {
- const service = extraArgs[0] || '';
- execSync(`docker-compose -f docker-compose.dev.yml logs -f ${service}`.trim(), {
- stdio: 'inherit',
- cwd: projectRoot,
- });
+ const service = extraArgs[0];
+ const args = ['logs', '-f'];
+ if (service) args.push(service);
+ runCompose(args, ctx);
return;
}
default:
@@ -154,4 +269,7 @@ module.exports = {
stopDockerServices,
waitForMysql,
isDockerRunning,
+ resolveComposeContext,
+ pickDockerComposeCommand,
+ mysqldumpFromDbContainer,
};
diff --git a/cli/lib/doctor.js b/cli/lib/doctor.js
index 8798fc6..13d0e8e 100644
--- a/cli/lib/doctor.js
+++ b/cli/lib/doctor.js
@@ -2,9 +2,21 @@ const fs = require('fs');
const net = require('net');
const path = require('path');
const { execSync } = require('child_process');
-const chalk = require('chalk');
+const ora = require('ora');
+const {
+ brand,
+ icon,
+ ok,
+ warn,
+ divider,
+ sectionHeader,
+ terminalWidth,
+ gradientText,
+ palette,
+} = require('../ui/theme');
const { getHealthUrl, checkHealth } = require('./http');
const { isDockerRunning } = require('./docker');
+const { checkNginx } = require('./nginx');
const { envFileStatus } = require('./status');
const { t } = require('./i18n');
@@ -64,6 +76,16 @@ async function checkPorts(projectRoot) {
const env = parseEnv(projectRoot);
const apiPort = parseInt(env.SERVER_PORT || '3002', 10);
const clientPort = parseInt(env.CLIENT_PORT || '3001', 10);
+
+ const healthUrl = getHealthUrl(projectRoot);
+ const apiHealth = await checkHealth(healthUrl);
+ if (apiHealth.ok) {
+ return {
+ ok: true,
+ message: t('doctor.portOk', { apiPort, clientPort }),
+ };
+ }
+
const [apiBusy, clientBusy] = await Promise.all([checkPort(apiPort), checkPort(clientPort)]);
const issues = [];
if (apiBusy) issues.push(t('doctor.portApiBusy', { port: apiPort }));
@@ -152,6 +174,21 @@ function checkPnpm() {
}
}
+async function runCheckWithSpinner(name, run) {
+ const spinner = ora({
+ text: t('doctor.checking', { name }),
+ color: 'magenta',
+ spinner: 'dots',
+ }).start();
+ const result = await run();
+ if (result.ok) {
+ spinner.stop();
+ } else {
+ spinner.stop();
+ }
+ return result;
+}
+
async function runDoctor(projectRoot) {
const env = envFileStatus(projectRoot);
const checks = [
@@ -174,34 +211,53 @@ async function runDoctor(projectRoot) {
}),
},
{ name: 'Docker', run: () => checkDocker() },
+ { name: t('doctor.check.nginx'), run: () => checkNginx(projectRoot) },
{ name: t('doctor.check.ports'), run: () => checkPorts(projectRoot) },
{ name: t('doctor.check.database'), run: () => checkDatabase(projectRoot) },
{ name: t('doctor.check.api'), run: () => checkApiHealth(projectRoot) },
];
+ const w = Math.min(terminalWidth() - 4, 52);
+ const results = [];
+ const fixes = [];
+
console.log('');
- console.log(chalk.bold.cyan(' ReactPress Doctor'));
- console.log(chalk.gray(t('doctor.project', { path: projectRoot })));
- console.log(chalk.gray(' ─────────────────────────────────────'));
+ console.log(
+ ` ${gradientText(t('doctor.title'), [palette.primary, palette.accent], { bold: true })} ${brand.dim(t('doctor.subtitle'))}`
+ );
+ console.log(` ${brand.dim(t('doctor.project', { path: projectRoot }))}`);
+ console.log(` ${divider(w)}`);
- let failed = 0;
for (const { name, run } of checks) {
- const result = await run();
- const icon = result.ok ? chalk.green('✓') : chalk.red('✗');
- console.log(` ${icon} ${chalk.bold(name)} ${result.message}`);
+ const result = await runCheckWithSpinner(name, run);
+ results.push({ name, ...result });
+ const mark = result.ok ? icon.ok : icon.fail;
+ const msgColor = result.ok ? brand.dim : brand.warn;
+ console.log(` ${mark} ${brand.bold(name)} ${msgColor(result.message)}`);
if (!result.ok && result.fix) {
- console.log(chalk.yellow(` → ${result.fix}`));
- failed += 1;
- } else if (!result.ok) {
- failed += 1;
+ fixes.push({ name, fix: result.fix });
}
}
- console.log(chalk.gray(' ─────────────────────────────────────'));
+ const passed = results.filter((r) => r.ok).length;
+ const failed = results.length - passed;
+
+ console.log(` ${divider(w)}`);
+ console.log(
+ ` ${brand.dim(t('doctor.summary', { passed, failed, total: results.length }))}`
+ );
+
if (failed === 0) {
- console.log(chalk.green(t('doctor.allPass')));
+ console.log(` ${ok(t('doctor.allPass'))}`);
} else {
- console.log(chalk.yellow(t('doctor.failed', { count: failed })));
+ console.log(` ${warn(t('doctor.failed', { count: failed }))}`);
+ if (fixes.length) {
+ console.log('');
+ console.log(sectionHeader(t('doctor.fixesHeader')));
+ for (const { name, fix } of fixes) {
+ console.log(` ${brand.primary('→')} ${brand.dim(name)} ${brand.warn(fix)}`);
+ }
+ }
}
console.log('');
return failed === 0 ? 0 : 1;
diff --git a/cli/lib/http.js b/cli/lib/http.js
index 23b23e1..403c486 100644
--- a/cli/lib/http.js
+++ b/cli/lib/http.js
@@ -50,13 +50,13 @@ function getHealthUrl(projectRoot) {
return `${base}${prefix}/health`;
}
-function checkHealth(url, timeoutMs = 3000) {
+function probeHttp(url, timeoutMs = 3000) {
return new Promise((resolve) => {
let parsed;
try {
parsed = new URL(url);
} catch {
- resolve({ ok: false });
+ resolve({ ok: false, statusCode: 0, data: null });
return;
}
const port = parsed.port || (parsed.protocol === 'https:' ? 443 : 80);
@@ -87,13 +87,48 @@ function checkHealth(url, timeoutMs = 3000) {
);
req.on('timeout', () => {
req.destroy();
- resolve({ ok: false });
+ resolve({ ok: false, statusCode: 0, data: null });
});
- req.on('error', () => resolve({ ok: false }));
+ req.on('error', () => resolve({ ok: false, statusCode: 0, data: null }));
req.end();
});
}
+/**
+ * Health probe: prefers `/api/health` JSON; falls back to API prefix (e.g. Swagger)
+ * for older bundled servers that omit the health route.
+ */
+async function checkHealth(url, timeoutMs = 3000) {
+ const primary = await probeHttp(url, timeoutMs);
+ if (primary.ok) return primary;
+
+ if (primary.statusCode === 404 || primary.statusCode === 0) {
+ try {
+ const parsed = new URL(url);
+ const prefix = parsed.pathname.replace(/\/health\/?$/, '') || '/api';
+ const candidates = [
+ `${parsed.origin}${prefix}/`,
+ `${parsed.origin}${prefix}`,
+ parsed.origin,
+ ];
+ for (const fallback of candidates) {
+ const alt = await probeHttp(fallback, timeoutMs);
+ if (alt.statusCode === 200) {
+ return {
+ ok: true,
+ statusCode: 200,
+ data: { status: 'ok', database: 'unknown' },
+ };
+ }
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ return primary;
+}
+
function isHttpResponding(url, timeoutMs = 2000) {
return new Promise((resolve) => {
let parsed;
diff --git a/cli/lib/i18n/strings.js b/cli/lib/i18n/strings.js
index 1001fff..4469cc2 100644
--- a/cli/lib/i18n/strings.js
+++ b/cli/lib/i18n/strings.js
@@ -26,6 +26,19 @@ const STRINGS = {
'cli.docker.restart': 'Restart Docker services',
'cli.docker.status': 'Docker container status',
'cli.docker.logs': 'Docker logs (db | nginx)',
+ 'cli.nginx.description': 'Nginx reverse proxy (unified entry on :80)',
+ 'cli.nginx.ensure': 'Write default nginx config if missing',
+ 'cli.nginx.up': 'Start nginx container',
+ 'cli.nginx.down': 'Stop nginx container',
+ 'cli.nginx.restart': 'Restart nginx container',
+ 'cli.nginx.status': 'Show nginx container and config status',
+ 'cli.nginx.logs': 'Follow nginx container logs',
+ 'cli.nginx.test': 'Validate nginx config inside container (nginx -t)',
+ 'cli.nginx.reload': 'Reload nginx after config change',
+ 'cli.nginx.open': 'Open nginx entry URL in browser',
+ 'cli.nginx.prod': 'Use production compose + nginx.conf (monorepo only)',
+ 'cli.nginx.force': 'Overwrite existing nginx config from template',
+ 'cli.help.nginx': ' reactpress nginx up Start reverse proxy (:80)',
'cli.status.description': 'Project, API, frontend, and Docker status',
'cli.doctor.description': 'Diagnose Node, Docker, ports, database, and API health',
'cli.db.description': 'Database operations',
@@ -47,6 +60,19 @@ const STRINGS = {
'cli.help.publish': ' reactpress publish Publish npm packages',
'cli.build.target': 'Build target: toolkit | server | client | docs | all',
'banner.subtitle': ' · Full-stack publishing CLI ',
+ /** Left label for the decorative pulse bar (not a URL — the repo link
+ * lives directly under the title bar at the top of the card). */
+ 'banner.pulseLabel': 'Setup',
+ 'banner.pulseReady': 'READY',
+ 'banner.pulsePending': 'INIT',
+ 'banner.label.mode': 'MODE',
+ 'banner.label.path': 'PATH',
+ 'banner.mode.standalone': 'STANDALONE',
+ 'banner.mode.monorepo': 'MONOREPO',
+ 'banner.mode.uninitialized': 'UNINITIALIZED',
+ 'banner.systemLabel': 'SYSTEM',
+ 'banner.systemOnline': 'ONLINE',
+ 'banner.systemPending': 'PENDING',
'menu.dev': 'Zero-config dev (env + DB + API + frontend)',
'menu.init': 'Initialize project (.reactpress + .env + database)',
'menu.status': 'View project status',
@@ -73,6 +99,55 @@ const STRINGS = {
'menu.done': 'Done',
'menu.opening': 'Opening {url}',
'menu.goodbye': ' Goodbye.',
+ 'menu.section.run': 'Run',
+ 'menu.section.lifecycle': 'Lifecycle',
+ 'menu.section.build': 'Build & Deploy',
+ 'menu.section.tools': 'Tools',
+ 'menu.tip': 'Tip: arrow keys to navigate, Enter to select, Ctrl+C to quit.',
+ 'menu.shortcuts': '↑/↓ navigate · enter select · esc back · ctrl+c quit',
+ 'menu.statusHeader': 'Status',
+ 'menu.contextStandalone': 'Project mode · standalone (using bundled API)',
+ 'menu.contextMonorepo': 'Project mode · monorepo (server/src + client/)',
+ 'menu.contextUnknown': 'Project mode · not initialized (run `init`)',
+ 'menu.statusApi': 'API {status}',
+ 'menu.statusDb': 'DB {status}',
+ 'menu.statusDocker': 'Docker {status}',
+ 'menu.statusLabelApi': 'API',
+ 'menu.statusLabelDb': 'DB',
+ 'menu.statusLabelDocker': 'Docker',
+ 'menu.statusChecking': 'checking…',
+ 'menu.startingApi': 'Starting API…',
+ 'menu.stoppingApi': 'Stopping API…',
+ 'menu.restartingApi': 'Restarting API…',
+ 'menu.statusOn': 'online',
+ 'menu.statusOff': 'offline',
+ 'menu.statusReady': 'ready',
+ 'menu.statusNotReady': 'not ready',
+ 'menu.statusYes': 'available',
+ 'menu.statusNo': 'unavailable',
+ 'menu.hint.dev': 'API + DB + frontend',
+ 'menu.hint.init': '.reactpress + .env',
+ 'menu.hint.status': 'all services overview',
+ 'menu.hint.doctor': 'environment diagnostics',
+ 'menu.hint.devApi': 'watch mode',
+ 'menu.hint.devClient': 'Next.js dev',
+ 'menu.hint.serverStart': 'production background',
+ 'menu.hint.serverStop': '',
+ 'menu.hint.serverRestart': '',
+ 'menu.hint.build': 'production output',
+ 'menu.hint.dockerStart': 'DB + nginx + dev stack',
+ 'menu.hint.dockerUp': 'database only',
+ 'menu.hint.dockerStop': '',
+ 'menu.nginxUp': 'Start nginx reverse proxy (:80)',
+ 'menu.nginxOpen': 'Open nginx entry in browser',
+ 'menu.nginxReload': 'Reload nginx after editing config',
+ 'menu.hint.nginxUp': 'unified entry :80',
+ 'menu.hint.nginxOpen': 'http://localhost',
+ 'menu.hint.nginxReload': 'nginx -t && reload',
+ 'menu.hint.openAdmin': 'opens in browser',
+ 'menu.hint.publish': 'maintainers only',
+ 'menu.hint.exit': '',
+ 'menu.actionPrefix': 'action',
'dev.startingApi': '[reactpress] Starting API (first run may install deps)…',
'dev.waitingApi': '[reactpress] Waiting for API: {url}',
'dev.apiTimeout': '[reactpress] API not ready within {seconds}s.\n → Run reactpress doctor\n → Embedded MySQL: reactpress docker up\n → Check DB_* and SERVER_SITE_URL in .env',
@@ -84,11 +159,16 @@ const STRINGS = {
'dev.nextDoctor': ' → reactpress doctor Diagnostics',
'dev.nextDocker': ' → reactpress docker up Start embedded MySQL',
'dev.nextEnv': ' → Check DB_* and SERVER_SITE_URL in .env',
- 'devBanner.ready': ' ✓ ReactPress dev environment is ready',
+ 'dev.standaloneHint': '[reactpress] Standalone project: only API is started here; build your own frontend separately.',
+ 'devBanner.ready': 'ReactPress dev environment is ready',
'devBanner.site': 'Site',
'devBanner.admin': 'Admin',
+ 'devBanner.api': 'API',
+ 'devBanner.swagger': 'Swagger',
'devBanner.health': 'Health',
- 'devBanner.hint': ' Diagnostics: reactpress doctor · Status: reactpress status',
+ 'devBanner.hint': 'Diagnostics: reactpress doctor · Status: reactpress status',
+ 'devBanner.shortcuts': 'Ctrl+C stop',
+ 'devBanner.allSystemsGo': 'ALL SYSTEMS GO',
'doctor.nodeBad': 'Node.js {version} (requires ≥ 18)',
'doctor.nodeFix': 'Install Node.js 18+: https://nodejs.org/',
'doctor.dockerOk': 'Docker engine is available',
@@ -119,17 +199,22 @@ const STRINGS = {
'doctor.envOk': '.env exists',
'doctor.envBad': 'Missing .env',
'doctor.envFix': 'Run reactpress init or reactpress config --apply',
- 'doctor.project': ' Project: {path}',
- 'doctor.allPass': ' All checks passed. You can start developing.',
- 'doctor.failed': ' {count} item(s) need attention.',
- 'status.title': ' ReactPress project status',
- 'status.dir': ' Project {path}',
- 'status.apiSource': ' API source {source}',
+ 'doctor.project': 'Project {path}',
+ 'doctor.allPass': 'All checks passed. You can start developing.',
+ 'doctor.failed': '{count} item(s) need attention.',
+ 'doctor.title': 'ReactPress Doctor',
+ 'doctor.subtitle': 'environment diagnostics',
+ 'doctor.checking': 'Checking {name}…',
+ 'doctor.summary': '{passed} passed · {failed} failed · {total} total',
+ 'doctor.fixesHeader': 'Suggested fixes',
+ 'status.title': 'ReactPress project status',
+ 'status.dir': 'Project {path}',
+ 'status.apiSource': 'API source {source}',
'status.apiSource.monorepo': 'monorepo server/',
'status.apiSource.bundle': 'reactpress-cli',
- 'status.configOk': '.reactpress/config.json ✓',
+ 'status.configOk': '.reactpress/config.json',
'status.configBad': 'not initialized',
- 'status.envOk': '.env ✓',
+ 'status.envOk': '.env',
'status.envBad': 'missing .env',
'status.apiOnline': 'online',
'status.apiOffline': 'offline',
@@ -137,10 +222,24 @@ const STRINGS = {
'status.dbUp': 'connected',
'status.dbDown': 'unavailable',
'status.pidRunning': '(running)',
- 'status.frontend': ' Frontend',
- 'status.docker': ' Docker',
+ 'status.frontend': 'Frontend',
+ 'status.docker': 'Docker',
'status.dockerUp': 'available',
'status.dockerDown': 'not running',
+ 'status.section.project': 'Project',
+ 'status.section.api': 'API service',
+ 'status.section.frontend': 'Frontend',
+ 'status.section.docker': 'Docker',
+ 'status.field.url': 'URL',
+ 'status.field.http': 'HTTP',
+ 'status.field.health': 'Health',
+ 'status.field.database': 'Database',
+ 'status.field.pid': 'PID',
+ 'status.field.engine': 'Engine',
+ 'status.field.config': 'Config',
+ 'status.field.env': '.env',
+ 'status.field.source': 'Source',
+ 'status.field.dir': 'Directory',
'bootstrap.configReady': 'Config exists and database is ready.',
'bootstrap.projectDbPending': 'Project created, but database is not ready: {message}. Start Docker and run reactpress dev again.',
'bootstrap.ready': 'ReactPress dev environment is ready (config + database).',
@@ -151,7 +250,9 @@ const STRINGS = {
'bootstrap.dbReady': 'Database is ready',
'db.backup.to': 'Backing up database to {path}',
'db.backup.done': 'Backup complete',
- 'db.backup.fail': 'mysqldump failed; ensure MySQL client is installed and .env is correct',
+ 'db.backup.viaDocker': 'mysqldump not on PATH; using mysqldump inside the db container…',
+ 'db.backup.fail':
+ 'mysqldump failed; install a MySQL client (e.g. brew install mysql-client), or ensure Docker db is running for automatic container backup',
'common.done': 'Done',
'common.yes': 'yes',
'common.no': 'no',
@@ -160,15 +261,16 @@ const STRINGS = {
'lifecycle.apiStopped': '[reactpress] API process stopped (pid {pid})',
'lifecycle.stopPidFailed': '[reactpress] Failed to stop pid {pid}:',
'lifecycle.apiAlreadyRunning': '[reactpress] API already running (pid {pid})',
- 'lifecycle.noServerSrc': '[reactpress] server/src not found, falling back to reactpress-cli start',
+ 'lifecycle.noServerAvailable': '[reactpress] No API runtime found. Reinstall @fecommunity/reactpress or run from a project with server/src.',
'lifecycle.startingLocalApi': '[reactpress] Starting local API (server/)…',
+ 'lifecycle.startingBundledApi': '[reactpress] Starting bundled API…',
'lifecycle.apiStartedBg': '[reactpress] API started in background (pid {pid})',
'lifecycle.apiTimeout120': '[reactpress] API not ready within 120s: {url}',
'lifecycle.apiReady': '[reactpress] API ready: {url}',
'lifecycle.apiStatusTitle': '[reactpress] API status',
'lifecycle.source': ' Source: {source}',
'lifecycle.source.monorepo': 'monorepo server/',
- 'lifecycle.source.bundle': 'reactpress-cli bundle',
+ 'lifecycle.source.bundle': 'bundled API (@fecommunity/reactpress)',
'lifecycle.pidFile': ' PID file: {path}',
'lifecycle.recordedPid': ' Recorded PID: {pid}',
'lifecycle.processAlive': ' Process alive: {alive}',
@@ -188,22 +290,50 @@ const STRINGS = {
'docker.mysqlTimeout': '[reactpress] MySQL not ready within timeout.',
'docker.mysqlNotReady': 'MySQL is not ready',
'docker.startDevStack': '[reactpress] Starting API + frontend (Docker MySQL)…',
- 'docker.visitUrls': '[reactpress] Visit: http://localhost:8080 (nginx) / http://localhost:3001 (client)',
+ 'docker.visitUrls': '[reactpress] Visit: http://localhost (nginx) / http://localhost:3001 (client)',
'docker.devProcessExit': 'Dev process exited with code {code}',
'docker.unknownCommand': 'Unknown docker command: {command}',
+ 'nginx.configCreated': '[reactpress] Created nginx config: {path}',
+ 'nginx.configExists': '[reactpress] Nginx config already exists: {path}',
+ 'nginx.ensureWarn': '[reactpress] Could not ensure nginx config: {message}',
+ 'nginx.started': '[reactpress] Nginx started — {url}',
+ 'nginx.configPath': '[reactpress] Config: {path}',
+ 'nginx.stopped': '[reactpress] Nginx stopped.',
+ 'nginx.startFailed': 'Failed to start nginx container',
+ 'nginx.prodMonorepoOnly': 'Production nginx (--prod) requires monorepo with docker-compose.prod.yml',
+ 'nginx.statusTitle': '[reactpress] Nginx status',
+ 'nginx.statusContainer': ' Container {name}: {running}',
+ 'nginx.statusConfig': ' Config {path}: {exists}',
+ 'nginx.statusUrl': ' Entry {url} (port {port})',
+ 'nginx.statusMode': ' Mode: {mode}',
+ 'nginx.notRunning': 'Nginx container is not running. Run: reactpress nginx up',
+ 'nginx.testOk': '[reactpress] Nginx config test passed.',
+ 'nginx.testFailed': 'Nginx config test failed',
+ 'nginx.reloadOk': '[reactpress] Nginx reloaded.',
+ 'nginx.reloadFailed': 'Nginx reload failed',
+ 'nginx.opening': '[reactpress] Opening {url}',
+ 'nginx.unknownCommand': 'Unknown nginx command: {command}',
+ 'nginx.templateMissing': 'Bundled nginx template missing: {path}',
+ 'nginx.doctorSkippedDocker': 'Skipped (Docker not running)',
+ 'nginx.doctorSkippedNotRunning': 'Not started (optional: reactpress nginx up)',
+ 'nginx.doctorNotRunningFix': 'reactpress nginx up (or reactpress docker up)',
+ 'nginx.doctorOk': 'Nginx healthy ({url}/health)',
+ 'nginx.doctorUnhealthy': 'Nginx running but /health failed ({url})',
+ 'nginx.doctorUnhealthyFix': 'Ensure client (:3001) and API (:3002) are running; reactpress nginx reload',
+ 'doctor.check.nginx': 'Nginx proxy',
'apiDev.modeServer': '[reactpress] Dev mode: server/ (nest start --watch)',
- 'apiDev.modeCli': '[reactpress] Dev mode: reactpress-cli (server/src not found)',
- 'apiDev.startedByCli': '[reactpress] API started via reactpress-cli.',
+ 'apiDev.modeBundled': '[reactpress] Dev mode: bundled API (built-in server)',
'apiDev.ctrlCHint': '[reactpress] Press Ctrl+C to stop API.',
'apiDev.stopHint': '[reactpress] Stop separately: reactpress server stop',
'build.unknownTarget': 'Unknown build target: {target}. Available: {available}',
'build.recursive': 'Build recursion detected (pnpm run build must not call itself). Use build:toolkit, build:server, or build:client.',
'build.forbiddenScript': 'Invalid build script "{script}"; use granular scripts like build:toolkit.',
- 'build.stepFailed': '[reactpress] [{current}/{total}] {label} failed',
- 'build.plan': '[reactpress] Production build — {total} step(s): toolkit → server → client',
- 'build.step': '[reactpress] [{current}/{total}] {label}…',
- 'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)',
- 'build.done': '[reactpress] Build finished in {seconds}s',
+ 'build.stepFailed': '[{current}/{total}] {label} failed',
+ 'build.plan': 'Production build — {total} step(s): toolkit → server → client',
+ 'build.step': '[{current}/{total}] {label}',
+ 'build.stepDone': '[{current}/{total}] {label} ({seconds}s)',
+ 'build.stepSkipped': 'skipped {label} (not part of this project layout)',
+ 'build.done': 'Build finished in {seconds}s',
'build.label.toolkit': 'Toolkit',
'build.label.server': 'API (server)',
'build.label.client': 'Frontend (client)',
@@ -317,6 +447,19 @@ const STRINGS = {
'cli.docker.restart': '重启 Docker 服务',
'cli.docker.status': '查看 Docker 容器状态',
'cli.docker.logs': '查看 Docker 日志 (db | nginx)',
+ 'cli.nginx.description': 'Nginx 反向代理(统一入口 :80)',
+ 'cli.nginx.ensure': '若缺失则生成默认 nginx 配置',
+ 'cli.nginx.up': '启动 nginx 容器',
+ 'cli.nginx.down': '停止 nginx 容器',
+ 'cli.nginx.restart': '重启 nginx 容器',
+ 'cli.nginx.status': '查看 nginx 容器与配置状态',
+ 'cli.nginx.logs': '跟踪 nginx 容器日志',
+ 'cli.nginx.test': '在容器内校验配置 (nginx -t)',
+ 'cli.nginx.reload': '修改配置后热加载 nginx',
+ 'cli.nginx.open': '在浏览器打开 nginx 入口',
+ 'cli.nginx.prod': '使用生产 compose + nginx.conf(仅 monorepo)',
+ 'cli.nginx.force': '用模板覆盖已有 nginx 配置',
+ 'cli.help.nginx': ' reactpress nginx up 启动反向代理 (:80)',
'cli.status.description': '查看项目、API、前端、Docker 综合状态',
'cli.doctor.description': '诊断环境:Node、Docker、端口、数据库、API 健康',
'cli.db.description': '数据库运维',
@@ -338,6 +481,18 @@ const STRINGS = {
'cli.help.publish': ' reactpress publish 发布 npm 包',
'cli.build.target': '构建目标: toolkit | server | client | docs | all',
'banner.subtitle': ' · 全栈发布平台 CLI ',
+ /** 左侧装饰性进度条标签(不是网址;仓库地址放在卡片顶部 Title 正下方)。 */
+ 'banner.pulseLabel': '准备',
+ 'banner.pulseReady': '就绪',
+ 'banner.pulsePending': '初始化',
+ 'banner.label.mode': '模式',
+ 'banner.label.path': '路径',
+ 'banner.mode.standalone': '独立项目',
+ 'banner.mode.monorepo': 'MONOREPO',
+ 'banner.mode.uninitialized': '未初始化',
+ 'banner.systemLabel': '系统',
+ 'banner.systemOnline': '在线',
+ 'banner.systemPending': '准备中',
'menu.dev': '零配置开发 (env + DB + API + 前端)',
'menu.init': '初始化项目 (.reactpress + .env + 数据库)',
'menu.status': '查看项目状态',
@@ -364,6 +519,55 @@ const STRINGS = {
'menu.done': '完成',
'menu.opening': '打开 {url}',
'menu.goodbye': ' 再见。',
+ 'menu.section.run': '运行',
+ 'menu.section.lifecycle': '生命周期',
+ 'menu.section.build': '构建与部署',
+ 'menu.section.tools': '工具',
+ 'menu.tip': '提示:上下方向键选择,回车确认,Ctrl+C 退出。',
+ 'menu.shortcuts': '↑/↓ 选择 · 回车 确认 · esc 返回 · Ctrl+C 退出',
+ 'menu.statusHeader': '当前状态',
+ 'menu.contextStandalone': '项目类型 · 独立项目(使用内置 API)',
+ 'menu.contextMonorepo': '项目类型 · monorepo(server/src + client/)',
+ 'menu.contextUnknown': '项目类型 · 未初始化(请先运行 init)',
+ 'menu.statusApi': 'API {status}',
+ 'menu.statusDb': '数据库 {status}',
+ 'menu.statusDocker': 'Docker {status}',
+ 'menu.statusLabelApi': 'API',
+ 'menu.statusLabelDb': '数据库',
+ 'menu.statusLabelDocker': 'Docker',
+ 'menu.statusChecking': '检测中…',
+ 'menu.startingApi': '正在启动 API…',
+ 'menu.stoppingApi': '正在停止 API…',
+ 'menu.restartingApi': '正在重启 API…',
+ 'menu.statusOn': '在线',
+ 'menu.statusOff': '离线',
+ 'menu.statusReady': '就绪',
+ 'menu.statusNotReady': '未就绪',
+ 'menu.statusYes': '可用',
+ 'menu.statusNo': '不可用',
+ 'menu.hint.dev': 'API + 数据库 + 前端',
+ 'menu.hint.init': '生成 .reactpress + .env',
+ 'menu.hint.status': '所有服务概览',
+ 'menu.hint.doctor': '环境健康检查',
+ 'menu.hint.devApi': 'watch 模式',
+ 'menu.hint.devClient': 'Next.js 开发',
+ 'menu.hint.serverStart': '后台生产模式',
+ 'menu.hint.serverStop': '',
+ 'menu.hint.serverRestart': '',
+ 'menu.hint.build': '生产构建产物',
+ 'menu.hint.dockerStart': '数据库 + nginx + 全栈',
+ 'menu.hint.dockerUp': '仅数据库',
+ 'menu.hint.dockerStop': '',
+ 'menu.nginxUp': '启动 nginx 反向代理 (:80)',
+ 'menu.nginxOpen': '在浏览器打开 nginx 入口',
+ 'menu.nginxReload': '修改配置后重载 nginx',
+ 'menu.hint.nginxUp': '统一入口 :80',
+ 'menu.hint.nginxOpen': 'http://localhost',
+ 'menu.hint.nginxReload': 'nginx -t 后 reload',
+ 'menu.hint.openAdmin': '在浏览器打开',
+ 'menu.hint.publish': '仅维护者使用',
+ 'menu.hint.exit': '',
+ 'menu.actionPrefix': '操作',
'dev.startingApi': '[reactpress] 正在启动 API(首次可能需安装依赖,请稍候)…',
'dev.waitingApi': '[reactpress] 等待 API 就绪: {url}',
'dev.apiTimeout': '[reactpress] API 在 {seconds}s 内未就绪。\n → 运行 reactpress doctor 查看详情\n → 嵌入式 MySQL:reactpress docker up\n → 检查 .env 中 DB_* 与 SERVER_SITE_URL',
@@ -375,11 +579,16 @@ const STRINGS = {
'dev.nextDoctor': ' → reactpress doctor 环境诊断',
'dev.nextDocker': ' → reactpress docker up 启动嵌入式 MySQL',
'dev.nextEnv': ' → 检查 .env 中 DB_* 与 SERVER_SITE_URL',
- 'devBanner.ready': ' ✓ ReactPress 开发环境已就绪',
+ 'dev.standaloneHint': '[reactpress] 独立项目:当前目录仅启动 API,前端请单独构建。',
+ 'devBanner.ready': 'ReactPress 开发环境已就绪',
'devBanner.site': '前台',
'devBanner.admin': '管理端',
+ 'devBanner.api': 'API',
+ 'devBanner.swagger': 'Swagger',
'devBanner.health': '健康检查',
- 'devBanner.hint': ' 诊断: reactpress doctor · 状态: reactpress status',
+ 'devBanner.hint': '诊断: reactpress doctor · 状态: reactpress status',
+ 'devBanner.shortcuts': 'Ctrl+C 停止',
+ 'devBanner.allSystemsGo': '一切就绪',
'doctor.nodeBad': 'Node.js {version}(需要 ≥ 18)',
'doctor.nodeFix': '请安装 Node.js 18+:https://nodejs.org/',
'doctor.dockerOk': 'Docker 引擎可用',
@@ -410,17 +619,22 @@ const STRINGS = {
'doctor.envOk': '.env 存在',
'doctor.envBad': '缺少 .env',
'doctor.envFix': '运行 reactpress init 或 reactpress config --apply',
- 'doctor.project': ' 项目: {path}',
- 'doctor.allPass': ' 全部检查通过,可以开始开发。',
- 'doctor.failed': ' {count} 项需要处理。',
- 'status.title': ' ReactPress 项目状态',
- 'status.dir': ' 项目目录 {path}',
- 'status.apiSource': ' API 来源 {source}',
+ 'doctor.project': '项目目录 {path}',
+ 'doctor.allPass': '全部检查通过,可以开始开发。',
+ 'doctor.failed': '{count} 项需要处理。',
+ 'doctor.title': 'ReactPress Doctor',
+ 'doctor.subtitle': '环境健康检查',
+ 'doctor.checking': '正在检查 {name}…',
+ 'doctor.summary': '通过 {passed} · 失败 {failed} · 共 {total} 项',
+ 'doctor.fixesHeader': '修复建议',
+ 'status.title': 'ReactPress 项目状态',
+ 'status.dir': '项目目录 {path}',
+ 'status.apiSource': 'API 来源 {source}',
'status.apiSource.monorepo': 'monorepo server/',
'status.apiSource.bundle': 'reactpress-cli',
- 'status.configOk': '.reactpress/config.json ✓',
+ 'status.configOk': '.reactpress/config.json',
'status.configBad': '未初始化',
- 'status.envOk': '.env ✓',
+ 'status.envOk': '.env',
'status.envBad': '缺少 .env',
'status.apiOnline': '在线',
'status.apiOffline': '离线',
@@ -428,10 +642,24 @@ const STRINGS = {
'status.dbUp': '连通',
'status.dbDown': '不可用',
'status.pidRunning': '(运行中)',
- 'status.frontend': ' 前端',
- 'status.docker': ' Docker',
+ 'status.frontend': '前端',
+ 'status.docker': 'Docker',
'status.dockerUp': '可用',
'status.dockerDown': '未运行',
+ 'status.section.project': '项目信息',
+ 'status.section.api': 'API 服务',
+ 'status.section.frontend': '前端',
+ 'status.section.docker': 'Docker',
+ 'status.field.url': 'URL',
+ 'status.field.http': 'HTTP',
+ 'status.field.health': '健康',
+ 'status.field.database': '数据库',
+ 'status.field.pid': 'PID',
+ 'status.field.engine': '引擎',
+ 'status.field.config': '配置',
+ 'status.field.env': '环境',
+ 'status.field.source': '来源',
+ 'status.field.dir': '目录',
'bootstrap.configReady': '配置已存在,数据库已就绪。',
'bootstrap.projectDbPending': '项目已创建,但数据库未就绪: {message}。请确认 Docker 已启动后重试 reactpress dev。',
'bootstrap.ready': 'ReactPress 开发环境已就绪(配置 + 数据库)。',
@@ -442,7 +670,9 @@ const STRINGS = {
'bootstrap.dbReady': '数据库已就绪',
'db.backup.to': '备份数据库到 {path}',
'db.backup.done': '备份完成',
- 'db.backup.fail': 'mysqldump 失败,请确认已安装 MySQL 客户端且 .env 正确',
+ 'db.backup.viaDocker': '本机未找到 mysqldump,改用 Docker 内 db 容器的 mysqldump…',
+ 'db.backup.fail':
+ 'mysqldump 失败:请安装 MySQL 客户端(如 brew install mysql-client),或确保 Docker 数据库已运行以便自动在容器内备份',
'common.done': '完成',
'common.yes': '是',
'common.no': '否',
@@ -451,15 +681,16 @@ const STRINGS = {
'lifecycle.apiStopped': '[reactpress] 已停止 API 进程 (pid {pid})',
'lifecycle.stopPidFailed': '[reactpress] 停止 pid {pid} 失败:',
'lifecycle.apiAlreadyRunning': '[reactpress] API 已在运行 (pid {pid})',
- 'lifecycle.noServerSrc': '[reactpress] 未检测到 server/src,回退到 reactpress-cli start',
+ 'lifecycle.noServerAvailable': '[reactpress] 未找到可用的 API 运行时。请重新安装 @fecommunity/reactpress,或在含 server/src 的项目目录中运行。',
'lifecycle.startingLocalApi': '[reactpress] 正在启动本地 API (server/)…',
+ 'lifecycle.startingBundledApi': '[reactpress] 正在启动内置 API…',
'lifecycle.apiStartedBg': '[reactpress] API 已后台启动 (pid {pid})',
'lifecycle.apiTimeout120': '[reactpress] API 在 120s 内未就绪: {url}',
'lifecycle.apiReady': '[reactpress] API 已就绪: {url}',
'lifecycle.apiStatusTitle': '[reactpress] API 状态',
'lifecycle.source': ' 来源: {source}',
'lifecycle.source.monorepo': 'monorepo server/',
- 'lifecycle.source.bundle': 'reactpress-cli bundle',
+ 'lifecycle.source.bundle': '内置 API (@fecommunity/reactpress)',
'lifecycle.pidFile': ' PID 文件: {path}',
'lifecycle.recordedPid': ' 记录 PID: {pid}',
'lifecycle.processAlive': ' 进程存活: {alive}',
@@ -479,22 +710,50 @@ const STRINGS = {
'docker.mysqlTimeout': '[reactpress] MySQL 在超时时间内未就绪。',
'docker.mysqlNotReady': 'MySQL 未就绪',
'docker.startDevStack': '[reactpress] 启动 API + 前端 (Docker MySQL)…',
- 'docker.visitUrls': '[reactpress] 访问: http://localhost:8080 (nginx) / http://localhost:3001 (client)',
+ 'docker.visitUrls': '[reactpress] 访问: http://localhost (nginx) / http://localhost:3001 (client)',
'docker.devProcessExit': '开发进程退出: {code}',
'docker.unknownCommand': '未知 docker 命令: {command}',
+ 'nginx.configCreated': '[reactpress] 已生成 nginx 配置: {path}',
+ 'nginx.configExists': '[reactpress] nginx 配置已存在: {path}',
+ 'nginx.ensureWarn': '[reactpress] 无法确保 nginx 配置: {message}',
+ 'nginx.started': '[reactpress] Nginx 已启动 — {url}',
+ 'nginx.configPath': '[reactpress] 配置: {path}',
+ 'nginx.stopped': '[reactpress] Nginx 已停止。',
+ 'nginx.startFailed': '启动 nginx 容器失败',
+ 'nginx.prodMonorepoOnly': '生产 nginx(--prod)需要 monorepo 且存在 docker-compose.prod.yml',
+ 'nginx.statusTitle': '[reactpress] Nginx 状态',
+ 'nginx.statusContainer': ' 容器 {name}: {running}',
+ 'nginx.statusConfig': ' 配置 {path}: {exists}',
+ 'nginx.statusUrl': ' 入口 {url} (端口 {port})',
+ 'nginx.statusMode': ' 模式: {mode}',
+ 'nginx.notRunning': 'Nginx 容器未运行。请执行: reactpress nginx up',
+ 'nginx.testOk': '[reactpress] Nginx 配置校验通过。',
+ 'nginx.testFailed': 'Nginx 配置校验失败',
+ 'nginx.reloadOk': '[reactpress] Nginx 已重载。',
+ 'nginx.reloadFailed': 'Nginx 重载失败',
+ 'nginx.opening': '[reactpress] 正在打开 {url}',
+ 'nginx.unknownCommand': '未知 nginx 命令: {command}',
+ 'nginx.templateMissing': '内置 nginx 模板缺失: {path}',
+ 'nginx.doctorSkippedDocker': '已跳过(Docker 未运行)',
+ 'nginx.doctorSkippedNotRunning': '未启动(可选: reactpress nginx up)',
+ 'nginx.doctorNotRunningFix': 'reactpress nginx up(或 reactpress docker up)',
+ 'nginx.doctorOk': 'Nginx 健康 ({url}/health)',
+ 'nginx.doctorUnhealthy': 'Nginx 在运行但 /health 失败 ({url})',
+ 'nginx.doctorUnhealthyFix': '确认前端 (:3001) 与 API (:3002) 已启动;可执行 reactpress nginx reload',
+ 'doctor.check.nginx': 'Nginx 代理',
'apiDev.modeServer': '[reactpress] 开发模式: server/ (nest start --watch)',
- 'apiDev.modeCli': '[reactpress] 开发模式: reactpress-cli(未找到 server/src)',
- 'apiDev.startedByCli': '[reactpress] API 已由 reactpress-cli 启动。',
+ 'apiDev.modeBundled': '[reactpress] 开发模式: 内置 API(随包附带)',
'apiDev.ctrlCHint': '[reactpress] 按 Ctrl+C 停止 API。',
'apiDev.stopHint': '[reactpress] 单独停止: reactpress server stop',
'build.unknownTarget': '未知构建目标: {target},可选: {available}',
'build.recursive': '检测到构建递归(pnpm run build 不能再次调用自身)。请使用 build:toolkit、build:server 或 build:client。',
'build.forbiddenScript': '无效的构建脚本 "{script}",请使用 build:toolkit 等细分脚本。',
- 'build.stepFailed': '[reactpress] [{current}/{total}] {label} 失败',
- 'build.plan': '[reactpress] 生产构建 — 共 {total} 步:toolkit → server → client',
- 'build.step': '[reactpress] [{current}/{total}] {label}…',
- 'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)',
- 'build.done': '[reactpress] 构建完成,耗时 {seconds}s',
+ 'build.stepFailed': '[{current}/{total}] {label} 失败',
+ 'build.plan': '生产构建 — 共 {total} 步:toolkit → server → client',
+ 'build.step': '[{current}/{total}] {label}',
+ 'build.stepDone': '[{current}/{total}] {label} ({seconds}s)',
+ 'build.stepSkipped': '已跳过 {label}(当前项目无对应源码包)',
+ 'build.done': '构建完成,耗时 {seconds}s',
'build.label.toolkit': 'Toolkit',
'build.label.server': 'API (server)',
'build.label.client': '前端 (client)',
diff --git a/cli/lib/lifecycle.js b/cli/lib/lifecycle.js
index f1de03c..e1c2ce7 100644
--- a/cli/lib/lifecycle.js
+++ b/cli/lib/lifecycle.js
@@ -1,16 +1,51 @@
-const { spawn, spawnSync } = require('child_process');
+const { spawn } = require('child_process');
+const ora = require('ora');
const { ensureProjectEnvironment } = require('./bootstrap');
const { loadServerSiteUrl, waitForHttp } = require('./http');
const {
getServerBin,
getServerDir,
isUsingMonorepoServer,
+ canStartLocalApi,
getPidFile,
} = require('./paths');
+const net = require('net');
const { readPid, isProcessRunning, clearPidFile, writePid } = require('./process');
-const { getMonorepoRoot } = require('./root');
+const { ensureOriginalCwd } = require('./root');
const { t } = require('./i18n');
+function parseServerPort(projectRoot) {
+ try {
+ const url = new URL(loadServerSiteUrl(projectRoot));
+ return Number(url.port) || 3002;
+ } catch {
+ return 3002;
+ }
+}
+
+function isPortBusy(port, host = '127.0.0.1') {
+ return new Promise((resolve) => {
+ const socket = net.createConnection({ port, host }, () => {
+ socket.destroy();
+ resolve(true);
+ });
+ socket.on('error', () => resolve(false));
+ socket.setTimeout(800, () => {
+ socket.destroy();
+ resolve(false);
+ });
+ });
+}
+
+async function waitForPortFree(port, timeoutMs = 8000) {
+ const deadline = Date.now() + timeoutMs;
+ while (Date.now() < deadline) {
+ if (!(await isPortBusy(port))) return true;
+ await new Promise((r) => setTimeout(r, 200));
+ }
+ return false;
+}
+
async function ensureConfig(projectRoot) {
try {
await ensureProjectEnvironment(projectRoot);
@@ -46,18 +81,19 @@ async function startApi(projectRoot, { wait = true } = {}) {
}
clearPidFile(projectRoot);
- if (!isUsingMonorepoServer()) {
- console.warn(t('lifecycle.noServerSrc'));
- const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], {
- cwd: projectRoot,
- stdio: 'inherit',
- });
- return start.status ?? 1;
+ if (!canStartLocalApi(projectRoot)) {
+ console.error(t('lifecycle.noServerAvailable'));
+ return 1;
+ }
+
+ if (isUsingMonorepoServer(projectRoot)) {
+ console.log(t('lifecycle.startingLocalApi'));
+ } else {
+ console.log(t('lifecycle.startingBundledApi'));
}
- console.log(t('lifecycle.startingLocalApi'));
- const child = spawn(process.execPath, [getServerBin()], {
- cwd: getServerDir(),
+ const child = spawn(process.execPath, [getServerBin(projectRoot)], {
+ cwd: getServerDir(projectRoot),
detached: true,
stdio: 'ignore',
env: {
@@ -75,12 +111,17 @@ async function startApi(projectRoot, { wait = true } = {}) {
}
const serverUrl = loadServerSiteUrl(projectRoot);
+ const spinner = ora({
+ text: t('dev.waitingApi', { url: serverUrl }),
+ color: 'magenta',
+ spinner: 'dots',
+ }).start();
const ready = await waitForHttp(serverUrl);
if (!ready) {
- console.error(t('lifecycle.apiTimeout120', { url: serverUrl }));
+ spinner.fail(t('lifecycle.apiTimeout120', { url: serverUrl }));
return 1;
}
- console.log(t('lifecycle.apiReady', { url: serverUrl }));
+ spinner.succeed(t('lifecycle.apiReady', { url: serverUrl }));
return 0;
}
@@ -90,7 +131,7 @@ async function statusApi(projectRoot) {
const { isHttpResponding } = require('./http');
const httpOk = await isHttpResponding(serverUrl);
- const source = isUsingMonorepoServer()
+ const source = isUsingMonorepoServer(projectRoot)
? t('lifecycle.source.monorepo')
: t('lifecycle.source.bundle');
@@ -119,7 +160,7 @@ async function statusApi(projectRoot) {
);
}
-async function runLifecycleCommand(command, projectRoot = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) {
+async function runLifecycleCommand(command, projectRoot = ensureOriginalCwd()) {
switch (command) {
case 'start':
return startApi(projectRoot, { wait: true });
@@ -127,22 +168,11 @@ async function runLifecycleCommand(command, projectRoot = process.env.REACTPRESS
return startApi(projectRoot, { wait: false });
case 'stop':
stopApi(projectRoot);
- if (!isUsingMonorepoServer()) {
- spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], {
- cwd: projectRoot,
- stdio: 'inherit',
- });
- }
return 0;
case 'restart':
stopApi(projectRoot);
- if (!isUsingMonorepoServer()) {
- spawnSync('pnpm', ['exec', 'reactpress-cli', 'restart'], {
- cwd: projectRoot,
- stdio: 'inherit',
- });
- return 0;
- }
+ await waitForPortFree(parseServerPort(projectRoot));
+ await new Promise((r) => setTimeout(r, 400));
return startApi(projectRoot, { wait: true });
case 'status':
await statusApi(projectRoot);
diff --git a/cli/lib/nginx.js b/cli/lib/nginx.js
new file mode 100644
index 0000000..e056cab
--- /dev/null
+++ b/cli/lib/nginx.js
@@ -0,0 +1,342 @@
+const fs = require('fs');
+const path = require('path');
+const http = require('http');
+const { spawnSync } = require('child_process');
+const open = require('open');
+const { detectProjectType } = require('./project-type');
+const { isDockerRunning, pickDockerComposeCommand } = require('./docker');
+const { t } = require('./i18n');
+
+const NGINX_CONTAINER = 'reactpress_nginx';
+const DEFAULT_NGINX_PORT = 80;
+
+function resolveNginxMode(options = {}) {
+ return options.prod ? 'prod' : 'dev';
+}
+
+function resolveNginxConfigBasename(mode) {
+ return mode === 'prod' ? 'nginx.conf' : 'nginx.dev.conf';
+}
+
+function resolveNginxConfigPath(projectRoot, mode = 'dev') {
+ const basename = resolveNginxConfigBasename(mode);
+ const type = detectProjectType(projectRoot);
+ if (type === 'monorepo') {
+ return path.join(projectRoot, basename);
+ }
+ return path.join(projectRoot, '.reactpress', basename);
+}
+
+function bundledTemplatePath(mode) {
+ const file = mode === 'prod' ? 'nginx.prod.conf' : 'nginx.dev.conf';
+ return path.join(__dirname, '..', 'templates', file);
+}
+
+function resolveNginxPort(projectRoot) {
+ const envPath = path.join(projectRoot, '.env');
+ try {
+ const content = fs.readFileSync(envPath, 'utf8');
+ const m = content.match(/^NGINX_PORT=(.+)$/m);
+ if (m) {
+ const port = parseInt(m[1].trim().replace(/^['"]|['"]$/g, ''), 10);
+ if (port > 0) return port;
+ }
+ } catch {
+ // ignore
+ }
+ return DEFAULT_NGINX_PORT;
+}
+
+function nginxEntryUrl(projectRoot) {
+ const port = resolveNginxPort(projectRoot);
+ return port === 80 ? 'http://localhost' : `http://localhost:${port}`;
+}
+
+/**
+ * Write default nginx config from CLI templates when missing (or when force).
+ *
+ * @returns {{ configPath: string, created: boolean, mode: 'dev' | 'prod' }}
+ */
+function ensureNginxConfig(projectRoot, options = {}) {
+ const mode = resolveNginxMode(options);
+ const configPath = resolveNginxConfigPath(projectRoot, mode);
+ const templatePath = bundledTemplatePath(mode);
+ if (!fs.existsSync(templatePath)) {
+ throw new Error(t('nginx.templateMissing', { path: templatePath }));
+ }
+
+ const exists = fs.existsSync(configPath);
+ if (exists && !options.force) {
+ return { configPath, created: false, mode };
+ }
+
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
+ fs.copyFileSync(templatePath, configPath);
+ return { configPath, created: !exists || !!options.force, mode };
+}
+
+function resolveNginxComposeContext(projectRoot, mode = 'dev') {
+ const type = detectProjectType(projectRoot);
+ if (mode === 'prod' && type === 'monorepo') {
+ return {
+ composeFile: path.join(projectRoot, 'docker-compose.prod.yml'),
+ cwd: projectRoot,
+ service: 'nginx',
+ };
+ }
+ if (type === 'monorepo') {
+ return {
+ composeFile: path.join(projectRoot, 'docker-compose.dev.yml'),
+ cwd: projectRoot,
+ service: 'nginx',
+ };
+ }
+ return {
+ composeFile: path.join(projectRoot, '.reactpress', 'docker-compose.yml'),
+ cwd: path.join(projectRoot, '.reactpress'),
+ service: 'nginx',
+ };
+}
+
+function composeDefinesNginxService(composeFile) {
+ try {
+ const content = fs.readFileSync(composeFile, 'utf8');
+ return /^\s*nginx:\s*$/m.test(content);
+ } catch {
+ return false;
+ }
+}
+
+function runComposeOnContext(ctx, args, options = {}) {
+ const { command, baseArgs } = pickDockerComposeCommand();
+ return spawnSync(command, [...baseArgs, '-f', ctx.composeFile, ...args], {
+ stdio: options.stdio ?? 'inherit',
+ cwd: ctx.cwd,
+ ...options,
+ });
+}
+
+function isNginxContainerRunning() {
+ const res = spawnSync(
+ 'docker',
+ ['inspect', '-f', '{{.State.Running}}', NGINX_CONTAINER],
+ { encoding: 'utf8' }
+ );
+ return res.status === 0 && res.stdout.trim() === 'true';
+}
+
+function startNginxContainer(configPath, port) {
+ spawnSync('docker', ['rm', '-f', NGINX_CONTAINER], { stdio: 'ignore' });
+ const absConfig = path.resolve(configPath);
+ const res = spawnSync(
+ 'docker',
+ [
+ 'run',
+ '-d',
+ '--name',
+ NGINX_CONTAINER,
+ '-p',
+ `${port}:80`,
+ '-v',
+ `${absConfig}:/etc/nginx/conf.d/default.conf:ro`,
+ '--add-host',
+ 'host.docker.internal:host-gateway',
+ 'nginx:alpine',
+ ],
+ { encoding: 'utf8' }
+ );
+ if (res.status !== 0) {
+ throw new Error(res.stderr?.trim() || t('nginx.startFailed'));
+ }
+}
+
+function stopNginxContainer() {
+ spawnSync('docker', ['rm', '-sf', NGINX_CONTAINER], { stdio: 'ignore' });
+}
+
+function nginxUp(projectRoot, options = {}) {
+ if (!isDockerRunning()) {
+ throw new Error(t('docker.notRunning'));
+ }
+
+ const mode = resolveNginxMode(options);
+ const type = detectProjectType(projectRoot);
+
+ if (mode === 'prod' && type !== 'monorepo') {
+ throw new Error(t('nginx.prodMonorepoOnly'));
+ }
+
+ const { configPath } = ensureNginxConfig(projectRoot, { mode, force: options.force });
+ const port = resolveNginxPort(projectRoot);
+ const ctx = resolveNginxComposeContext(projectRoot, mode);
+
+ if (fs.existsSync(ctx.composeFile) && composeDefinesNginxService(ctx.composeFile)) {
+ const result = runComposeOnContext(ctx, ['up', '-d', ctx.service]);
+ if (result.status !== 0) {
+ throw new Error(t('nginx.startFailed'));
+ }
+ } else {
+ startNginxContainer(configPath, port);
+ }
+
+ console.log(t('nginx.started', { url: nginxEntryUrl(projectRoot) }));
+ console.log(t('nginx.configPath', { path: configPath }));
+}
+
+function nginxDown(projectRoot, options = {}) {
+ const mode = resolveNginxMode(options);
+ const ctx = resolveNginxComposeContext(projectRoot, mode);
+ if (fs.existsSync(ctx.composeFile) && composeDefinesNginxService(ctx.composeFile)) {
+ runComposeOnContext(ctx, ['stop', ctx.service], { stdio: 'ignore' });
+ }
+ stopNginxContainer();
+ console.log(t('nginx.stopped'));
+}
+
+function nginxRestart(projectRoot, options = {}) {
+ nginxDown(projectRoot, options);
+ nginxUp(projectRoot, options);
+}
+
+function nginxStatus(projectRoot, options = {}) {
+ const mode = resolveNginxMode(options);
+ const configPath = resolveNginxConfigPath(projectRoot, mode);
+ const port = resolveNginxPort(projectRoot);
+ const running = isNginxContainerRunning();
+ const configExists = fs.existsSync(configPath);
+
+ console.log(t('nginx.statusTitle'));
+ console.log(t('nginx.statusContainer', { name: NGINX_CONTAINER, running: running ? t('common.yes') : t('common.no') }));
+ console.log(t('nginx.statusConfig', { path: configPath, exists: configExists ? t('common.yes') : t('common.no') }));
+ console.log(t('nginx.statusUrl', { url: nginxEntryUrl(projectRoot), port }));
+ console.log(t('nginx.statusMode', { mode }));
+}
+
+function nginxLogs(extraArgs = []) {
+ const args = ['logs', '-f', NGINX_CONTAINER, ...extraArgs];
+ spawnSync('docker', args, { stdio: 'inherit' });
+}
+
+function dockerExecNginx(args) {
+ return spawnSync('docker', ['exec', NGINX_CONTAINER, 'nginx', ...args], {
+ encoding: 'utf8',
+ });
+}
+
+function nginxTest() {
+ if (!isNginxContainerRunning()) {
+ throw new Error(t('nginx.notRunning'));
+ }
+ const res = dockerExecNginx(['-t']);
+ process.stdout.write(res.stdout || '');
+ process.stderr.write(res.stderr || '');
+ if (res.status !== 0) {
+ throw new Error(t('nginx.testFailed'));
+ }
+ console.log(t('nginx.testOk'));
+}
+
+function nginxReload() {
+ nginxTest();
+ const res = dockerExecNginx(['-s', 'reload']);
+ if (res.status !== 0) {
+ throw new Error(res.stderr?.trim() || t('nginx.reloadFailed'));
+ }
+ console.log(t('nginx.reloadOk'));
+}
+
+async function nginxOpen(projectRoot) {
+ const url = nginxEntryUrl(projectRoot);
+ console.log(t('nginx.opening', { url }));
+ await open(url);
+}
+
+function probeNginxHealth(projectRoot, timeoutMs = 2000) {
+ const url = new URL('/health', nginxEntryUrl(projectRoot));
+ return new Promise((resolve) => {
+ const req = http.get(url, { timeout: timeoutMs }, (res) => {
+ res.resume();
+ resolve(res.statusCode === 200);
+ });
+ req.on('error', () => resolve(false));
+ req.on('timeout', () => {
+ req.destroy();
+ resolve(false);
+ });
+ });
+}
+
+async function checkNginx(projectRoot) {
+ if (!isDockerRunning()) {
+ return { ok: true, message: t('nginx.doctorSkippedDocker') };
+ }
+ if (!isNginxContainerRunning()) {
+ return { ok: true, message: t('nginx.doctorSkippedNotRunning') };
+ }
+ const healthy = await probeNginxHealth(projectRoot);
+ if (healthy) {
+ return {
+ ok: true,
+ message: t('nginx.doctorOk', { url: nginxEntryUrl(projectRoot) }),
+ };
+ }
+ return {
+ ok: false,
+ message: t('nginx.doctorUnhealthy', { url: nginxEntryUrl(projectRoot) }),
+ fix: t('nginx.doctorUnhealthyFix'),
+ };
+}
+
+async function runNginxCommand(command, projectRoot, extraArgs = [], options = {}) {
+ switch (command) {
+ case 'ensure': {
+ const { configPath, created } = ensureNginxConfig(projectRoot, options);
+ console.log(
+ created ? t('nginx.configCreated', { path: configPath }) : t('nginx.configExists', { path: configPath })
+ );
+ return;
+ }
+ case 'up':
+ nginxUp(projectRoot, options);
+ return;
+ case 'down':
+ case 'stop':
+ nginxDown(projectRoot, options);
+ return;
+ case 'restart':
+ nginxRestart(projectRoot, options);
+ return;
+ case 'status':
+ nginxStatus(projectRoot, options);
+ return;
+ case 'logs':
+ nginxLogs(extraArgs);
+ return;
+ case 'test':
+ nginxTest();
+ return;
+ case 'reload':
+ nginxReload();
+ return;
+ case 'open':
+ await nginxOpen(projectRoot);
+ return;
+ default:
+ throw new Error(t('nginx.unknownCommand', { command }));
+ }
+}
+
+module.exports = {
+ NGINX_CONTAINER,
+ DEFAULT_NGINX_PORT,
+ resolveNginxMode,
+ resolveNginxConfigPath,
+ resolveNginxComposeContext,
+ ensureNginxConfig,
+ nginxEntryUrl,
+ resolveNginxPort,
+ isNginxContainerRunning,
+ probeNginxHealth,
+ checkNginx,
+ runNginxCommand,
+};
diff --git a/cli/lib/paths.js b/cli/lib/paths.js
index 12846f9..42351d4 100644
--- a/cli/lib/paths.js
+++ b/cli/lib/paths.js
@@ -1,13 +1,19 @@
const fs = require('fs');
const path = require('path');
-const { getMonorepoRoot } = require('./root');
+const { ensureOriginalCwd, getMonorepoRoot } = require('./root');
-function getMonorepoServerDir() {
- return path.join(getMonorepoRoot(), 'server');
+function resolveProjectRoot(projectRoot) {
+ return path.resolve(projectRoot || ensureOriginalCwd());
}
-function hasMonorepoServerSource() {
- return fs.existsSync(path.join(getMonorepoServerDir(), 'src', 'main.ts'));
+function getMonorepoServerDir(projectRoot) {
+ return path.join(resolveProjectRoot(projectRoot), 'server');
+}
+
+function hasMonorepoServerSource(projectRoot) {
+ return fs.existsSync(
+ path.join(getMonorepoServerDir(projectRoot), 'src', 'main.ts')
+ );
}
function getCliPackageRoot() {
@@ -28,42 +34,69 @@ function getBundledServerDir() {
return path.join(getCliPackageRoot(), 'server');
}
-function getServerDir() {
- if (hasMonorepoServerSource()) {
- return getMonorepoServerDir();
+function hasBundledServerBuild() {
+ return fs.existsSync(path.join(getBundledServerDir(), 'dist', 'main.js'));
+}
+
+function getServerDir(projectRoot) {
+ if (hasMonorepoServerSource(projectRoot)) {
+ return getMonorepoServerDir(projectRoot);
}
return getBundledServerDir();
}
-function getServerBin() {
- return path.join(getServerDir(), 'bin', 'reactpress-server.js');
+function getServerBin(projectRoot) {
+ return path.join(getServerDir(projectRoot), 'bin', 'reactpress-server.js');
}
-function getSwaggerPath() {
- return path.join(getServerDir(), 'public', 'swagger.json');
+function getSwaggerPath(projectRoot) {
+ return path.join(getServerDir(projectRoot), 'public', 'swagger.json');
}
-function getServerMain() {
- return path.join(getServerDir(), 'dist', 'main.js');
+function getServerMain(projectRoot) {
+ return path.join(getServerDir(projectRoot), 'dist', 'main.js');
}
-function isUsingMonorepoServer() {
- return hasMonorepoServerSource();
+function isUsingMonorepoServer(projectRoot) {
+ return hasMonorepoServerSource(projectRoot);
}
-function getClientBin() {
- return path.join(getMonorepoRoot(), 'client', 'bin', 'reactpress-client.js');
+function canStartLocalApi(projectRoot) {
+ return (
+ isUsingMonorepoServer(projectRoot) ||
+ hasBundledServerBuild()
+ );
+}
+
+function getClientBin(projectRoot) {
+ const binPath = path.join(
+ resolveProjectRoot(projectRoot),
+ 'client',
+ 'bin',
+ 'reactpress-client.js'
+ );
+ if (!fs.existsSync(binPath)) {
+ const err = new Error(
+ `Client entry not found: ${binPath}. Run from a ReactPress monorepo root or use reactpress dev --client-only with a remote API.`
+ );
+ err.code = 'REACTPRESS_CLIENT_NOT_FOUND';
+ throw err;
+ }
+ return binPath;
}
function getPidFile(projectRoot) {
- return path.join(projectRoot, '.reactpress', 'server.pid');
+ return path.join(resolveProjectRoot(projectRoot), '.reactpress', 'server.pid');
}
module.exports = {
getMonorepoRoot,
+ resolveProjectRoot,
getMonorepoServerDir,
hasMonorepoServerSource,
+ hasBundledServerBuild,
isUsingMonorepoServer,
+ canStartLocalApi,
getCliPackageRoot,
getBundledServerDir,
getServerDir,
diff --git a/cli/lib/pm2.js b/cli/lib/pm2.js
index 129f2a9..1473b06 100644
--- a/cli/lib/pm2.js
+++ b/cli/lib/pm2.js
@@ -5,9 +5,9 @@ const { t } = require('./i18n');
function startApiWithPm2(projectRoot = ensureOriginalCwd()) {
return new Promise((resolve, reject) => {
- const child = spawn(process.execPath, [getServerBin(), '--pm2'], {
+ const child = spawn(process.execPath, [getServerBin(projectRoot), '--pm2'], {
stdio: 'inherit',
- cwd: getServerDir(),
+ cwd: getServerDir(projectRoot),
env: {
...process.env,
REACTPRESS_ORIGINAL_CWD: projectRoot,
diff --git a/cli/lib/project-type.js b/cli/lib/project-type.js
new file mode 100644
index 0000000..32e5301
--- /dev/null
+++ b/cli/lib/project-type.js
@@ -0,0 +1,72 @@
+const fs = require('fs');
+const path = require('path');
+
+/**
+ * Decide whether a given directory is a ReactPress monorepo checkout (with
+ * editable `server/src`, `client/`, `toolkit/`) or a standalone project that
+ * was created with `reactpress init` and relies on the bundled runtime.
+ *
+ * @param {string} root absolute project root
+ * @returns {'monorepo' | 'standalone' | 'unknown'}
+ */
+function detectProjectType(root) {
+ if (!root) return 'unknown';
+ const abs = path.resolve(root);
+
+ const monorepoMarkers = [
+ path.join(abs, 'pnpm-workspace.yaml'),
+ path.join(abs, 'server', 'src', 'main.ts'),
+ ];
+ if (monorepoMarkers.some((p) => fs.existsSync(p))) {
+ return 'monorepo';
+ }
+
+ if (fs.existsSync(path.join(abs, '.reactpress', 'config.json'))) {
+ return 'standalone';
+ }
+
+ return 'unknown';
+}
+
+/**
+ * @param {string} root
+ */
+function hasClient(root) {
+ return fs.existsSync(path.join(root, 'client', 'package.json'));
+}
+
+/**
+ * @param {string} root
+ */
+function hasServerSource(root) {
+ return fs.existsSync(path.join(root, 'server', 'src', 'main.ts'));
+}
+
+/**
+ * @param {string} root
+ */
+function hasToolkit(root) {
+ return fs.existsSync(path.join(root, 'toolkit', 'package.json'));
+}
+
+/**
+ * @param {string} root
+ */
+function describeProject(root) {
+ const type = detectProjectType(root);
+ return {
+ type,
+ root,
+ hasClient: hasClient(root),
+ hasServerSource: hasServerSource(root),
+ hasToolkit: hasToolkit(root),
+ };
+}
+
+module.exports = {
+ detectProjectType,
+ describeProject,
+ hasClient,
+ hasServerSource,
+ hasToolkit,
+};
diff --git a/cli/lib/publish.js b/cli/lib/publish.js
index 1ae6005..976fcb5 100644
--- a/cli/lib/publish.js
+++ b/cli/lib/publish.js
@@ -7,6 +7,15 @@ const chalk = require('chalk');
const inquirer = require('inquirer');
const crypto = require('crypto');
const { t } = require('./i18n');
+const { getMonorepoRoot } = require('./root');
+
+function getWorkspaceRoot() {
+ const root = getMonorepoRoot();
+ if (fs.existsSync(path.join(root, 'pnpm-workspace.yaml'))) {
+ return root;
+ }
+ return process.cwd();
+}
function getPackages() {
return [
@@ -67,7 +76,7 @@ function generateHash(filePath) {
// Get package content hash
function getPackageHash(packagePath) {
- const fullPath = path.join(process.cwd(), packagePath);
+ const fullPath = path.join(getWorkspaceRoot(), packagePath);
return generateHash(fullPath);
}
@@ -75,7 +84,7 @@ function getPackageHash(packagePath) {
function hasMeaningfulChangesForBuild(packagePath, packageName) {
try {
// Create a hash file path to store previous hash in node_modules
- const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`);
+ const hashFilePath = path.join(getWorkspaceRoot(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`);
// Generate current hash
const currentHash = getPackageHash(packagePath);
@@ -98,7 +107,7 @@ function hasMeaningfulChangesForBuild(packagePath, packageName) {
function hasMeaningfulChangesForPublish(packagePath, packageName) {
try {
// Create a hash file path to store previous hash in node_modules
- const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`);
+ const hashFilePath = path.join(getWorkspaceRoot(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`);
// Generate current hash
const currentHash = getPackageHash(packagePath);
@@ -120,7 +129,7 @@ function hasMeaningfulChangesForPublish(packagePath, packageName) {
// Save package hash (for build)
function savePackageHashForBuild(packagePath, packageName) {
try {
- const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`);
+ const hashFilePath = path.join(getWorkspaceRoot(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`);
// Ensure cache directory exists
const cacheDir = path.dirname(hashFilePath);
@@ -139,7 +148,7 @@ function savePackageHashForBuild(packagePath, packageName) {
// Save package hash (for publish)
function savePackageHashForPublish(packagePath, packageName) {
try {
- const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`);
+ const hashFilePath = path.join(getWorkspaceRoot(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`);
// Ensure cache directory exists
const cacheDir = path.dirname(hashFilePath);
@@ -158,7 +167,7 @@ function savePackageHashForPublish(packagePath, packageName) {
// Get current versions
function getCurrentVersion(packagePath) {
try {
- const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
+ const pkgPath = path.join(getWorkspaceRoot(), packagePath, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
return pkg.version;
} catch (error) {
@@ -168,10 +177,12 @@ function getCurrentVersion(packagePath) {
// Increment version based on type
function incrementVersion(version, type) {
- const parts = version.split('-')[0].split('.');
- const major = parseInt(parts[0]);
- const minor = parseInt(parts[1]);
- const patch = parseInt(parts[2]);
+ const base = String(version).split('-')[0];
+ const parts = base.split('.').map((p) => parseInt(p, 10));
+ while (parts.length < 3) parts.push(0);
+ const major = Number.isFinite(parts[0]) ? parts[0] : 0;
+ const minor = Number.isFinite(parts[1]) ? parts[1] : 0;
+ const patch = Number.isFinite(parts[2]) ? parts[2] : 0;
switch (type) {
case 'major':
@@ -246,7 +257,7 @@ function getNextAvailableVersion(packageName, currentVersion, versionType) {
function updateVersion(packagePath, newVersion) {
console.log(chalk.blue(`\n✏️ Updating version to ${newVersion}...`));
- const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
+ const pkgPath = path.join(getWorkspaceRoot(), packagePath, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const oldVersion = pkg.version;
@@ -260,7 +271,7 @@ function updateVersion(packagePath, newVersion) {
function fixWorkspaceDependenciesForBuild(packagePath) {
console.log(chalk.blue(`🔧 Fixing workspace dependencies for build: ${packagePath}...`));
- const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
+ const pkgPath = path.join(getWorkspaceRoot(), packagePath, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// Fix dependencies
@@ -291,7 +302,7 @@ function fixWorkspaceDependenciesForBuild(packagePath) {
function restoreWorkspaceDependenciesAfterBuild(packagePath) {
console.log(chalk.blue(`🔄 Restoring workspace dependencies after build: ${packagePath}...`));
- const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
+ const pkgPath = path.join(getWorkspaceRoot(), packagePath, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// Restore dependencies
@@ -319,7 +330,7 @@ function restoreWorkspaceDependenciesAfterBuild(packagePath) {
function fixWorkspaceDependenciesForPublish(packagePath, packageVersions) {
console.log(chalk.blue(`🔧 Fixing workspace dependencies for publish: ${packagePath}...`));
- const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
+ const pkgPath = path.join(getWorkspaceRoot(), packagePath, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// Fix dependencies
@@ -350,7 +361,7 @@ function fixWorkspaceDependenciesForPublish(packagePath, packageVersions) {
function restoreWorkspaceDependenciesAfterPublish(packagePath) {
console.log(chalk.blue(`🔄 Restoring workspace dependencies for ${packagePath}...`));
- const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
+ const pkgPath = path.join(getWorkspaceRoot(), packagePath, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// Restore dependencies
@@ -383,15 +394,22 @@ function buildPackage(pkg) {
fixWorkspaceDependenciesForBuild(pkg.path);
try {
- if (pkg.path === 'config') {
- execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
+ const pkgDir = path.join(getWorkspaceRoot(), pkg.path);
+ if (pkg.path === 'cli') {
+ execSync('node scripts/sync-bundled-core.mjs', { cwd: pkgDir, stdio: 'inherit' });
+ if (fs.existsSync(path.join(pkgDir, 'server', 'package.json'))) {
+ execSync('pnpm run build', { cwd: path.join(pkgDir, 'server'), stdio: 'inherit' });
+ }
+ } else if (pkg.path === 'server') {
+ execSync('pnpm run build', { cwd: pkgDir, stdio: 'inherit' });
} else if (pkg.path === 'client') {
- execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
+ execSync('pnpm run prebuild && pnpm run build', { cwd: pkgDir, stdio: 'inherit' });
} else if (pkg.path === 'toolkit') {
- execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
+ execSync('pnpm run build', { cwd: pkgDir, stdio: 'inherit' });
} else if (pkg.path === 'templates/hello-world' || pkg.path === 'templates/twentytwentyfive') {
- // Templates don't need to be built, just validate package.json
console.log(chalk.gray(' Templates do not require building, skipping...'));
+ } else if (fs.existsSync(path.join(pkgDir, 'package.json'))) {
+ execSync('pnpm run build', { cwd: pkgDir, stdio: 'inherit' });
}
console.log(chalk.green(`✅ ${pkg.name} built successfully`));
} finally {
@@ -410,7 +428,7 @@ function publishPackage(packagePath, packageName, tag = 'latest') {
try {
const command = `pnpm publish --access public --tag ${tag} --registry https://registry.npmjs.org --no-git-checks`;
- execSync(command, { cwd: path.join(process.cwd(), packagePath), stdio: 'inherit' });
+ execSync(command, { cwd: path.join(getWorkspaceRoot(), packagePath), stdio: 'inherit' });
console.log(chalk.green(`✅ ${packageName} published successfully!`));
} catch (error) {
console.log(chalk.red(`❌ Failed to publish ${packageName}`));
@@ -472,7 +490,7 @@ async function buildPackages() {
// Check for meaningful changes in each package
for (const pkg of packages) {
- if (fs.existsSync(path.join(process.cwd(), pkg.path))) {
+ if (fs.existsSync(path.join(getWorkspaceRoot(), pkg.path))) {
if (hasMeaningfulChangesForBuild(pkg.path, pkg.name)) {
packagesToBuild.push(pkg);
console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be built`));
@@ -552,7 +570,7 @@ async function publishPackages() {
// Check for meaningful changes in each package
for (const pkg of packages) {
- if (fs.existsSync(path.join(process.cwd(), pkg.path))) {
+ if (fs.existsSync(path.join(getWorkspaceRoot(), pkg.path))) {
if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
packagesToBuild.push(pkg);
console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be built`));
@@ -569,17 +587,15 @@ async function publishPackages() {
return;
}
- // Build packages that have changes
for (const pkg of packagesToBuild) {
- buildPackage(pkg);
- // Save the hash after successful build
+ await buildPackage(pkg);
savePackageHashForPublish(pkg.path, pkg.name);
}
-
+
console.log(chalk.green(`\n🎉 ${packagesToBuild.length} package(s) built successfully!`));
return;
}
-
+
if (action === 'publish-one') {
const { selectedPackage } = await inquirer.prompt([
{
@@ -706,7 +722,7 @@ async function publishPackages() {
// Check for meaningful changes in each package
for (const pkg of packages) {
- if (!fs.existsSync(path.join(process.cwd(), pkg.path))) {
+ if (!fs.existsSync(path.join(getWorkspaceRoot(), pkg.path))) {
console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`));
continue;
}
@@ -845,7 +861,7 @@ async function publishPackages() {
// Check for meaningful changes in each package
for (const pkg of packages) {
- if (!fs.existsSync(path.join(process.cwd(), pkg.path))) {
+ if (!fs.existsSync(path.join(getWorkspaceRoot(), pkg.path))) {
console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`));
continue;
}
diff --git a/cli/lib/root.js b/cli/lib/root.js
index 5d98fbc..cfb53a7 100644
--- a/cli/lib/root.js
+++ b/cli/lib/root.js
@@ -1,6 +1,8 @@
const fs = require('fs');
const path = require('path');
+const CLI_PACKAGE_NAME = '@fecommunity/reactpress';
+
/**
* Install root: monorepo checkout (repo root) or published @fecommunity/reactpress package root.
* cli/lib -> ../.. when pnpm-workspace.yaml exists; published lib/ -> .. only.
@@ -14,8 +16,51 @@ function getMonorepoRoot() {
return packageRoot;
}
+function isPublishedCliRoot(dir) {
+ const resolved = path.resolve(dir);
+ try {
+ const pkg = JSON.parse(
+ fs.readFileSync(path.join(resolved, 'package.json'), 'utf8')
+ );
+ if (pkg.name !== CLI_PACKAGE_NAME) return false;
+ } catch {
+ return false;
+ }
+ return !fs.existsSync(path.join(resolved, 'pnpm-workspace.yaml'));
+}
+
+function isProjectRoot(dir) {
+ const resolved = path.resolve(dir);
+ if (isPublishedCliRoot(resolved)) return false;
+ return (
+ fs.existsSync(path.join(resolved, '.reactpress', 'config.json')) ||
+ fs.existsSync(path.join(resolved, 'pnpm-workspace.yaml')) ||
+ fs.existsSync(path.join(resolved, 'server', 'src', 'main.ts')) ||
+ fs.existsSync(path.join(resolved, 'toolkit', 'package.json'))
+ );
+}
+
+function findProjectRoot(startDir = process.cwd()) {
+ let dir = path.resolve(startDir);
+ while (true) {
+ if (isProjectRoot(dir)) return dir;
+ const parent = path.dirname(dir);
+ if (parent === dir) break;
+ dir = parent;
+ }
+ return null;
+}
+
function getProjectRoot() {
- return path.resolve(process.env.REACTPRESS_ORIGINAL_CWD || process.cwd());
+ const envRoot = process.env.REACTPRESS_ORIGINAL_CWD;
+ if (envRoot) {
+ const resolved = path.resolve(envRoot);
+ if (isProjectRoot(resolved)) return resolved;
+ }
+ const discovered = findProjectRoot(process.cwd());
+ if (discovered) return discovered;
+ if (envRoot) return path.resolve(envRoot);
+ return path.resolve(process.cwd());
}
function ensureOriginalCwd() {
@@ -24,10 +69,11 @@ function ensureOriginalCwd() {
return root;
}
-function isMonorepoCheckout(cwd = getMonorepoRoot()) {
+function isMonorepoCheckout(cwd) {
+ const resolved = path.resolve(cwd || process.cwd());
return (
- fs.existsSync(path.join(cwd, 'pnpm-workspace.yaml')) ||
- fs.existsSync(path.join(cwd, 'server', 'src', 'main.ts'))
+ fs.existsSync(path.join(resolved, 'pnpm-workspace.yaml')) ||
+ fs.existsSync(path.join(resolved, 'server', 'src', 'main.ts'))
);
}
@@ -36,4 +82,7 @@ module.exports = {
getProjectRoot,
ensureOriginalCwd,
isMonorepoCheckout,
+ isProjectRoot,
+ findProjectRoot,
+ isPublishedCliRoot,
};
diff --git a/cli/lib/spawn.js b/cli/lib/spawn.js
index 46b8eba..4753e97 100644
--- a/cli/lib/spawn.js
+++ b/cli/lib/spawn.js
@@ -1,13 +1,13 @@
const { spawn, spawnSync } = require('child_process');
const path = require('path');
const chalk = require('chalk');
-const { getMonorepoRoot } = require('./root');
+const { ensureOriginalCwd } = require('./root');
const { getCliPackageRoot } = require('./paths');
const { t, resolveLocale } = require('./i18n');
function runSync(command, args, options = {}) {
const result = spawnSync(command, args, {
- cwd: options.cwd || getMonorepoRoot(),
+ cwd: options.cwd || ensureOriginalCwd(),
stdio: 'inherit',
env: {
...process.env,
@@ -32,7 +32,7 @@ function runNodeScript(scriptPath, args = [], options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [scriptPath, ...args], {
stdio: 'inherit',
- cwd: options.cwd || getMonorepoRoot(),
+ cwd: options.cwd || ensureOriginalCwd(),
env: {
...process.env,
REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(),
diff --git a/cli/lib/status.js b/cli/lib/status.js
index 2021a7f..a14ce81 100644
--- a/cli/lib/status.js
+++ b/cli/lib/status.js
@@ -1,6 +1,16 @@
const fs = require('fs');
const path = require('path');
-const chalk = require('chalk');
+const {
+ brand,
+ icon,
+ divider,
+ padRight,
+ statusPill,
+ sectionHeader,
+ terminalWidth,
+ gradientText,
+ palette,
+} = require('../ui/theme');
const {
loadServerSiteUrl,
loadClientSiteUrl,
@@ -25,6 +35,10 @@ function envFileStatus(projectRoot) {
};
}
+function fieldRow(label, value) {
+ return ` ${brand.muted(padRight(label, 10))} ${value}`;
+}
+
async function printUnifiedStatus(projectRoot = ensureOriginalCwd()) {
const env = envFileStatus(projectRoot);
const apiUrl = loadServerSiteUrl(projectRoot);
@@ -37,71 +51,81 @@ async function printUnifiedStatus(projectRoot = ensureOriginalCwd()) {
checkHealth(healthUrl),
]);
- const apiSource = isUsingMonorepoServer()
+ const apiSource = isUsingMonorepoServer(projectRoot)
? t('status.apiSource.monorepo')
: t('status.apiSource.bundle');
+ const w = Math.min(terminalWidth() - 4, 52);
+ const httpOn = { on: t('status.apiOnline'), off: t('status.apiOffline') };
+
console.log('');
- console.log(chalk.bold.cyan(t('status.title')));
- console.log(chalk.gray(' ─────────────────────────────────────'));
- console.log(t('status.dir', { path: projectRoot }));
- console.log(t('status.apiSource', { source: apiSource }));
- console.log(
- ` ${chalk.bold('Config')} ${
- env.config
- ? chalk.green(t('status.configOk'))
- : chalk.yellow(t('status.configBad'))
- }`,
- );
+ console.log(` ${gradientText(t('status.title'), [palette.primary, palette.accent], { bold: true })}`);
+ console.log(` ${divider(w)}`);
+
+ console.log(sectionHeader(t('status.section.project')));
+ console.log(fieldRow(t('status.field.dir'), brand.dim(projectRoot)));
+ console.log(fieldRow(t('status.field.source'), brand.accent(apiSource)));
console.log(
- ` ${chalk.bold('.env')} ${
- env.env ? chalk.green(t('status.envOk')) : chalk.yellow(t('status.envBad'))
- }`,
+ fieldRow(
+ t('status.field.config'),
+ env.config ? brand.success(t('status.configOk')) : brand.warn(t('status.configBad'))
+ )
);
- console.log(chalk.gray(' ─────────────────────────────────────'));
- console.log(chalk.bold(' API'));
- console.log(` URL ${apiUrl}`);
console.log(
- ` HTTP ${
- apiHttp ? chalk.green(t('status.apiOnline')) : chalk.red(t('status.apiOffline'))
- }`,
+ fieldRow(
+ t('status.field.env'),
+ env.env ? brand.success(t('status.envOk')) : brand.warn(t('status.envBad'))
+ )
);
+
+ console.log('');
+ console.log(sectionHeader(t('status.section.api')));
+ console.log(fieldRow(t('status.field.url'), brand.dim(apiUrl)));
+ console.log(fieldRow(t('status.field.http'), statusPill(apiHttp, httpOn)));
console.log(
- ` Health ${
+ fieldRow(
+ t('status.field.health'),
health.ok
- ? chalk.green(`${healthUrl} ✓`)
- : chalk.gray(t('status.apiUnreachable', { url: healthUrl }))
- }`,
+ ? `${icon.ok} ${brand.dim(healthUrl)}`
+ : brand.dim(t('status.apiUnreachable', { url: healthUrl }))
+ )
);
if (health.ok && health.data?.data) {
const db = health.data.data.database;
+ const dbOk = db === 'up';
console.log(
- ` Database ${
- db === 'up'
- ? chalk.green(t('status.dbUp'))
- : chalk.red(db === 'down' ? t('status.dbDown') : '—')
- }`,
+ fieldRow(
+ t('status.field.database'),
+ statusPill(dbOk, { on: t('status.dbUp'), off: t('status.dbDown') })
+ )
);
}
+ const pidAlive = pid && isProcessRunning(pid);
console.log(
- ` PID ${pid ?? '—'} ${
- pid && isProcessRunning(pid) ? chalk.green(t('status.pidRunning')) : ''
- }`,
- );
- console.log(chalk.bold(t('status.frontend')));
- console.log(` URL ${clientUrl}`);
- console.log(
- ` HTTP ${
- clientHttp ? chalk.green(t('status.apiOnline')) : chalk.gray(t('status.apiOffline'))
- }`,
+ fieldRow(
+ t('status.field.pid'),
+ `${brand.dim(pid ?? '—')}${pidAlive ? ` ${brand.success(t('status.pidRunning'))}` : ''}`
+ )
);
- console.log(chalk.gray(' ─────────────────────────────────────'));
- console.log(chalk.bold(t('status.docker')));
+
+ console.log('');
+ console.log(sectionHeader(t('status.section.frontend')));
+ console.log(fieldRow(t('status.field.url'), brand.dim(clientUrl)));
+ console.log(fieldRow(t('status.field.http'), statusPill(clientHttp, httpOn)));
+
+ console.log('');
+ console.log(sectionHeader(t('status.section.docker')));
console.log(
- ` Engine ${
- isDockerRunning() ? chalk.green(t('status.dockerUp')) : chalk.red(t('status.dockerDown'))
- }`,
+ fieldRow(
+ t('status.field.engine'),
+ statusPill(isDockerRunning(), {
+ on: t('status.dockerUp'),
+ off: t('status.dockerDown'),
+ })
+ )
);
+
+ console.log(` ${divider(w)}`);
console.log('');
}
diff --git a/cli/package.json b/cli/package.json
index 358c4bd..ad79a22 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@fecommunity/reactpress",
- "version": "3.0.1",
+ "version": "3.0.3",
"description": "ReactPress 3.0 — zero-config CMS: one package, one reactpress command",
"author": "fecommunity",
"license": "MIT",
@@ -35,6 +35,7 @@
"LICENSE"
],
"scripts": {
+ "test": "node --test tests",
"prepare": "node scripts/sync-bundled-core.mjs",
"prepack": "node scripts/sync-bundled-core.mjs"
},
diff --git a/cli/tests/build.test.js b/cli/tests/build.test.js
new file mode 100644
index 0000000..5b35247
--- /dev/null
+++ b/cli/tests/build.test.js
@@ -0,0 +1,32 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const { resolveBuildInvocation, TARGETS } = require('../lib/build');
+const { createMonorepoFixture, createStandaloneProject, rmDir } = require('./helpers/tmp-project');
+
+describe('lib/build', () => {
+ it('resolves toolkit build to toolkit/ directory in monorepo', () => {
+ const root = createMonorepoFixture();
+ try {
+ const inv = resolveBuildInvocation('build:toolkit', root);
+ assert.ok(inv);
+ assert.match(inv.cwd, /toolkit$/);
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('skips client build when client/ is missing', () => {
+ const root = createStandaloneProject();
+ try {
+ const inv = resolveBuildInvocation('build:client', root);
+ assert.equal(inv, null);
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('exposes known targets', () => {
+ assert.ok(TARGETS.includes('all'));
+ assert.ok(TARGETS.includes('toolkit'));
+ });
+});
diff --git a/cli/tests/docker.test.js b/cli/tests/docker.test.js
new file mode 100644
index 0000000..0614277
--- /dev/null
+++ b/cli/tests/docker.test.js
@@ -0,0 +1,28 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const path = require('path');
+const { resolveComposeContext } = require('../lib/docker');
+const { createStandaloneProject, createMonorepoFixture, rmDir } = require('./helpers/tmp-project');
+
+describe('lib/docker', () => {
+ it('uses .reactpress/docker-compose.yml for standalone projects', () => {
+ const root = createStandaloneProject();
+ try {
+ const ctx = resolveComposeContext(root);
+ assert.equal(ctx.type, 'standalone');
+ assert.ok(ctx.composeFile.endsWith(path.join('.reactpress', 'docker-compose.yml')));
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('uses docker-compose.dev.yml for monorepo when present', () => {
+ const root = createMonorepoFixture();
+ try {
+ const ctx = resolveComposeContext(root);
+ assert.equal(ctx.type, 'monorepo');
+ } finally {
+ rmDir(root);
+ }
+ });
+});
diff --git a/cli/tests/helpers/tmp-project.js b/cli/tests/helpers/tmp-project.js
new file mode 100644
index 0000000..5909bcc
--- /dev/null
+++ b/cli/tests/helpers/tmp-project.js
@@ -0,0 +1,77 @@
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+function createStandaloneProject() {
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'reactpress-test-'));
+ const reactpressDir = path.join(root, '.reactpress');
+ fs.mkdirSync(reactpressDir, { recursive: true });
+
+ fs.writeFileSync(
+ path.join(reactpressDir, 'config.json'),
+ JSON.stringify(
+ {
+ version: 1,
+ database: { mode: 'embedded-docker', containerName: 'reactpress_cli_db' },
+ server: {
+ port: 3002,
+ clientUrl: 'http://localhost:3001',
+ serverUrl: 'http://localhost:3002',
+ },
+ },
+ null,
+ 2
+ )
+ );
+
+ fs.writeFileSync(
+ path.join(root, '.env'),
+ [
+ 'DB_HOST=127.0.0.1',
+ 'DB_PORT=3307',
+ 'DB_USER=reactpress',
+ 'DB_PASSWD=reactpress',
+ 'DB_DATABASE=reactpress',
+ 'CLIENT_SITE_URL=http://localhost:3001',
+ 'SERVER_SITE_URL=http://localhost:3002',
+ 'SERVER_PORT=3002',
+ 'SERVER_API_PREFIX=/api',
+ '',
+ ].join('\n')
+ );
+
+ fs.copyFileSync(
+ path.join(__dirname, '../../templates/docker-compose.yml'),
+ path.join(reactpressDir, 'docker-compose.yml')
+ );
+
+ return root;
+}
+
+function createMonorepoFixture() {
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'reactpress-mono-'));
+ fs.writeFileSync(path.join(root, 'pnpm-workspace.yaml'), 'packages:\n - server\n - client\n');
+ fs.mkdirSync(path.join(root, 'server', 'src'), { recursive: true });
+ fs.writeFileSync(path.join(root, 'server', 'src', 'main.ts'), 'export {};\n');
+ fs.writeFileSync(
+ path.join(root, 'server', 'package.json'),
+ JSON.stringify({ name: 'server', scripts: { build: 'echo build' } })
+ );
+ fs.mkdirSync(path.join(root, 'client'));
+ fs.writeFileSync(
+ path.join(root, 'client', 'package.json'),
+ JSON.stringify({ name: 'client', scripts: { dev: 'echo dev' } })
+ );
+ fs.mkdirSync(path.join(root, 'toolkit'));
+ fs.writeFileSync(
+ path.join(root, 'toolkit', 'package.json'),
+ JSON.stringify({ name: 'toolkit', scripts: { build: 'echo build' } })
+ );
+ return root;
+}
+
+function rmDir(dir) {
+ fs.rmSync(dir, { recursive: true, force: true });
+}
+
+module.exports = { createStandaloneProject, createMonorepoFixture, rmDir };
diff --git a/cli/tests/http.test.js b/cli/tests/http.test.js
new file mode 100644
index 0000000..8b8d7d0
--- /dev/null
+++ b/cli/tests/http.test.js
@@ -0,0 +1,23 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const {
+ loadServerSiteUrl,
+ loadClientSiteUrl,
+ getApiPrefix,
+ getHealthUrl,
+} = require('../lib/http');
+const { createStandaloneProject, rmDir } = require('./helpers/tmp-project');
+
+describe('lib/http', () => {
+ it('reads URLs and health path from .env', () => {
+ const root = createStandaloneProject();
+ try {
+ assert.equal(loadServerSiteUrl(root), 'http://localhost:3002');
+ assert.equal(loadClientSiteUrl(root), 'http://localhost:3001');
+ assert.equal(getApiPrefix(root), '/api');
+ assert.equal(getHealthUrl(root), 'http://localhost:3002/api/health');
+ } finally {
+ rmDir(root);
+ }
+ });
+});
diff --git a/cli/tests/i18n.test.js b/cli/tests/i18n.test.js
new file mode 100644
index 0000000..d6e511d
--- /dev/null
+++ b/cli/tests/i18n.test.js
@@ -0,0 +1,20 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const { t, setLocale, getLocale } = require('../lib/i18n');
+
+describe('lib/i18n', () => {
+ it('translates known keys in en and zh', () => {
+ setLocale('en');
+ assert.match(t('cli.description'), /ReactPress/);
+ setLocale('zh');
+ assert.match(t('cli.description'), /ReactPress/);
+ assert.match(t('menu.dev'), /开发|dev/i);
+ setLocale('en');
+ assert.equal(getLocale(), 'en');
+ });
+
+ it('interpolates variables', () => {
+ setLocale('en');
+ assert.match(t('menu.opening', { url: 'http://x' }), /http:\/\/x/);
+ });
+});
diff --git a/cli/tests/nginx.test.js b/cli/tests/nginx.test.js
new file mode 100644
index 0000000..540b6bb
--- /dev/null
+++ b/cli/tests/nginx.test.js
@@ -0,0 +1,59 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+const {
+ resolveNginxConfigPath,
+ resolveNginxComposeContext,
+ ensureNginxConfig,
+} = require('../lib/nginx');
+const { createStandaloneProject, createMonorepoFixture, rmDir } = require('./helpers/tmp-project');
+
+describe('lib/nginx', () => {
+ it('resolves dev config at repo root for monorepo', () => {
+ const root = createMonorepoFixture();
+ try {
+ const configPath = resolveNginxConfigPath(root, 'dev');
+ assert.equal(configPath, path.join(root, 'nginx.dev.conf'));
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('resolves dev config under .reactpress for standalone', () => {
+ const root = createStandaloneProject();
+ try {
+ const configPath = resolveNginxConfigPath(root, 'dev');
+ assert.equal(configPath, path.join(root, '.reactpress', 'nginx.dev.conf'));
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('ensureNginxConfig writes template when missing', () => {
+ const root = createStandaloneProject();
+ try {
+ const target = path.join(root, '.reactpress', 'nginx.dev.conf');
+ if (fs.existsSync(target)) fs.unlinkSync(target);
+ const { configPath, created } = ensureNginxConfig(root, { mode: 'dev' });
+ assert.equal(created, true);
+ assert.equal(configPath, target);
+ assert.ok(fs.existsSync(target));
+ assert.ok(fs.readFileSync(target, 'utf8').includes('host.docker.internal'));
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('uses docker-compose.dev.yml for monorepo dev nginx', () => {
+ const root = createMonorepoFixture();
+ try {
+ fs.writeFileSync(path.join(root, 'docker-compose.dev.yml'), 'services:\n nginx:\n image: nginx\n');
+ const ctx = resolveNginxComposeContext(root, 'dev');
+ assert.equal(ctx.composeFile, path.join(root, 'docker-compose.dev.yml'));
+ assert.equal(ctx.service, 'nginx');
+ } finally {
+ rmDir(root);
+ }
+ });
+});
diff --git a/cli/tests/parity-pack.test.js b/cli/tests/parity-pack.test.js
new file mode 100644
index 0000000..9754d0a
--- /dev/null
+++ b/cli/tests/parity-pack.test.js
@@ -0,0 +1,43 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+
+const CLI_ROOT = path.join(__dirname, '..');
+
+/** Files that must ship in the npm tarball and exist locally (publish/local parity). */
+const REQUIRED_SHIPPED = [
+ 'package.json',
+ 'bin/reactpress.js',
+ 'bin/reactpress-cli-shim.js',
+ 'lib/root.js',
+ 'lib/publish.js',
+ 'lib/project-type.js',
+ 'ui/interactive.js',
+ 'ui/banner.js',
+ 'ui/theme.js',
+ 'dist/index.js',
+ 'templates/env.default',
+ 'templates/config.default.json',
+];
+
+describe('publish/local file parity', () => {
+ it('critical runtime files exist on disk', () => {
+ for (const required of REQUIRED_SHIPPED) {
+ assert.ok(fs.existsSync(path.join(CLI_ROOT, required)), `missing locally: ${required}`);
+ }
+ });
+
+ it('package.json files[] lists top-level dirs for all shipped assets', () => {
+ const pkg = JSON.parse(fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf8'));
+ const declared = new Set(pkg.files || []);
+ for (const required of REQUIRED_SHIPPED) {
+ if (required === 'package.json') continue;
+ const top = required.split('/')[0];
+ assert.ok(
+ declared.has(top) || declared.has(required),
+ `package.json files[] missing "${top}" (needed for ${required})`
+ );
+ }
+ });
+});
diff --git a/cli/tests/project-type.test.js b/cli/tests/project-type.test.js
new file mode 100644
index 0000000..d4982ad
--- /dev/null
+++ b/cli/tests/project-type.test.js
@@ -0,0 +1,32 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const {
+ detectProjectType,
+ describeProject,
+ hasClient,
+} = require('../lib/project-type');
+const { createStandaloneProject, createMonorepoFixture, rmDir } = require('./helpers/tmp-project');
+
+describe('lib/project-type', () => {
+ it('detects standalone projects', () => {
+ const root = createStandaloneProject();
+ try {
+ assert.equal(detectProjectType(root), 'standalone');
+ assert.equal(hasClient(root), false);
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('detects monorepo checkouts', () => {
+ const root = createMonorepoFixture();
+ try {
+ assert.equal(detectProjectType(root), 'monorepo');
+ const info = describeProject(root);
+ assert.equal(info.hasClient, true);
+ assert.equal(info.hasServerSource, true);
+ } finally {
+ rmDir(root);
+ }
+ });
+});
diff --git a/cli/tests/publish-version.test.js b/cli/tests/publish-version.test.js
new file mode 100644
index 0000000..e1051a9
--- /dev/null
+++ b/cli/tests/publish-version.test.js
@@ -0,0 +1,42 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+
+// incrementVersion is not exported — mirror logic for regression tests
+function incrementVersion(version, type) {
+ const base = String(version).split('-')[0];
+ const parts = base.split('.').map((p) => parseInt(p, 10));
+ while (parts.length < 3) parts.push(0);
+ const major = Number.isFinite(parts[0]) ? parts[0] : 0;
+ const minor = Number.isFinite(parts[1]) ? parts[1] : 0;
+ const patch = Number.isFinite(parts[2]) ? parts[2] : 0;
+
+ switch (type) {
+ case 'major':
+ return `${major + 1}.0.0`;
+ case 'minor':
+ return `${major}.${minor + 1}.0`;
+ case 'patch':
+ return `${major}.${minor}.${patch + 1}`;
+ case 'beta': {
+ const match = version.match(/^(.*)-beta\.(\d+)$/);
+ if (match) return `${match[1]}-beta.${parseInt(match[2], 10) + 1}`;
+ return `${version}-beta.1`;
+ }
+ default:
+ return version;
+ }
+}
+
+describe('publish version bump', () => {
+ it('bumps patch', () => {
+ assert.equal(incrementVersion('3.0.3', 'patch'), '3.0.4');
+ });
+
+ it('handles two-segment versions', () => {
+ assert.equal(incrementVersion('3.0', 'patch'), '3.0.1');
+ });
+
+ it('bumps beta', () => {
+ assert.equal(incrementVersion('3.0.0-beta.1', 'beta'), '3.0.0-beta.2');
+ });
+});
diff --git a/cli/tests/root.test.js b/cli/tests/root.test.js
new file mode 100644
index 0000000..7b9dc67
--- /dev/null
+++ b/cli/tests/root.test.js
@@ -0,0 +1,45 @@
+const { describe, it } = require('node:test');
+const assert = require('node:assert/strict');
+const path = require('path');
+const {
+ findProjectRoot,
+ isProjectRoot,
+ isPublishedCliRoot,
+ getMonorepoRoot,
+} = require('../lib/root');
+const { createStandaloneProject, createMonorepoFixture, rmDir } = require('./helpers/tmp-project');
+
+describe('lib/root', () => {
+ it('findProjectRoot discovers standalone .reactpress project', () => {
+ const root = createStandaloneProject();
+ try {
+ assert.equal(findProjectRoot(root), root);
+ assert.equal(isProjectRoot(root), true);
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('isPublishedCliRoot is false for user projects', () => {
+ const root = createStandaloneProject();
+ try {
+ assert.equal(isPublishedCliRoot(root), false);
+ } finally {
+ rmDir(root);
+ }
+ });
+
+ it('getMonorepoRoot resolves to repo root in workspace', () => {
+ const mono = getMonorepoRoot();
+ assert.ok(path.basename(mono) !== 'lib');
+ });
+
+ it('findProjectRoot discovers monorepo via pnpm-workspace.yaml', () => {
+ const root = createMonorepoFixture();
+ try {
+ assert.equal(findProjectRoot(root), root);
+ } finally {
+ rmDir(root);
+ }
+ });
+});
diff --git a/cli/ui/banner.js b/cli/ui/banner.js
index 3a80de3..c4fa9f7 100644
--- a/cli/ui/banner.js
+++ b/cli/ui/banner.js
@@ -1,22 +1,441 @@
-const chalk = require('chalk');
-const { brand } = require('./theme');
-const { getMonorepoRoot } = require('../lib/root');
+const os = require('os');
const path = require('path');
+const chalk = require('chalk');
+const {
+ brand,
+ icon,
+ palette,
+ visibleLength,
+ padRight,
+ terminalWidth,
+ gradientText,
+ pulseBar,
+ statusLights,
+} = require('./theme');
const { t } = require('../lib/i18n');
-function printBanner() {
- const version = require(path.join(getMonorepoRoot(), 'package.json')).version;
+/**
+ * "REACTPRESS" rendered in the ANSI Shadow font.
+ * Each row is exactly 81 single-cell columns, so we can size the surrounding
+ * cyber-card deterministically without measuring per-glyph widths.
+ */
+const TECH_LOGO = [
+ '██████╗ ███████╗ █████╗ ██████╗████████╗██████╗ ██████╗ ███████╗███████╗███████╗',
+ '██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝',
+ '██████╔╝█████╗ ███████║██║ ██║ ██████╔╝██████╔╝█████╗ ███████╗███████╗',
+ '██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║',
+ '██║ ██║███████╗██║ ██║╚██████╗ ██║ ██║ ██║ ██║███████╗███████║███████║',
+ '╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝',
+];
+
+const LOGO_WIDTH = 81;
+const LOGO_GRADIENTS = [
+ [palette.pink, palette.primary],
+ [palette.pink, palette.primary],
+ [palette.primary, palette.accent],
+ [palette.primary, palette.accent],
+ [palette.accent, palette.primary],
+ [palette.accent, palette.primary],
+];
+
+const REPO_URL = 'https://github.com/fecommunity/reactpress';
+/**
+ * Shorter, human-friendly form of REPO_URL shown beneath the title bar.
+ * The clickable hyperlink still resolves to the full https:// URL via
+ * `hyperlink()`, so users can `cmd+click` from any modern terminal.
+ */
+const REPO_DISPLAY = 'github.com/fecommunity/reactpress';
+
+/**
+ * Wrap text in an OSC-8 hyperlink escape so terminals that support it (iTerm2,
+ * Warp, WezTerm, modern macOS Terminal, VS Code, GNOME Terminal, Kitty, …)
+ * render the label as a clickable link. We only emit the escape sequence when
+ * stdout is a real TTY — otherwise (CI logs, file redirects, dumb terminals)
+ * we fall back to the plain styled label so users never see the raw `]8;;`.
+ */
+function hyperlink(url, label) {
+ if (!process.stdout.isTTY) return label;
+ if (process.env.TERM === 'dumb') return label;
+ return `\u001B]8;;${url}\u0007${label}\u001B]8;;\u0007`;
+}
+
+function safeReadCliVersion() {
+ try {
+ return require(path.join(__dirname, '..', 'package.json')).version;
+ } catch {
+ return 'dev';
+ }
+}
+
+function homify(p) {
+ if (!p) return p;
+ const home = os.homedir();
+ if (home && p.startsWith(home)) {
+ return '~' + p.slice(home.length);
+ }
+ return p;
+}
+
+function renderLogoLines() {
+ return TECH_LOGO.map((line, i) => gradientText(line, LOGO_GRADIENTS[i]));
+}
+
+function modeChip(type) {
+ if (type === 'monorepo') {
+ return chalk
+ .bgHex(palette.primary)
+ .hex('#0B1220')
+ .bold(` ${t('banner.mode.monorepo')} `);
+ }
+ if (type === 'standalone') {
+ return chalk
+ .bgHex(palette.accent)
+ .hex('#0B1220')
+ .bold(` ${t('banner.mode.standalone')} `);
+ }
+ return chalk
+ .bgHex(palette.gray)
+ .hex('#0B1220')
+ .bold(` ${t('banner.mode.uninitialized')} `);
+}
+
+/**
+ * Decide how "ready" the welcome banner should look. When a fully
+ * initialized project is detected we render the pulse bar at 100% and
+ * report `ONLINE` status, instead of the static 70% placeholder that used
+ * to make `doctor` runs look incomplete even when everything passed.
+ */
+function bannerReadyState(options) {
+ const type = options && options.project && options.project.type;
+ if (type === 'monorepo' || type === 'standalone') {
+ return { ratio: 1, ready: true };
+ }
+ return { ratio: 0.4, ready: false };
+}
+
+/**
+ * Build the top edge of the cyber-card with a centered title block:
+ * ╔══════════[ REACTPRESS · v3.0.3 ]══════════╗
+ */
+function brandedTopBorder(version, width) {
+ const titleBlock =
+ brand.primary('[') +
+ ' ' +
+ gradientText('REACTPRESS', [palette.primary, palette.accent], { bold: true }) +
+ ' ' +
+ brand.muted('·') +
+ ' ' +
+ brand.accent(`v${version}`) +
+ ' ' +
+ brand.primary(']');
+ const dashTotal = Math.max(0, width - 2 - visibleLength(titleBlock));
+ const left = Math.floor(dashTotal / 2);
+ const right = dashTotal - left;
+ return (
+ brand.primary('╔' + '═'.repeat(left)) +
+ titleBlock +
+ brand.primary('═'.repeat(right) + '╗')
+ );
+}
+
+function bottomBorder(width) {
+ return brand.primary('╚' + '═'.repeat(width - 2) + '╝');
+}
+
+function bodyLine(content, innerWidth) {
+ const padded = padRight(content, innerWidth);
+ return brand.primary('║ ') + padded + brand.primary(' ║');
+}
+
+function emptyBodyLine(innerWidth) {
+ return bodyLine('', innerWidth);
+}
+
+/**
+ * A subtle "CRT scan-line" rendered just under the logo.
+ * ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
+ */
+function scanline(width) {
+ return brand.muted('▔'.repeat(width));
+}
+
+/**
+ * Width of the left-side banner label column.
+ *
+ * Sized to fit our longest English label (`MODE` / `PATH` → 4 cells)
+ * plus a 2-cell trailing gap, which also accommodates the Chinese
+ * translations `模式` / `路径` (4 East-Asian cells each).
+ */
+const LABEL_WIDTH = 6;
+
+/**
+ * Centered, dim repo subtitle that sits directly under the top border.
+ * Replaces the previous in-body `◇ REPO ↗ …` row, which competed visually
+ * with the operational fields (MODE / PATH / pulse) further down.
+ */
+function repoSubline(innerWidth) {
+ const link =
+ brand.muted('↗ ') + hyperlink(REPO_URL, brand.accent.underline(REPO_DISPLAY));
+ const pad = Math.max(0, Math.floor((innerWidth - visibleLength(link)) / 2));
+ return ' '.repeat(pad) + link;
+}
+
+/**
+ * Single-cell-wide chip label, e.g. `◇ MODE ▸ monorepo`.
+ */
+function infoRow(label, value) {
+ return (
+ brand.accent('◇ ') +
+ brand.muted(padRight(label, LABEL_WIDTH)) +
+ ' ' +
+ brand.primary('▸ ') +
+ brand.dim(value)
+ );
+}
+
+/**
+ * Render the "command rail" navigation footer:
+ * ⟫ init ⟫ dev ⟫ build ⟫ deploy ⟫ publish
+ */
+function commandRail() {
+ const items = ['init', 'dev', 'build', 'deploy', 'publish'];
+ return items
+ .map(
+ (name) =>
+ brand.primary('⟫ ') + gradientText(name, [palette.primary, palette.accent])
+ )
+ .join(brand.muted(' '));
+}
+
+/**
+ * Wide, full-fat cyber banner: ASCII logo + scan-line + bordered card.
+ */
+function printWideBanner(version, options) {
+ const cols = terminalWidth();
+ const cardWidth = Math.min(Math.max(LOGO_WIDTH + 8, 88), cols - 2);
+ const innerWidth = cardWidth - 4;
+
+ const lines = [];
+ lines.push('');
+ lines.push(' ' + brandedTopBorder(version, cardWidth));
+ lines.push(' ' + bodyLine(repoSubline(innerWidth), innerWidth));
+ lines.push(' ' + emptyBodyLine(innerWidth));
+
+ const logoIndent = Math.max(0, Math.floor((innerWidth - LOGO_WIDTH) / 2));
+ const indent = ' '.repeat(logoIndent);
+ for (const logoLine of renderLogoLines()) {
+ lines.push(' ' + bodyLine(indent + logoLine, innerWidth));
+ }
+
+ const scanWidth = Math.min(innerWidth - 2, LOGO_WIDTH);
+ const scanIndent = ' '.repeat(Math.max(0, Math.floor((innerWidth - scanWidth) / 2)));
+ lines.push(' ' + bodyLine(scanIndent + scanline(scanWidth), innerWidth));
+
+ lines.push(' ' + emptyBodyLine(innerWidth));
+
+ const ready = bannerReadyState(options);
+ const subtitle =
+ chalk.bold(brand.accent('◆ ')) +
+ gradientText(t('banner.subtitle').trim(), [palette.accent, palette.primary, palette.pink], {
+ bold: true,
+ });
+ const stateLabel = ready.ready
+ ? brand.success(t('banner.systemOnline').trim())
+ : brand.warn(t('banner.systemPending').trim());
+ const right =
+ statusLights(ready.ready ? 'online' : 'pending') +
+ ' ' +
+ brand.dim(t('banner.systemLabel').trim() + ' ') +
+ stateLabel;
+ lines.push(' ' + bodyLine(subtitle + spacer(subtitle, right, innerWidth) + right, innerWidth));
+
+ lines.push(' ' + emptyBodyLine(innerWidth));
+
+ if (options.project) {
+ lines.push(
+ ' ' +
+ bodyLine(
+ brand.accent('◇ ') +
+ brand.muted(padRight(t('banner.label.mode').trim(), LABEL_WIDTH)) +
+ ' ' +
+ modeChip(options.project.type),
+ innerWidth
+ )
+ );
+ }
+ if (options.projectRoot) {
+ lines.push(
+ ' ' +
+ bodyLine(
+ infoRow(t('banner.label.path').trim(), homify(options.projectRoot)),
+ innerWidth
+ )
+ );
+ }
+
+ const pulseWidth = Math.min(28, innerWidth - 18);
+ if (pulseWidth > 8) {
+ const filled = Math.max(1, Math.min(pulseWidth, Math.round(pulseWidth * ready.ratio)));
+ const pulse = pulseBar(pulseWidth, filled);
+ const pulseStatus = ready.ready
+ ? t('banner.pulseReady').trim()
+ : t('banner.pulsePending').trim();
+ const pulseLine =
+ brand.accent('◇ ') +
+ brand.muted(padRight(t('banner.pulseLabel').trim(), LABEL_WIDTH)) +
+ ' ' +
+ pulse +
+ ' ' +
+ (ready.ready ? brand.success(pulseStatus) : brand.warn(pulseStatus));
+ lines.push(' ' + bodyLine(pulseLine, innerWidth));
+ }
+
+ lines.push(' ' + emptyBodyLine(innerWidth));
+ lines.push(' ' + bottomBorder(cardWidth));
+ lines.push(' ' + commandRail());
+ lines.push('');
+
+ for (const line of lines) console.log(line);
+}
+
+/**
+ * Pad between a left-aligned and a right-aligned segment so they sit on the
+ * same line of the cyber card.
+ */
+function spacer(left, right, innerWidth) {
+ const used = visibleLength(left) + visibleLength(right);
+ const gap = Math.max(2, innerWidth - used);
+ return ' '.repeat(gap);
+}
+
+/**
+ * Compact cyber banner for terminals that cannot host the full ASCII logo.
+ */
+function printCompactBanner(version, options) {
+ const cols = terminalWidth();
+ const cardWidth = Math.min(cols - 2, 76);
+ const innerWidth = cardWidth - 4;
+
+ const lines = [];
+ lines.push('');
+ lines.push(' ' + brandedTopBorder(version, cardWidth));
+ lines.push(' ' + bodyLine(repoSubline(innerWidth), innerWidth));
+ lines.push(' ' + emptyBodyLine(innerWidth));
+
+ const ready = bannerReadyState(options);
+ const wordmark =
+ brand.primary('▌▍▎ ') +
+ gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
+ bold: true,
+ }) +
+ brand.primary(' ▎▍▌');
+ const lights = statusLights(ready.ready ? 'online' : 'pending');
+ lines.push(
+ ' ' + bodyLine(wordmark + spacer(wordmark, lights, innerWidth) + lights, innerWidth)
+ );
+
+ const subtitle =
+ chalk.bold(brand.accent('◆ ')) + brand.dim(t('banner.subtitle').trim());
+ lines.push(' ' + bodyLine(subtitle, innerWidth));
+ lines.push(' ' + emptyBodyLine(innerWidth));
+
+ if (options.project) {
+ lines.push(
+ ' ' +
+ bodyLine(
+ brand.accent('◇ ') +
+ brand.muted(padRight(t('banner.label.mode').trim(), LABEL_WIDTH)) +
+ ' ' +
+ modeChip(options.project.type),
+ innerWidth
+ )
+ );
+ }
+ if (options.projectRoot) {
+ lines.push(
+ ' ' +
+ bodyLine(
+ infoRow(t('banner.label.path').trim(), homify(options.projectRoot)),
+ innerWidth
+ )
+ );
+ }
+
+ lines.push(' ' + emptyBodyLine(innerWidth));
+ lines.push(' ' + bottomBorder(cardWidth));
+ lines.push(' ' + commandRail());
+ lines.push('');
+
+ for (const line of lines) console.log(line);
+}
+
+/**
+ * Single-line banner for ultra-narrow terminals (CI logs, embedded shells).
+ */
+function printMinimalBanner(version, options) {
+ const ready = bannerReadyState(options);
+ const wordmark = gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
+ bold: true,
+ });
console.log('');
- console.log(brand.primary(' ╭─────────────────────────────────────────╮'));
+ console.log(` ${brand.primary('▌▍▎')} ${wordmark} ${brand.muted('·')} ${brand.accent(`v${version}`)} ${statusLights(ready.ready ? 'online' : 'pending')}`);
+ console.log(` ${brand.dim(t('banner.subtitle').trim())}`);
+ if (options.project) {
+ console.log(` ${modeChip(options.project.type)}`);
+ }
+ if (options.projectRoot) {
+ console.log(` ${icon.bullet} ${brand.dim(homify(options.projectRoot))}`);
+ }
console.log(
- brand.primary(' │') +
- chalk.bold.white(' ReactPress') +
- brand.muted(t('banner.subtitle')) +
- brand.primary('│'),
+ ` ${brand.muted('↗')} ${hyperlink(REPO_URL, brand.accent.underline(REPO_URL))}`
);
- console.log(brand.primary(' ╰─────────────────────────────────────────╯'));
- console.log(brand.muted(` v${version} · init · dev · build · deploy · publish`));
console.log('');
}
-module.exports = { printBanner };
+/**
+ * Print the top-of-screen banner. Adaptive to terminal width: collapses to a
+ * single-line greeting on very narrow terminals, otherwise renders a bordered
+ * cyber-card with the full ANSI Shadow logo when there is room.
+ *
+ * @param {{
+ * projectRoot?: string,
+ * project?: { type: string, hasClient: boolean, hasServerSource: boolean }
+ * }} [options]
+ */
+function printBanner(options = {}) {
+ const version = safeReadCliVersion();
+ const cols = terminalWidth();
+
+ if (cols < 64) {
+ printMinimalBanner(version, options);
+ return;
+ }
+
+ if (cols < LOGO_WIDTH + 10) {
+ printCompactBanner(version, options);
+ return;
+ }
+
+ printWideBanner(version, options);
+}
+
+/**
+ * Box helper retained for backwards compatibility: a few callers still
+ * import `box` from this module to wrap arbitrary multi-line content.
+ */
+function box(lines, { width } = {}) {
+ const innerWidth = width
+ ? width - 4
+ : lines.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
+
+ const horizontal = '═'.repeat(innerWidth + 2);
+ const top = brand.primary(` ╔${horizontal}╗`);
+ const bottom = brand.primary(` ╚${horizontal}╝`);
+ const body = lines.map((line) => {
+ const padded = padRight(line, innerWidth);
+ return brand.primary(' ║ ') + padded + brand.primary(' ║');
+ });
+ return [top, ...body, bottom];
+}
+
+module.exports = { printBanner, visibleLength, padRight, box };
diff --git a/cli/ui/interactive.js b/cli/ui/interactive.js
index 700391f..282a2b0 100644
--- a/cli/ui/interactive.js
+++ b/cli/ui/interactive.js
@@ -1,55 +1,231 @@
+const fs = require('fs');
+const path = require('path');
const inquirer = require('inquirer');
+const ora = require('ora');
const open = require('open');
const { printBanner } = require('./banner');
-const { brand, label } = require('./theme');
+const {
+ brand,
+ icon,
+ label,
+ ok,
+ fail,
+ sectionHeader,
+ statusPill,
+ padRight,
+} = require('./theme');
const { ensureOriginalCwd } = require('../lib/root');
+const { describeProject, hasClient } = require('../lib/project-type');
const { ensureProjectEnvironment } = require('../lib/bootstrap');
const { runDev } = require('../lib/dev');
const { runApiDev } = require('../lib/api-dev');
const { runLifecycleCommand } = require('../lib/lifecycle');
const { runDockerCommand } = require('../lib/docker');
+const { runNginxCommand } = require('../lib/nginx');
const { printUnifiedStatus } = require('../lib/status');
const { runDoctor } = require('../lib/doctor');
const { runBuild, TARGETS } = require('../lib/build');
const { runNodeScript } = require('../lib/spawn');
const { getClientBin } = require('../lib/paths');
-const { loadClientSiteUrl } = require('../lib/http');
+const { loadClientSiteUrl, loadServerSiteUrl, isHttpResponding } = require('../lib/http');
+const { isDockerRunning } = require('../lib/docker');
const { t } = require('../lib/i18n');
-function getMenuActions() {
- return [
- { name: t('menu.dev'), value: 'dev' },
- { name: t('menu.init'), value: 'init' },
- { name: t('menu.status'), value: 'status' },
- { name: t('menu.doctor'), value: 'doctor' },
- new inquirer.Separator(),
- { name: t('menu.devApi'), value: 'dev:api' },
- { name: t('menu.devClient'), value: 'dev:client' },
- { name: t('menu.serverStart'), value: 'server:start' },
- { name: t('menu.serverStop'), value: 'server:stop' },
- { name: t('menu.serverRestart'), value: 'server:restart' },
- new inquirer.Separator(),
- { name: t('menu.build'), value: 'build' },
- { name: t('menu.dockerStart'), value: 'docker:start' },
- { name: t('menu.dockerUp'), value: 'docker:up' },
- { name: t('menu.dockerStop'), value: 'docker:stop' },
+function menuSection(title) {
+ return new inquirer.Separator(sectionHeader(title));
+}
+
+function formatChoice(key, text, hint) {
+ const keyCol = key ? brand.primary(padRight(key, 2)) : ' ';
+ const hintPart = hint ? brand.dim(` ${hint}`) : '';
+ return `${keyCol} ${text}${hintPart}`;
+}
+
+function assignShortcuts(items) {
+ let n = 0;
+ return items.map((item) => {
+ if (item instanceof inquirer.Separator || item.type === 'separator') {
+ return item;
+ }
+ n += 1;
+ const key = n <= 9 ? String(n) : '';
+ return {
+ ...item,
+ name: formatChoice(key, item._label || item.name, item._hint),
+ short: item.value,
+ };
+ });
+}
+
+function choice(labelKey, value, hintKey) {
+ return {
+ _label: t(labelKey),
+ _hint: hintKey ? t(hintKey) : '',
+ value,
+ };
+}
+
+function getMenuActions(project) {
+ const standalone = project.type === 'standalone';
+ const monorepo = project.type === 'monorepo';
+ const showClient = hasClient(project.root);
+
+ const items = [
+ menuSection(t('menu.section.run')),
+ choice('menu.dev', 'dev', 'menu.hint.dev'),
+ choice('menu.init', 'init', 'menu.hint.init'),
+ choice('menu.status', 'status', 'menu.hint.status'),
+ choice('menu.doctor', 'doctor', 'menu.hint.doctor'),
+ menuSection(t('menu.section.lifecycle')),
+ choice('menu.devApi', 'dev:api', 'menu.hint.devApi'),
+ ];
+
+ if (showClient) {
+ items.push(choice('menu.devClient', 'dev:client', 'menu.hint.devClient'));
+ }
+
+ items.push(
+ choice('menu.serverStart', 'server:start', 'menu.hint.serverStart'),
+ choice('menu.serverStop', 'server:stop', 'menu.hint.serverStop'),
+ choice('menu.serverRestart', 'server:restart', 'menu.hint.serverRestart'),
+ menuSection(t('menu.section.build')),
+ choice('menu.build', 'build', 'menu.hint.build')
+ );
+
+ if (monorepo) {
+ items.push(
+ choice('menu.dockerStart', 'docker:start', 'menu.hint.dockerStart'),
+ choice('menu.dockerUp', 'docker:up', 'menu.hint.dockerUp'),
+ choice('menu.dockerStop', 'docker:stop', 'menu.hint.dockerStop')
+ );
+ } else if (standalone) {
+ items.push(
+ choice('menu.dockerUp', 'docker:up', 'menu.hint.dockerUp'),
+ choice('menu.dockerStop', 'docker:stop', 'menu.hint.dockerStop')
+ );
+ }
+
+ items.push(
+ menuSection(t('menu.section.tools')),
+ choice('menu.nginxUp', 'nginx:up', 'menu.hint.nginxUp'),
+ choice('menu.nginxOpen', 'nginx:open', 'menu.hint.nginxOpen'),
+ choice('menu.nginxReload', 'nginx:reload', 'menu.hint.nginxReload'),
+ choice('menu.openAdmin', 'open:admin', 'menu.hint.openAdmin')
+ );
+
+ if (monorepo) {
+ items.push(choice('menu.publish', 'publish', 'menu.hint.publish'));
+ }
+
+ items.push(
new inquirer.Separator(),
- { name: t('menu.openAdmin'), value: 'open:admin' },
- { name: t('menu.publish'), value: 'publish' },
- { name: t('menu.exit'), value: 'exit' },
+ choice('menu.exit', 'exit', 'menu.hint.exit')
+ );
+
+ return assignShortcuts(items);
+}
+
+function parseEnvFile(projectRoot) {
+ const envPath = path.join(projectRoot, '.env');
+ const env = {};
+ try {
+ if (!fs.existsSync(envPath)) return env;
+ for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
+ if (m) env[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, '');
+ }
+ } catch {
+ // ignore
+ }
+ return env;
+}
+
+async function probeDatabase(projectRoot) {
+ try {
+ const mysql = require('mysql2/promise');
+ const env = parseEnvFile(projectRoot);
+ const conn = await mysql.createConnection({
+ host: env.DB_HOST || '127.0.0.1',
+ port: Number(env.DB_PORT || 3306),
+ user: env.DB_USER || 'reactpress',
+ password: env.DB_PASSWD || env.DB_PASSWORD || 'reactpress',
+ database: env.DB_DATABASE || 'reactpress',
+ connectTimeout: 2000,
+ });
+ await conn.ping();
+ await conn.end();
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+async function fetchContextStatus(projectRoot) {
+ const apiUrl = loadServerSiteUrl(projectRoot);
+ const [apiOk, dockerOk, dbOk] = await Promise.all([
+ isHttpResponding(apiUrl, 1500),
+ Promise.resolve(isDockerRunning()),
+ probeDatabase(projectRoot),
+ ]);
+ return { apiOk, dbOk, dockerOk };
+}
+
+function printStatusPanel(status) {
+ const on = { on: t('menu.statusOn'), off: t('menu.statusOff') };
+ const db = {
+ on: t('menu.statusReady'),
+ off: t('menu.statusNotReady'),
+ pending: t('menu.statusChecking'),
+ };
+ const docker = { on: t('menu.statusYes'), off: t('menu.statusNo') };
+
+ console.log(sectionHeader(t('menu.statusHeader')));
+ const rows = [
+ [t('menu.statusLabelApi'), statusPill(status.apiOk, on)],
+ [t('menu.statusLabelDb'), statusPill(status.dbOk, db)],
+ [t('menu.statusLabelDocker'), statusPill(status.dockerOk, docker)],
];
+ for (const [name, pill] of rows) {
+ console.log(` ${brand.muted(padRight(name, 10))} ${pill}`);
+ }
+ console.log('');
}
-async function runMenuAction(action, projectRoot) {
+async function printContextStatus(projectRoot) {
+ const spinner = ora({
+ text: brand.dim(t('menu.statusChecking')),
+ color: 'magenta',
+ spinner: 'dots',
+ }).start();
+ const status = await fetchContextStatus(projectRoot);
+ spinner.stop();
+ printStatusPanel(status);
+ return status;
+}
+
+async function withSpinner(text, fn) {
+ const spinner = ora({ text, color: 'magenta', spinner: 'dots' }).start();
+ try {
+ const result = await fn();
+ spinner.succeed();
+ return result;
+ } catch (err) {
+ spinner.fail();
+ throw err;
+ }
+}
+
+async function runMenuAction(action, projectRoot, project) {
switch (action) {
case 'dev':
console.log(label(t('menu.startingDev')));
await runDev(projectRoot);
return false;
case 'init': {
- console.log(label(t('menu.initProject')));
- const result = await ensureProjectEnvironment(projectRoot);
- console.log(brand.success(result.message || t('menu.done')));
+ const result = await withSpinner(t('menu.initProject'), () =>
+ ensureProjectEnvironment(projectRoot)
+ );
+ console.log(ok(result.message || t('menu.done')));
return true;
}
case 'status':
@@ -67,15 +243,21 @@ async function runMenuAction(action, projectRoot) {
await runNodeScript(getClientBin(), [], { cwd: projectRoot });
return false;
case 'server:start': {
- const code = await runLifecycleCommand('start', projectRoot);
+ const code = await withSpinner(t('menu.startingApi'), () =>
+ runLifecycleCommand('start', projectRoot)
+ );
if (code !== 0) process.exit(code);
return true;
}
case 'server:stop':
- await runLifecycleCommand('stop', projectRoot);
+ await withSpinner(t('menu.stoppingApi'), async () => {
+ await runLifecycleCommand('stop', projectRoot);
+ });
return true;
case 'server:restart': {
- const code = await runLifecycleCommand('restart', projectRoot);
+ const code = await withSpinner(t('menu.restartingApi'), () =>
+ runLifecycleCommand('restart', projectRoot)
+ );
if (code !== 0) process.exit(code);
return true;
}
@@ -92,6 +274,7 @@ async function runMenuAction(action, projectRoot) {
type: 'list',
name: 'target',
message: t('menu.buildTarget'),
+ pageSize: 12,
choices: buildChoices,
},
]);
@@ -102,10 +285,23 @@ async function runMenuAction(action, projectRoot) {
await runDockerCommand('start', projectRoot);
return false;
case 'docker:up':
- await runDockerCommand('up', projectRoot);
+ await withSpinner(t('docker.starting'), () => runDockerCommand('up', projectRoot));
return true;
case 'docker:stop':
- await runDockerCommand('down', projectRoot);
+ await withSpinner(t('docker.stopping'), async () => {
+ await runDockerCommand('down', projectRoot);
+ });
+ return true;
+ case 'nginx:up':
+ await withSpinner(t('cli.nginx.up'), async () => {
+ await runNginxCommand('up', projectRoot);
+ });
+ return true;
+ case 'nginx:open':
+ await runNginxCommand('open', projectRoot);
+ return true;
+ case 'nginx:reload':
+ await runNginxCommand('reload', projectRoot);
return true;
case 'open:admin': {
const url = loadClientSiteUrl(projectRoot);
@@ -121,7 +317,6 @@ async function runMenuAction(action, projectRoot) {
return true;
}
case 'exit':
- console.log(brand.muted(t('menu.goodbye')));
return false;
default:
return true;
@@ -130,7 +325,12 @@ async function runMenuAction(action, projectRoot) {
async function runInteractiveLoop() {
const projectRoot = ensureOriginalCwd();
- printBanner();
+ const project = describeProject(projectRoot);
+
+ printBanner({ projectRoot, project });
+ await printContextStatus(projectRoot);
+ console.log(` ${brand.dim(t('menu.shortcuts'))}`);
+ console.log('');
let loop = true;
while (loop) {
@@ -138,9 +338,10 @@ async function runInteractiveLoop() {
{
type: 'list',
name: 'action',
- message: t('menu.prompt'),
- pageSize: 14,
- choices: getMenuActions(),
+ message: `${brand.primary(t('menu.actionPrefix'))} ${brand.dim('›')}`,
+ pageSize: 20,
+ loop: false,
+ choices: getMenuActions(project),
},
]);
@@ -150,24 +351,15 @@ async function runInteractiveLoop() {
}
try {
- const stay = await runMenuAction(action, projectRoot);
- if (!stay) {
- break;
- }
+ const stay = await runMenuAction(action, projectRoot, project);
+ if (!stay) break;
- if (action !== 'status') {
- const { again } = await inquirer.prompt([
- {
- type: 'confirm',
- name: 'again',
- message: t('menu.back'),
- default: true,
- },
- ]);
- loop = again;
+ if (action !== 'status' && action !== 'doctor') {
+ console.log('');
+ await printContextStatus(projectRoot);
}
} catch (err) {
- console.error(brand.error(` ${err.message || err}`));
+ console.error(fail(err.message || err));
const { retry } = await inquirer.prompt([
{
type: 'confirm',
@@ -177,8 +369,12 @@ async function runInteractiveLoop() {
},
]);
loop = retry;
+ if (loop) {
+ console.log('');
+ await printContextStatus(projectRoot);
+ }
}
}
}
-module.exports = { runInteractiveLoop, runMenuAction };
+module.exports = { runInteractiveLoop, runMenuAction, getMenuActions };
diff --git a/cli/ui/theme.js b/cli/ui/theme.js
index d752db2..7b06f7a 100644
--- a/cli/ui/theme.js
+++ b/cli/ui/theme.js
@@ -1,25 +1,265 @@
const chalk = require('chalk');
+/**
+ * ReactPress CLI visual identity — a single source of truth so banners,
+ * menus, status, doctor, build output all share the same colours and glyphs.
+ */
+const palette = {
+ primary: '#7C5CFF',
+ accent: '#22D3EE',
+ pink: '#F472B6',
+ green: '#22C55E',
+ amber: '#F59E0B',
+ red: '#EF4444',
+ gray: '#6B7280',
+ dim: '#9CA3AF',
+};
+
const brand = {
- primary: chalk.hex('#6366f1'),
- accent: chalk.hex('#22d3ee'),
- success: chalk.green,
- warn: chalk.yellow,
- error: chalk.red,
- muted: chalk.gray,
+ primary: chalk.hex(palette.primary),
+ accent: chalk.hex(palette.accent),
+ pink: chalk.hex(palette.pink),
+ success: chalk.hex(palette.green),
+ warn: chalk.hex(palette.amber),
+ error: chalk.hex(palette.red),
+ muted: chalk.hex(palette.gray),
+ dim: chalk.hex(palette.dim),
bold: chalk.bold,
};
+const icon = {
+ ok: brand.success('✓'),
+ fail: brand.error('✗'),
+ warn: brand.warn('⚠'),
+ info: brand.accent('ℹ'),
+ arrow: brand.primary('›'),
+ pointer: brand.primary('▸'),
+ bullet: brand.muted('·'),
+ dotOn: brand.success('●'),
+ dotOff: brand.muted('○'),
+ dotPending: brand.warn('◐'),
+ dotInfo: brand.accent('●'),
+ spark: brand.primary('✱'),
+ link: brand.muted('↗'),
+};
+
+/**
+ * Whether a Unicode code point should occupy two terminal cells.
+ *
+ * Covers the common "East Asian Wide / Full-width" ranges that show up in
+ * Chinese / Japanese / Korean text plus full-width punctuation. We
+ * deliberately do not pull in a heavy dependency like `string-width` to keep
+ * the CLI's startup cheap.
+ */
+function isWideCodePoint(cp) {
+ return (
+ (cp >= 0x1100 && cp <= 0x115f) ||
+ (cp >= 0x2e80 && cp <= 0x303e) ||
+ (cp >= 0x3041 && cp <= 0x33ff) ||
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
+ (cp >= 0x4e00 && cp <= 0x9fff) ||
+ (cp >= 0xa000 && cp <= 0xa4cf) ||
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
+ (cp >= 0xf900 && cp <= 0xfaff) ||
+ (cp >= 0xfe30 && cp <= 0xfe4f) ||
+ (cp >= 0xff00 && cp <= 0xff60) ||
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
+ (cp >= 0x1f300 && cp <= 0x1f64f) ||
+ (cp >= 0x1f900 && cp <= 0x1f9ff) ||
+ (cp >= 0x20000 && cp <= 0x2fffd) ||
+ (cp >= 0x30000 && cp <= 0x3fffd)
+ );
+}
+
+/**
+ * Visible terminal-cell width of a string, after stripping ANSI colour codes
+ * and accounting for East Asian wide characters (which occupy 2 cells).
+ */
+function visibleLength(text) {
+ const stripped = String(text)
+ .replace(/\u001b\[[0-9;]*m/g, '')
+ .replace(/\u001b\]8;[^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '');
+ let width = 0;
+ for (const ch of stripped) {
+ const cp = ch.codePointAt(0);
+ if (cp === undefined) continue;
+ if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0)) continue;
+ width += isWideCodePoint(cp) ? 2 : 1;
+ }
+ return width;
+}
+
+function padRight(text, width) {
+ const len = visibleLength(text);
+ if (len >= width) return text;
+ return text + ' '.repeat(width - len);
+}
+
+function padLeft(text, width) {
+ const len = visibleLength(text);
+ if (len >= width) return text;
+ return ' '.repeat(width - len) + text;
+}
+
+function terminalWidth(fallback = 80) {
+ const cols = Number(process.stdout.columns) || fallback;
+ return Math.max(48, Math.min(120, cols));
+}
+
+function divider(width = 44, char = '─', colorize = brand.muted) {
+ return colorize(char.repeat(width));
+}
+
+/**
+ * Cyberpunk-flavoured progress-bar style decoration.
+ * `filled` segments use the primary colour, the trailing track stays muted.
+ */
+function pulseBar(width = 24, filled = Math.ceil(width * 0.7)) {
+ const f = Math.max(0, Math.min(width, filled));
+ const head = brand.primary('▰'.repeat(f));
+ const tail = brand.muted('▱'.repeat(Math.max(0, width - f)));
+ return `${head}${tail}`;
+}
+
+/**
+ * Three-light status indicator used in the top-right of the banner.
+ * Mimics the running-light cluster you'd see on a server rack.
+ */
+function statusLights(state = 'online') {
+ if (state === 'offline') {
+ return `${brand.muted('●')} ${brand.muted('●')} ${brand.muted('●')}`;
+ }
+ if (state === 'pending') {
+ return `${brand.warn('●')} ${brand.warn('●')} ${brand.muted('○')}`;
+ }
+ return `${brand.success('●')} ${brand.warn('●')} ${brand.muted('○')}`;
+}
+
+function hex2rgb(h) {
+ const s = h.replace('#', '');
+ return {
+ r: parseInt(s.substring(0, 2), 16),
+ g: parseInt(s.substring(2, 4), 16),
+ b: parseInt(s.substring(4, 6), 16),
+ };
+}
+
+function rgb2hex(r, g, b) {
+ const pad = (n) => n.toString(16).padStart(2, '0');
+ return `#${pad(r)}${pad(g)}${pad(b)}`;
+}
+
+function mixHex(a, b, t) {
+ const pa = hex2rgb(a);
+ const pb = hex2rgb(b);
+ const r = Math.round(pa.r + (pb.r - pa.r) * t);
+ const g = Math.round(pa.g + (pb.g - pa.g) * t);
+ const bl = Math.round(pa.b + (pb.b - pa.b) * t);
+ return rgb2hex(r, g, bl);
+}
+
+/**
+ * Paint a string with a left→right linear gradient across `colors` (hex).
+ * Falls back to plain text when stdout does not support truecolor.
+ */
+function gradientText(text, colors = [palette.primary, palette.accent], { bold = false } = {}) {
+ if (!text) return '';
+ const supports = chalk.supportsColor && chalk.supportsColor.has16m;
+ if (!supports || colors.length < 2) {
+ const c = chalk.hex(colors[0] || palette.primary);
+ return bold ? c.bold(text) : c(text);
+ }
+ const chars = [...String(text)];
+ const n = Math.max(chars.length - 1, 1);
+ return chars
+ .map((ch, i) => {
+ const ratio = i / n;
+ const idx = ratio * (colors.length - 1);
+ const lo = Math.floor(idx);
+ const hi = Math.min(colors.length - 1, lo + 1);
+ const local = idx - lo;
+ const c = chalk.hex(mixHex(colors[lo], colors[hi], local));
+ return bold ? c.bold(ch) : c(ch);
+ })
+ .join('');
+}
+
function label(text) {
- return brand.primary(`› ${text}`);
+ return `${icon.arrow} ${brand.primary(text)}`;
}
function ok(text) {
- return brand.success(`✓ ${text}`);
+ return `${icon.ok} ${brand.success(text)}`;
}
function fail(text) {
- return brand.error(`✗ ${text}`);
+ return `${icon.fail} ${brand.error(text)}`;
+}
+
+function warn(text) {
+ return `${icon.warn} ${brand.warn(text)}`;
+}
+
+function info(text) {
+ return `${icon.info} ${brand.accent(text)}`;
+}
+
+function chip(text, color = brand.primary) {
+ return color(`[ ${text} ]`);
+}
+
+function kv(key, value, { keyWidth = 10, valueColor = (s) => s } = {}) {
+ return `${brand.muted(padRight(key, keyWidth))} ${valueColor(value)}`;
}
-module.exports = { brand, label, ok, fail };
+/**
+ * Render a 3-state status pill, e.g. `● online` / `○ offline` / `◐ pending`.
+ *
+ * @param {boolean | 'pending'} state
+ * @param {{ on?: string, off?: string, pending?: string }} labels
+ */
+function statusPill(state, labels = {}) {
+ if (state === 'pending') {
+ return `${icon.dotPending} ${brand.warn(labels.pending || 'pending')}`;
+ }
+ if (state === true) {
+ return `${icon.dotOn} ${brand.success(labels.on || 'online')}`;
+ }
+ return `${icon.dotOff} ${brand.dim(labels.off || 'offline')}`;
+}
+
+/**
+ * Render a single-line section header: ` ── Title ────────────`.
+ */
+function sectionHeader(title, { width } = {}) {
+ const w = width ?? terminalWidth();
+ const prefix = brand.muted('── ');
+ const t = brand.bold(brand.primary(title));
+ const usedLen = visibleLength(prefix) + visibleLength(t) + 2;
+ const fillLen = Math.max(3, w - usedLen - 2);
+ const fill = brand.muted('─'.repeat(fillLen));
+ return ` ${prefix}${t} ${fill}`;
+}
+
+module.exports = {
+ palette,
+ brand,
+ icon,
+ label,
+ ok,
+ fail,
+ warn,
+ info,
+ chip,
+ kv,
+ statusPill,
+ sectionHeader,
+ visibleLength,
+ padRight,
+ padLeft,
+ terminalWidth,
+ divider,
+ gradientText,
+ pulseBar,
+ statusLights,
+};
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 6b0184e..5d7933c 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -36,7 +36,7 @@ services:
image: nginx:alpine
container_name: reactpress_nginx
ports:
- - "8080:80"
+ - "${NGINX_PORT:-80}:80"
volumes:
- ./nginx.dev.conf:/etc/nginx/conf.d/default.conf
networks:
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index 2196089..321609b 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -42,7 +42,7 @@ services:
image: nginx:alpine
container_name: reactpress_nginx
ports:
- - "8080:80"
+ - "${NGINX_PORT:-80}:80"
depends_on:
- client
extra_hosts:
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index 1f68d16..68292a9 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -51,6 +51,11 @@ const config: Config = {
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
+ headTags: [
+ {tagName: 'meta', attributes: {property: 'og:type', content: 'website'}},
+ {tagName: 'meta', attributes: {property: 'og:site_name', content: 'ReactPress'}},
+ ],
+
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
@@ -63,6 +68,40 @@ const config: Config = {
[
'@docusaurus/preset-classic',
{
+ sitemap: {
+ lastmod: 'date',
+ changefreq: 'weekly',
+ priority: 0.5,
+ ignorePatterns: ['/tags/**', '/markdown-page'],
+ filename: 'sitemap.xml',
+ createSitemapItems: async (params) => {
+ const {defaultCreateSitemapItems, ...rest} = params;
+ const items = await defaultCreateSitemapItems(rest);
+ return items
+ .filter((item) => !item.url.includes('/markdown-page'))
+ .map((item) => {
+ const pathname = new URL(item.url).pathname;
+ if (pathname === '/' || pathname === '/zh/') {
+ return {...item, priority: 1.0, changefreq: 'daily' as const};
+ }
+ if (pathname.endsWith('/docs/intro')) {
+ return {...item, priority: 0.9};
+ }
+ if (pathname.includes('/blog/changelog')) {
+ return {...item, priority: 0.8};
+ }
+ if (
+ pathname.includes('/blog/archive') ||
+ pathname.includes('/blog/authors') ||
+ pathname.endsWith('/blog/tags') ||
+ pathname.includes('/blog/tags/')
+ ) {
+ return {...item, priority: 0.3};
+ }
+ return item;
+ });
+ },
+ },
docs: {
path: './tutorial',
sidebarPath: './sidebars.ts',
@@ -178,7 +217,14 @@ const config: Config = {
darkTheme: prismThemes.dracula,
},
metadata: [
- { name: 'keywords', content: 'reactpress, blog, cms, next.js, react, nest.js' },
+ {
+ name: 'keywords',
+ content:
+ 'reactpress, blog, cms, next.js, react, nest.js, headless cms, content management, 博客, 内容管理',
+ },
+ {name: 'robots', content: 'index, follow, max-image-preview:large'},
+ {name: 'googlebot', content: 'index, follow'},
+ {name: 'twitter:card', content: 'summary_large_image'},
],
} satisfies Preset.ThemeConfig,
};
diff --git a/docs/package.json b/docs/package.json
index 2daad1f..6652871 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -5,6 +5,7 @@
"scripts": {
"docusaurus": "docusaurus",
"dev": "docusaurus start",
+ "preview": "docusaurus build && docusaurus serve",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
diff --git a/docs/src/pages/Home/CallToAction/index.tsx b/docs/src/components/Home/CallToAction/index.tsx
similarity index 100%
rename from docs/src/pages/Home/CallToAction/index.tsx
rename to docs/src/components/Home/CallToAction/index.tsx
diff --git a/docs/src/pages/Home/CallToAction/styles.module.css b/docs/src/components/Home/CallToAction/styles.module.css
similarity index 100%
rename from docs/src/pages/Home/CallToAction/styles.module.css
rename to docs/src/components/Home/CallToAction/styles.module.css
diff --git a/docs/src/pages/Home/Hero/Devices.tsx b/docs/src/components/Home/Hero/Devices.tsx
similarity index 100%
rename from docs/src/pages/Home/Hero/Devices.tsx
rename to docs/src/components/Home/Hero/Devices.tsx
diff --git a/docs/src/pages/Home/Hero/FloorBackground.tsx b/docs/src/components/Home/Hero/FloorBackground.tsx
similarity index 100%
rename from docs/src/pages/Home/Hero/FloorBackground.tsx
rename to docs/src/components/Home/Hero/FloorBackground.tsx
diff --git a/docs/src/pages/Home/Hero/GridBackground.tsx b/docs/src/components/Home/Hero/GridBackground.tsx
similarity index 100%
rename from docs/src/pages/Home/Hero/GridBackground.tsx
rename to docs/src/components/Home/Hero/GridBackground.tsx
diff --git a/docs/src/pages/Home/Hero/index.tsx b/docs/src/components/Home/Hero/index.tsx
similarity index 91%
rename from docs/src/pages/Home/Hero/index.tsx
rename to docs/src/components/Home/Hero/index.tsx
index 5c93d78..bea123d 100644
--- a/docs/src/pages/Home/Hero/index.tsx
+++ b/docs/src/components/Home/Hero/index.tsx
@@ -1,9 +1,9 @@
import React from 'react';
import Link from '@docusaurus/Link';
-import Logo from '@site/src/pages/Home/Logo';
-import GridBackground from '@site/src/pages/Home/Hero/GridBackground';
-import FloorBackground from '@site/src/pages/Home/Hero/FloorBackground';
-import Devices from '@site/src/pages/Home/Hero/Devices';
+import Logo from '@site/src/components/Home/Logo';
+import GridBackground from '@site/src/components/Home/Hero/GridBackground';
+import FloorBackground from '@site/src/components/Home/Hero/FloorBackground';
+import Devices from '@site/src/components/Home/Hero/Devices';
import CliCommandBlock from '@site/src/components/CliCommandBlock';
import styles from './styles.module.css';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
diff --git a/docs/src/pages/Home/Hero/styles.module.css b/docs/src/components/Home/Hero/styles.module.css
similarity index 100%
rename from docs/src/pages/Home/Hero/styles.module.css
rename to docs/src/components/Home/Hero/styles.module.css
diff --git a/docs/src/pages/Home/Highlights/index.tsx b/docs/src/components/Home/Highlights/index.tsx
similarity index 100%
rename from docs/src/pages/Home/Highlights/index.tsx
rename to docs/src/components/Home/Highlights/index.tsx
diff --git a/docs/src/pages/Home/Highlights/styles.module.css b/docs/src/components/Home/Highlights/styles.module.css
similarity index 100%
rename from docs/src/pages/Home/Highlights/styles.module.css
rename to docs/src/components/Home/Highlights/styles.module.css
diff --git a/docs/src/pages/Home/Logo.tsx b/docs/src/components/Home/Logo.tsx
similarity index 100%
rename from docs/src/pages/Home/Logo.tsx
rename to docs/src/components/Home/Logo.tsx
diff --git a/docs/src/components/Home/index.tsx b/docs/src/components/Home/index.tsx
new file mode 100644
index 0000000..f862950
--- /dev/null
+++ b/docs/src/components/Home/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import Hero from '@site/src/components/Home/Hero';
+import Highlights from '@site/src/components/Home/Highlights';
+import CallToAction from '@site/src/components/Home/CallToAction';
+
+export default function Home() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/docs/src/css/customTheme.scss b/docs/src/css/customTheme.scss
index cc56d86..021d40b 100644
--- a/docs/src/css/customTheme.scss
+++ b/docs/src/css/customTheme.scss
@@ -46,43 +46,45 @@
--ifm-navbar-sidebar-width: 340px;
}
- --logo: #087ea4;
- --home-hero-floor-background: rgb(236, 248, 250);
- --home-hero-floor-background-bottom: white;
- --home-button-primary: #087ea4;
- --home-button-primary-text: white;
- --home-button-primary-hover: #4e98b6;
- --home-button-secondary: white;
- --home-button-secondary-text: #404756;
- --home-button-secondary-border: #bcc1cd;
- --home-button-secondary-hover: #f8f9fa;
- --home-hero-devices-border: #5e687e;
- --home-hero-devices-stop: #66728b;
- --home-hero-devices-skeleton-element: #cfe6ee;
- --home-hero-devices-background: #fff;
- --home-hero-devices-icon: #000;
- --home-hero-devices-icon-border: #e5e7eb;
- --home-hero-floor: #acddec;
- --home-hero-grid-icon-background: #f8f9fa;
- --home-hero-grid-icon: #e1e4e8;
- --home-hero-grid-grid: #ced4da;
- --home-section-top: white;
- --home-section-bottom: #efeff2;
- --home-secondary-text: #404756;
- --home-feature-background-1: #f1dbfc;
- --home-feature-background-2: #e1eefc;
- --home-feature-background-3: #d4f3e7;
- --home-border: #e5e7eb;
- --home-background: #fff;
- --home-secondary-background: #f6f7f9;
- --home-text: #1c1e21;
- --home-code-red: #d73a49;
- --home-code-purple: #6f42c1;
- --home-code-green: #22863a;
- --home-code-diff-removed: #f8514926;
- --home-code-diff-removed-word: #f8514966;
- --home-code-diff-added: #2ea04326;
- --home-code-diff-added-word: #2ea04366;
+ & {
+ --logo: #087ea4;
+ --home-hero-floor-background: rgb(236, 248, 250);
+ --home-hero-floor-background-bottom: white;
+ --home-button-primary: #087ea4;
+ --home-button-primary-text: white;
+ --home-button-primary-hover: #4e98b6;
+ --home-button-secondary: white;
+ --home-button-secondary-text: #404756;
+ --home-button-secondary-border: #bcc1cd;
+ --home-button-secondary-hover: #f8f9fa;
+ --home-hero-devices-border: #5e687e;
+ --home-hero-devices-stop: #66728b;
+ --home-hero-devices-skeleton-element: #cfe6ee;
+ --home-hero-devices-background: #fff;
+ --home-hero-devices-icon: #000;
+ --home-hero-devices-icon-border: #e5e7eb;
+ --home-hero-floor: #acddec;
+ --home-hero-grid-icon-background: #f8f9fa;
+ --home-hero-grid-icon: #e1e4e8;
+ --home-hero-grid-grid: #ced4da;
+ --home-section-top: white;
+ --home-section-bottom: #efeff2;
+ --home-secondary-text: #404756;
+ --home-feature-background-1: #f1dbfc;
+ --home-feature-background-2: #e1eefc;
+ --home-feature-background-3: #d4f3e7;
+ --home-border: #e5e7eb;
+ --home-background: #fff;
+ --home-secondary-background: #f6f7f9;
+ --home-text: #1c1e21;
+ --home-code-red: #d73a49;
+ --home-code-purple: #6f42c1;
+ --home-code-green: #22863a;
+ --home-code-diff-removed: #f8514926;
+ --home-code-diff-removed-word: #f8514966;
+ --home-code-diff-added: #2ea04326;
+ --home-code-diff-added-word: #2ea04366;
+ }
@media (max-width: 900px) {
--home-hero-grid-icon: transparent;
@@ -116,43 +118,45 @@ html[data-theme="dark"] {
--docsearch-container-background: rgba(0, 0, 0, 0.6);
}
- --logo: #58c4dc;
- --home-hero-floor-background: #151517;
- --home-hero-floor-background-bottom: #1b1b1d;
- --home-button-primary: #58c4dc;
- --home-button-primary-text: #1b1b1d;
- --home-button-primary-hover: #71d6ec;
- --home-button-secondary: #1b1b1d;
- --home-button-secondary-text: #f6f7f9;
- --home-button-secondary-border: #4e5668;
- --home-button-secondary-hover: #2b2b2d;
- --home-hero-devices-border: #404756;
- --home-hero-devices-stop: #4a5160;
- --home-hero-devices-skeleton-element: #404756;
- --home-hero-devices-background: #242426;
- --home-hero-devices-icon: #fff;
- --home-hero-devices-icon-border: #404756;
- --home-hero-floor: #30363d;
- --home-hero-grid-icon-background: #1e2025;
- --home-hero-grid-icon: #282c36;
- --home-hero-grid-grid: #30363d;
- --home-section-top: #1b1b1d;
- --home-section-bottom: #111113;
- --home-secondary-text: #c0c1c4;
- --home-feature-background-1: #333049;
- --home-feature-background-2: #1c3950;
- --home-feature-background-3: #1d413e;
- --home-border: #404756;
- --home-background: #242426;
- --home-secondary-background: #2c2c2e;
- --home-text: #e3e3e3;
- --home-code-red: #f46b78;
- --home-code-purple: #a77cf7;
- --home-code-green: #74e68f;
- --home-code-diff-removed: #f8514926;
- --home-code-diff-removed-word: #f8514966;
- --home-code-diff-added: #2ea04326;
- --home-code-diff-added-word: #2ea04366;
+ & {
+ --logo: #58c4dc;
+ --home-hero-floor-background: #151517;
+ --home-hero-floor-background-bottom: #1b1b1d;
+ --home-button-primary: #58c4dc;
+ --home-button-primary-text: #1b1b1d;
+ --home-button-primary-hover: #71d6ec;
+ --home-button-secondary: #1b1b1d;
+ --home-button-secondary-text: #f6f7f9;
+ --home-button-secondary-border: #4e5668;
+ --home-button-secondary-hover: #2b2b2d;
+ --home-hero-devices-border: #404756;
+ --home-hero-devices-stop: #4a5160;
+ --home-hero-devices-skeleton-element: #404756;
+ --home-hero-devices-background: #242426;
+ --home-hero-devices-icon: #fff;
+ --home-hero-devices-icon-border: #404756;
+ --home-hero-floor: #30363d;
+ --home-hero-grid-icon-background: #1e2025;
+ --home-hero-grid-icon: #282c36;
+ --home-hero-grid-grid: #30363d;
+ --home-section-top: #1b1b1d;
+ --home-section-bottom: #111113;
+ --home-secondary-text: #c0c1c4;
+ --home-feature-background-1: #333049;
+ --home-feature-background-2: #1c3950;
+ --home-feature-background-3: #1d413e;
+ --home-border: #404756;
+ --home-background: #242426;
+ --home-secondary-background: #2c2c2e;
+ --home-text: #e3e3e3;
+ --home-code-red: #f46b78;
+ --home-code-purple: #a77cf7;
+ --home-code-green: #74e68f;
+ --home-code-diff-removed: #f8514926;
+ --home-code-diff-removed-word: #f8514966;
+ --home-code-diff-added: #2ea04326;
+ --home-code-diff-added-word: #2ea04366;
+ }
@media (max-width: 900px) {
--home-hero-grid-icon: transparent;
diff --git a/docs/src/pages/Home/index.tsx b/docs/src/pages/Home/index.tsx
deleted file mode 100644
index 5709bce..0000000
--- a/docs/src/pages/Home/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-
-import Hero from '@site/src/pages/Home/Hero';
-import Highlights from '@site/src/pages/Home/Highlights';
-import CallToAction from '@site/src/pages/Home/CallToAction';
-
-export default function Home() {
- return (
- <>
-
-
-
- >
- );
-}
diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx
index dfe2061..b4996aa 100644
--- a/docs/src/pages/index.tsx
+++ b/docs/src/pages/index.tsx
@@ -6,13 +6,14 @@
*/
import Head from '@docusaurus/Head';
-import Home from '@site/src/pages/Home';
+import Home from '@site/src/components/Home';
import Layout from '@theme/Layout';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import { translate } from '@docusaurus/Translate';
const Index = () => {
- const { siteConfig } = useDocusaurusContext();
+ const {siteConfig} = useDocusaurusContext();
+ const canonicalUrl = `${siteConfig.url}${siteConfig.baseUrl}`.replace(/\/$/, '') + '/';
const title = `${siteConfig.title} · ${siteConfig.tagline}`;
const description = translate({
message:
@@ -24,8 +25,12 @@ const Index = () => {
{title}
+
+
+
+
diff --git a/docs/src/pages/markdown-page.md b/docs/src/pages/markdown-page.md
index 9756c5b..ebeccec 100644
--- a/docs/src/pages/markdown-page.md
+++ b/docs/src/pages/markdown-page.md
@@ -1,5 +1,6 @@
---
title: Markdown page example
+unlisted: true
---
# Markdown page example
diff --git a/docs/static/robots.txt b/docs/static/robots.txt
index 6f27bb6..df456fe 100644
--- a/docs/static/robots.txt
+++ b/docs/static/robots.txt
@@ -1,2 +1,5 @@
User-agent: *
-Disallow:
\ No newline at end of file
+Allow: /
+
+Sitemap: https://reactpress.surge.sh/sitemap.xml
+Sitemap: https://reactpress.surge.sh/zh/sitemap.xml
diff --git a/docs/tutorial/intro.md b/docs/tutorial/intro.md
index 01f4a7d..2cba26b 100644
--- a/docs/tutorial/intro.md
+++ b/docs/tutorial/intro.md
@@ -2,6 +2,8 @@
sidebar_position: 1
id: intro
title: 介绍
+description: ReactPress 开源发布平台与 CMS:基于 React、Next.js、NestJS,一条命令零配置起站。
+keywords: [reactpress, cms, 博客, next.js, react]
---
## 项目简介
diff --git a/package.json b/package.json
index 3e476da..abcd70a 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,8 @@
"publish:build": "node ./cli/bin/reactpress.js publish --build",
"prepare": "husky",
"precommit": "lint-staged",
+ "test": "pnpm --dir cli test",
+ "test:cli": "pnpm --dir cli test",
"test:smoke": "node scripts/smoke-api-health.mjs",
"test:benchmark": "node scripts/benchmark-cold-start.mjs"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fddafe5..6633ad5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2004,74 +2004,98 @@ packages:
'@corex/deepmerge@2.6.148':
resolution: {integrity: sha512-6QMz0/2h5C3ua51iAnXMPWFbb1QOU1UvSM4bKBw5mzdT+WtLgjbETBBIQZ+Sh9WvEcGwlAt/DEdRpIC3XlDBMA==}
- '@csstools/cascade-layer-name-parser@2.0.4':
- resolution: {integrity: sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==}
+ '@csstools/cascade-layer-name-parser@2.0.5':
+ resolution: {integrity: sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==}
engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.4
- '@csstools/css-tokenizer': ^3.0.3
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
- '@csstools/color-helpers@5.0.2':
- resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
engines: {node: '>=18'}
- '@csstools/css-calc@2.1.2':
- resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==}
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.4
- '@csstools/css-tokenizer': ^3.0.3
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
- '@csstools/css-color-parser@3.0.8':
- resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==}
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.4
- '@csstools/css-tokenizer': ^3.0.3
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
- '@csstools/css-parser-algorithms@3.0.4':
- resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-tokenizer': ^3.0.3
+ '@csstools/css-tokenizer': ^3.0.4
- '@csstools/css-tokenizer@3.0.3':
- resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
- '@csstools/media-query-list-parser@4.0.2':
- resolution: {integrity: sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==}
+ '@csstools/media-query-list-parser@4.0.3':
+ resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==}
engines: {node: '>=18'}
peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.4
- '@csstools/css-tokenizer': ^3.0.3
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
- '@csstools/postcss-cascade-layers@5.0.1':
- resolution: {integrity: sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==}
+ '@csstools/postcss-alpha-function@1.0.1':
+ resolution: {integrity: sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-color-function@4.0.8':
- resolution: {integrity: sha512-9dUvP2qpZI6PlGQ/sob+95B3u5u7nkYt9yhZFCC7G9HBRHBxj+QxS/wUlwaMGYW0waf+NIierI8aoDTssEdRYw==}
+ '@csstools/postcss-cascade-layers@5.0.2':
+ resolution: {integrity: sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-color-mix-function@3.0.8':
- resolution: {integrity: sha512-yuZpgWUzqZWQhEqfvtJufhl28DgO9sBwSbXbf/59gejNuvZcoUTRGQZhzhwF4ccqb53YAGB+u92z9+eSKoB4YA==}
+ '@csstools/postcss-color-function-display-p3-linear@1.0.1':
+ resolution: {integrity: sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-content-alt-text@2.0.4':
- resolution: {integrity: sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==}
+ '@csstools/postcss-color-function@4.0.12':
+ resolution: {integrity: sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-exponential-functions@2.0.7':
- resolution: {integrity: sha512-XTb6Mw0v2qXtQYRW9d9duAjDnoTbBpsngD7sRNLmYDjvwU2ebpIHplyxgOeo6jp/Kr52gkLi5VaK5RDCqzMzZQ==}
+ '@csstools/postcss-color-mix-function@3.0.12':
+ resolution: {integrity: sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2':
+ resolution: {integrity: sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-content-alt-text@2.0.8':
+ resolution: {integrity: sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-contrast-color-function@2.0.12':
+ resolution: {integrity: sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-exponential-functions@2.0.9':
+ resolution: {integrity: sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -2082,26 +2106,26 @@ packages:
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-gamut-mapping@2.0.8':
- resolution: {integrity: sha512-/K8u9ZyGMGPjmwCSIjgaOLKfic2RIGdFHHes84XW5LnmrvdhOTVxo255NppHi3ROEvoHPW7MplMJgjZK5Q+TxA==}
+ '@csstools/postcss-gamut-mapping@2.0.11':
+ resolution: {integrity: sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-gradients-interpolation-method@5.0.8':
- resolution: {integrity: sha512-CoHQ/0UXrvxLovu0ZeW6c3/20hjJ/QRg6lyXm3dZLY/JgvRU6bdbQZF/Du30A4TvowfcgvIHQmP1bNXUxgDrAw==}
+ '@csstools/postcss-gradients-interpolation-method@5.0.12':
+ resolution: {integrity: sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-hwb-function@4.0.8':
- resolution: {integrity: sha512-LpFKjX6hblpeqyych1cKmk+3FJZ19QmaJtqincySoMkbkG/w2tfbnO5oE6mlnCTXcGUJ0rCEuRHvTqKK0nHYUQ==}
+ '@csstools/postcss-hwb-function@4.0.12':
+ resolution: {integrity: sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-ic-unit@4.0.0':
- resolution: {integrity: sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==}
+ '@csstools/postcss-ic-unit@4.0.4':
+ resolution: {integrity: sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -2112,14 +2136,14 @@ packages:
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-is-pseudo-class@5.0.1':
- resolution: {integrity: sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==}
+ '@csstools/postcss-is-pseudo-class@5.0.3':
+ resolution: {integrity: sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-light-dark-function@2.0.7':
- resolution: {integrity: sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==}
+ '@csstools/postcss-light-dark-function@2.0.11':
+ resolution: {integrity: sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -2148,20 +2172,20 @@ packages:
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-logical-viewport-units@3.0.3':
- resolution: {integrity: sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==}
+ '@csstools/postcss-logical-viewport-units@3.0.4':
+ resolution: {integrity: sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-media-minmax@2.0.7':
- resolution: {integrity: sha512-LB6tIP7iBZb5CYv8iRenfBZmbaG3DWNEziOnPjGoQX5P94FBPvvTBy68b/d9NnS5PELKwFmmOYsAEIgEhDPCHA==}
+ '@csstools/postcss-media-minmax@2.0.9':
+ resolution: {integrity: sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4':
- resolution: {integrity: sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==}
+ '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5':
+ resolution: {integrity: sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -2172,32 +2196,44 @@ packages:
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-normalize-display-values@4.0.0':
- resolution: {integrity: sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==}
+ '@csstools/postcss-normalize-display-values@4.0.1':
+ resolution: {integrity: sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-oklab-function@4.0.8':
- resolution: {integrity: sha512-+5aPsNWgxohXoYNS1f+Ys0x3Qnfehgygv3qrPyv+Y25G0yX54/WlVB+IXprqBLOXHM1gsVF+QQSjlArhygna0Q==}
+ '@csstools/postcss-oklab-function@4.0.12':
+ resolution: {integrity: sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-progressive-custom-properties@4.0.0':
- resolution: {integrity: sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==}
+ '@csstools/postcss-position-area-property@1.0.0':
+ resolution: {integrity: sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-random-function@1.0.3':
- resolution: {integrity: sha512-dbNeEEPHxAwfQJ3duRL5IPpuD77QAHtRl4bAHRs0vOVhVbHrsL7mHnwe0irYjbs9kYwhAHZBQTLBgmvufPuRkA==}
+ '@csstools/postcss-progressive-custom-properties@4.2.1':
+ resolution: {integrity: sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-relative-color-syntax@3.0.8':
- resolution: {integrity: sha512-eGE31oLnJDoUysDdjS9MLxNZdtqqSxjDXMdISpLh80QMaYrKs7VINpid34tWQ+iU23Wg5x76qAzf1Q/SLLbZVg==}
+ '@csstools/postcss-property-rule-prelude-list@1.0.0':
+ resolution: {integrity: sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-random-function@2.0.1':
+ resolution: {integrity: sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-relative-color-syntax@3.0.12':
+ resolution: {integrity: sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -2208,26 +2244,38 @@ packages:
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-sign-functions@1.1.2':
- resolution: {integrity: sha512-4EcAvXTUPh7n6UoZZkCzgtCf/wPzMlTNuddcKg7HG8ozfQkUcHsJ2faQKeLmjyKdYPyOUn4YA7yDPf8K/jfIxw==}
+ '@csstools/postcss-sign-functions@1.1.4':
+ resolution: {integrity: sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-stepped-value-functions@4.0.9':
+ resolution: {integrity: sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-stepped-value-functions@4.0.7':
- resolution: {integrity: sha512-rdrRCKRnWtj5FyRin0u/gLla7CIvZRw/zMGI1fVJP0Sg/m1WGicjPVHRANL++3HQtsiXKAbPrcPr+VkyGck0IA==}
+ '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1':
+ resolution: {integrity: sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-text-decoration-shorthand@4.0.2':
- resolution: {integrity: sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA==}
+ '@csstools/postcss-system-ui-font-family@1.0.0':
+ resolution: {integrity: sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- '@csstools/postcss-trigonometric-functions@4.0.7':
- resolution: {integrity: sha512-qTrZgLju3AV7Djhzuh2Bq/wjFqbcypnk0FhHjxW8DWJQcZLS1HecIus4X2/RLch1ukX7b+YYCdqbEnpIQO5ccg==}
+ '@csstools/postcss-text-decoration-shorthand@4.0.3':
+ resolution: {integrity: sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss: ^8.4
+
+ '@csstools/postcss-trigonometric-functions@4.0.9':
+ resolution: {integrity: sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -2238,8 +2286,8 @@ packages:
peerDependencies:
postcss: ^8.4
- '@csstools/selector-resolve-nested@3.0.0':
- resolution: {integrity: sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==}
+ '@csstools/selector-resolve-nested@3.1.0':
+ resolution: {integrity: sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==}
engines: {node: '>=18'}
peerDependencies:
postcss-selector-parser: ^7.0.0
@@ -4165,8 +4213,8 @@ packages:
engines: {node: '>= 4.5.0'}
hasBin: true
- autoprefixer@10.4.20:
- resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
+ autoprefixer@10.5.0:
+ resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
@@ -4288,6 +4336,11 @@ packages:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
engines: {node: '>=0.10.0'}
+ baseline-browser-mapping@2.10.31:
+ resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
basic-ftp@5.3.1:
resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==}
engines: {node: '>=10.0.0'}
@@ -4343,6 +4396,10 @@ packages:
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ body-parser@1.20.5:
+ resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
bonjour-service@1.3.0:
resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==}
@@ -4416,6 +4473,11 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ browserslist@4.28.2:
+ resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
bs-logger@0.2.6:
resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
engines: {node: '>= 6'}
@@ -4546,6 +4608,9 @@ packages:
caniuse-lite@1.0.30001701:
resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==}
+ caniuse-lite@1.0.30001793:
+ resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
+
capture-exit@2.0.0:
resolution: {integrity: sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==}
engines: {node: 6.* || 8.* || >= 10.*}
@@ -4870,6 +4935,10 @@ packages:
resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
engines: {node: '>= 0.8.0'}
+ compression@1.8.1:
+ resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==}
+ engines: {node: '>= 0.8.0'}
+
compute-scroll-into-view@3.1.0:
resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==}
@@ -5099,8 +5168,8 @@ packages:
peerDependencies:
postcss: ^8.0.9
- css-has-pseudo@7.0.2:
- resolution: {integrity: sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==}
+ css-has-pseudo@7.0.3:
+ resolution: {integrity: sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -5169,8 +5238,8 @@ packages:
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
- cssdb@8.2.3:
- resolution: {integrity: sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA==}
+ cssdb@8.9.0:
+ resolution: {integrity: sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
@@ -5624,6 +5693,9 @@ packages:
electron-to-chromium@1.5.27:
resolution: {integrity: sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==}
+ electron-to-chromium@1.5.359:
+ resolution: {integrity: sha512-8lPELWuYZIWk7NDvCNthtmMw/7Q5Wu25NpM4djFMHBmk8DubPAtL4YTOp7ou0e7HyJtwkVlWv8XMLURnrtgJQw==}
+
elliptic@6.6.1:
resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==}
@@ -6107,6 +6179,10 @@ packages:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'}
+ express@4.22.2:
+ resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==}
+ engines: {node: '>= 0.10.0'}
+
ext@1.7.0:
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
@@ -6393,8 +6469,8 @@ packages:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
- fraction.js@4.3.7:
- resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
fragment-cache@0.2.1:
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
@@ -6855,6 +6931,10 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
+ http-errors@2.0.1:
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
+ engines: {node: '>= 0.8'}
+
http-parser-js@0.5.9:
resolution: {integrity: sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==}
@@ -6862,8 +6942,8 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
- http-proxy-middleware@2.0.7:
- resolution: {integrity: sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==}
+ http-proxy-middleware@2.0.9:
+ resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/express': ^4.17.13
@@ -8414,6 +8494,9 @@ packages:
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
@@ -8495,6 +8578,11 @@ packages:
nan@2.20.0:
resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==}
+ nanoid@3.3.12:
+ resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
nanoid@3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -8529,6 +8617,10 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
+ negotiator@0.6.4:
+ resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
+ engines: {node: '>= 0.6'}
+
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -8691,6 +8783,9 @@ packages:
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+ node-releases@2.0.44:
+ resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==}
+
node-stream-zip@1.15.0:
resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==}
engines: {node: '>=0.12.0'}
@@ -8713,10 +8808,6 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- normalize-range@0.1.2:
- resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
- engines: {node: '>=0.10.0'}
-
normalize-url@8.0.1:
resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
engines: {node: '>=14.16'}
@@ -8860,6 +8951,10 @@ packages:
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
engines: {node: '>= 0.8'}
+ on-headers@1.1.0:
+ resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==}
+ engines: {node: '>= 0.8'}
+
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -9278,8 +9373,8 @@ packages:
peerDependencies:
postcss: ^8.4.6
- postcss-color-functional-notation@7.0.8:
- resolution: {integrity: sha512-S/TpMKVKofNvsxfau/+bw+IA6cSfB6/kmzFj5szUofHOVnFFMB2WwK+Zu07BeMD8T0n+ZnTO5uXiMvAKe2dPkA==}
+ postcss-color-functional-notation@7.0.12:
+ resolution: {integrity: sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -9308,20 +9403,20 @@ packages:
peerDependencies:
postcss: ^8.4.31
- postcss-custom-media@11.0.5:
- resolution: {integrity: sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==}
+ postcss-custom-media@11.0.6:
+ resolution: {integrity: sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- postcss-custom-properties@14.0.4:
- resolution: {integrity: sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==}
+ postcss-custom-properties@14.0.6:
+ resolution: {integrity: sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
- postcss-custom-selectors@8.0.4:
- resolution: {integrity: sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==}
+ postcss-custom-selectors@8.0.5:
+ resolution: {integrity: sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -9362,8 +9457,8 @@ packages:
peerDependencies:
postcss: ^8.4.31
- postcss-double-position-gradients@6.0.0:
- resolution: {integrity: sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==}
+ postcss-double-position-gradients@6.0.4:
+ resolution: {integrity: sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -9397,8 +9492,8 @@ packages:
peerDependencies:
postcss: ^8.4
- postcss-lab-function@7.0.8:
- resolution: {integrity: sha512-plV21I86Hg9q8omNz13G9fhPtLopIWH06bt/Cb5cs1XnaGU2kUtEitvVd4vtQb/VqCdNUHK5swKn3QFmMRbpDg==}
+ postcss-lab-function@7.0.12:
+ resolution: {integrity: sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -9482,8 +9577,8 @@ packages:
peerDependencies:
postcss: ^8.1.0
- postcss-nesting@13.0.1:
- resolution: {integrity: sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==}
+ postcss-nesting@13.0.2:
+ resolution: {integrity: sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -9571,8 +9666,8 @@ packages:
peerDependencies:
postcss: ^8.4
- postcss-preset-env@10.1.5:
- resolution: {integrity: sha512-LQybafF/K7H+6fAs4SIkgzkSCixJy0/h0gubDIAP3Ihz+IQBRwsjyvBnAZ3JUHD+A/ITaxVRPDxn//a3Qy4pDw==}
+ postcss-preset-env@10.6.1:
+ resolution: {integrity: sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
@@ -9651,6 +9746,10 @@ packages:
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
engines: {node: ^10 || ^12 || >=14}
+ postcss@8.5.14:
+ resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==}
+ engines: {node: ^10 || ^12 || >=14}
+
postcss@8.5.3:
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
@@ -9813,6 +9912,10 @@ packages:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
+ qs@6.15.2:
+ resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
+ engines: {node: '>=0.6'}
+
qs@6.5.3:
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
engines: {node: '>=0.6'}
@@ -9860,6 +9963,10 @@ packages:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
+ raw-body@2.5.3:
+ resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
+ engines: {node: '>= 0.8'}
+
rc-animate@2.11.1:
resolution: {integrity: sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==}
peerDependencies:
@@ -10208,8 +10315,8 @@ packages:
react-lifecycles-compat@3.0.4:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
- react-loadable-ssr-addon-v5-slorber@1.0.1:
- resolution: {integrity: sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==}
+ react-loadable-ssr-addon-v5-slorber@1.0.3:
+ resolution: {integrity: sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==}
engines: {node: '>=10.13.0'}
peerDependencies:
react-loadable: '*'
@@ -10837,8 +10944,8 @@ packages:
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
- serve-handler@6.1.6:
- resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==}
+ serve-handler@6.1.7:
+ resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==}
serve-index@1.9.1:
resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==}
@@ -11175,6 +11282,10 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
std-env@3.8.1:
resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==}
@@ -11698,6 +11809,9 @@ packages:
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
tslint@5.20.1:
resolution: {integrity: sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==}
engines: {node: '>=4.8.0'}
@@ -11982,6 +12096,12 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
update-notifier@6.0.2:
resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==}
engines: {node: '>=14.16'}
@@ -14556,247 +14676,304 @@ snapshots:
'@corex/deepmerge@2.6.148': {}
- '@csstools/cascade-layer-name-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
+ '@csstools/cascade-layer-name-parser@2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
- '@csstools/color-helpers@5.0.2': {}
+ '@csstools/color-helpers@5.1.0': {}
- '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
- '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
dependencies:
- '@csstools/color-helpers': 5.0.2
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
- '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
dependencies:
- '@csstools/css-tokenizer': 3.0.3
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
- '@csstools/css-tokenizer@3.0.3': {}
+ '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
- '@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
+ '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.14)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-cascade-layers@5.0.1(postcss@8.5.3)':
+ '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.14)':
dependencies:
'@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0)
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- '@csstools/postcss-color-function@4.0.8(postcss@8.5.3)':
+ '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-color-mix-function@3.0.8(postcss@8.5.3)':
+ '@csstools/postcss-color-function@4.0.12(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-content-alt-text@2.0.4(postcss@8.5.3)':
+ '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.14)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-exponential-functions@2.0.7(postcss@8.5.3)':
+ '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.14)':
dependencies:
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.3)':
+ '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.14)':
dependencies:
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
+
+ '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.14)':
+ dependencies:
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
+
+ '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.14)':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
+
+ '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.14)':
+ dependencies:
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-gamut-mapping@2.0.8(postcss@8.5.3)':
+ '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
- '@csstools/postcss-gradients-interpolation-method@5.0.8(postcss@8.5.3)':
+ '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-hwb-function@4.0.8(postcss@8.5.3)':
+ '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-ic-unit@4.0.0(postcss@8.5.3)':
+ '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.14)':
dependencies:
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-initial@2.0.1(postcss@8.5.3)':
+ '@csstools/postcss-initial@2.0.1(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- '@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.5.3)':
+ '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.14)':
dependencies:
'@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0)
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- '@csstools/postcss-light-dark-function@2.0.7(postcss@8.5.3)':
+ '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.14)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.3)':
+ '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.3)':
+ '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.3)':
+ '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.3)':
+ '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-logical-viewport-units@3.0.3(postcss@8.5.3)':
+ '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.14)':
dependencies:
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-media-minmax@2.0.7(postcss@8.5.3)':
+ '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.14)':
dependencies:
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- postcss: 8.5.3
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ postcss: 8.5.14
- '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4(postcss@8.5.3)':
+ '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- postcss: 8.5.3
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ postcss: 8.5.14
- '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.3)':
+ '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.14)':
dependencies:
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.3)':
+ '@csstools/postcss-normalize-display-values@4.0.1(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-oklab-function@4.0.8(postcss@8.5.3)':
+ '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- '@csstools/postcss-progressive-custom-properties@4.0.0(postcss@8.5.3)':
+ '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
+
+ '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.14)':
+ dependencies:
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-random-function@1.0.3(postcss@8.5.3)':
+ '@csstools/postcss-property-rule-prelude-list@1.0.0(postcss@8.5.14)':
dependencies:
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
- '@csstools/postcss-relative-color-syntax@3.0.8(postcss@8.5.3)':
+ '@csstools/postcss-random-function@2.0.1(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
- '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.3)':
+ '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
+
+ '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.14)':
+ dependencies:
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- '@csstools/postcss-sign-functions@1.1.2(postcss@8.5.3)':
+ '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.14)':
dependencies:
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
- '@csstools/postcss-stepped-value-functions@4.0.7(postcss@8.5.3)':
+ '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.14)':
dependencies:
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
- '@csstools/postcss-text-decoration-shorthand@4.0.2(postcss@8.5.3)':
+ '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1(postcss@8.5.14)':
dependencies:
- '@csstools/color-helpers': 5.0.2
- postcss: 8.5.3
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
+
+ '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.14)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
+
+ '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.14)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-trigonometric-functions@4.0.7(postcss@8.5.3)':
+ '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.14)':
dependencies:
- '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
- '@csstools/postcss-unset-value@4.0.0(postcss@8.5.3)':
+ '@csstools/postcss-unset-value@4.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- '@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.1.0)':
+ '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.0)':
dependencies:
postcss-selector-parser: 7.1.0
@@ -14804,9 +14981,9 @@ snapshots:
dependencies:
postcss-selector-parser: 7.1.0
- '@csstools/utilities@2.0.0(postcss@8.5.3)':
+ '@csstools/utilities@2.0.0(postcss@8.5.14)':
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
'@ctrl/tinycolor@3.6.1': {}
@@ -14844,7 +15021,7 @@ snapshots:
'@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
babel-plugin-dynamic-import-node: 2.3.3
fs-extra: 11.3.0
- tslib: 2.7.0
+ tslib: 2.8.1
transitivePeerDependencies:
- '@swc/core'
- acorn
@@ -14868,17 +15045,17 @@ snapshots:
copy-webpack-plugin: 11.0.0(webpack@5.98.0)
css-loader: 6.11.0(webpack@5.98.0)
css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.98.0)
- cssnano: 6.1.2(postcss@8.5.3)
+ cssnano: 6.1.2(postcss@8.5.14)
file-loader: 6.2.0(webpack@5.98.0)
html-minifier-terser: 7.2.0
mini-css-extract-plugin: 2.9.2(webpack@5.98.0)
null-loader: 4.0.1(webpack@5.98.0)
- postcss: 8.5.3
- postcss-loader: 7.3.4(postcss@8.5.3)(typescript@5.6.3)(webpack@5.98.0)
- postcss-preset-env: 10.1.5(postcss@8.5.3)
+ postcss: 8.5.14
+ postcss-loader: 7.3.4(postcss@8.5.14)(typescript@5.6.3)(webpack@5.98.0)
+ postcss-preset-env: 10.6.1(postcss@8.5.14)
react-dev-utils: 12.0.1(eslint@9.36.0(jiti@1.21.7))(typescript@5.6.3)(webpack@5.98.0)
- terser-webpack-plugin: 5.3.10(webpack@5.98.0)
- tslib: 2.7.0
+ terser-webpack-plugin: 5.3.12(webpack@5.98.0)
+ tslib: 2.8.1
url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0))(webpack@5.98.0)
webpack: 5.98.0
webpackbar: 6.0.1(webpack@5.98.0)
@@ -14934,14 +15111,14 @@ snapshots:
react-dom: 19.0.0(react@19.0.0)
react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)'
react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)'
- react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.98.0)
+ react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.98.0)
react-router: 5.3.4(react@19.0.0)
react-router-config: 5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0)
react-router-dom: 5.3.4(react@19.0.0)
semver: 7.6.3
- serve-handler: 6.1.6
+ serve-handler: 6.1.7
shelljs: 0.8.5
- tslib: 2.7.0
+ tslib: 2.8.1
update-notifier: 6.0.2
webpack: 5.98.0
webpack-bundle-analyzer: 4.10.2
@@ -14969,15 +15146,15 @@ snapshots:
'@docusaurus/cssnano-preset@3.7.0':
dependencies:
- cssnano-preset-advanced: 6.1.2(postcss@8.5.3)
- postcss: 8.5.3
- postcss-sort-media-queries: 5.2.0(postcss@8.5.3)
- tslib: 2.7.0
+ cssnano-preset-advanced: 6.1.2(postcss@8.5.14)
+ postcss: 8.5.14
+ postcss-sort-media-queries: 5.2.0(postcss@8.5.14)
+ tslib: 2.8.1
'@docusaurus/logger@3.7.0':
dependencies:
chalk: 4.1.2
- tslib: 2.7.0
+ tslib: 2.8.1
'@docusaurus/mdx-loader@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
@@ -15001,7 +15178,7 @@ snapshots:
remark-frontmatter: 5.0.0
remark-gfm: 4.0.1
stringify-object: 3.3.0
- tslib: 2.7.0
+ tslib: 2.8.1
unified: 11.0.5
unist-util-visit: 5.0.0
url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0))(webpack@5.98.0)
@@ -15537,7 +15714,7 @@ snapshots:
'@docusaurus/utils-common@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
- tslib: 2.7.0
+ tslib: 2.8.1
transitivePeerDependencies:
- '@swc/core'
- acorn
@@ -15557,7 +15734,7 @@ snapshots:
joi: 17.13.3
js-yaml: 4.1.0
lodash: 4.17.21
- tslib: 2.7.0
+ tslib: 2.8.1
transitivePeerDependencies:
- '@swc/core'
- acorn
@@ -15586,7 +15763,7 @@ snapshots:
prompts: 2.4.2
resolve-pathname: 3.0.0
shelljs: 0.8.5
- tslib: 2.7.0
+ tslib: 2.8.1
url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0))(webpack@5.98.0)
utility-types: 3.11.0
webpack: 5.98.0
@@ -15888,7 +16065,9 @@ snapshots:
source-map: 0.6.1
string-length: 2.0.0
transitivePeerDependencies:
+ - bufferutil
- supports-color
+ - utf-8-validate
'@jest/schemas@29.6.3':
dependencies:
@@ -15913,7 +16092,9 @@ snapshots:
jest-runner: 24.9.0
jest-runtime: 24.9.0
transitivePeerDependencies:
+ - bufferutil
- supports-color
+ - utf-8-validate
'@jest/transform@24.9.0':
dependencies:
@@ -16447,7 +16628,7 @@ snapshots:
dependencies:
'@react-native-community/cli-debugger-ui': 14.1.0
'@react-native-community/cli-tools': 14.1.0
- compression: 1.7.4
+ compression: 1.8.1
connect: 3.7.0
errorhandler: 1.5.1
nocache: 3.0.4
@@ -18042,7 +18223,7 @@ snapshots:
ast-types@0.15.2:
dependencies:
- tslib: 2.7.0
+ tslib: 2.8.1
astral-regex@1.0.0: {}
@@ -18068,14 +18249,13 @@ snapshots:
atob@2.1.2: {}
- autoprefixer@10.4.20(postcss@8.5.3):
+ autoprefixer@10.5.0(postcss@8.5.14):
dependencies:
- browserslist: 4.24.4
- caniuse-lite: 1.0.30001701
- fraction.js: 4.3.7
- normalize-range: 0.1.2
+ browserslist: 4.28.2
+ caniuse-lite: 1.0.30001793
+ fraction.js: 5.3.4
picocolors: 1.1.1
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
available-typed-arrays@1.0.7:
@@ -18257,6 +18437,8 @@ snapshots:
mixin-deep: 1.3.2
pascalcase: 0.1.1
+ baseline-browser-mapping@2.10.31: {}
+
basic-ftp@5.3.1: {}
batch@0.6.1: {}
@@ -18326,6 +18508,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ body-parser@1.20.5:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.1
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.15.2
+ raw-body: 2.5.3
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
bonjour-service@1.3.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -18453,6 +18652,14 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
+ browserslist@4.28.2:
+ dependencies:
+ baseline-browser-mapping: 2.10.31
+ caniuse-lite: 1.0.30001793
+ electron-to-chromium: 1.5.359
+ node-releases: 2.0.44
+ update-browserslist-db: 1.2.3(browserslist@4.28.2)
+
bs-logger@0.2.6:
dependencies:
fast-json-stable-stringify: 2.1.0
@@ -18584,7 +18791,7 @@ snapshots:
camel-case@4.1.2:
dependencies:
pascal-case: 3.1.2
- tslib: 2.7.0
+ tslib: 2.8.1
camelcase@4.1.0: {}
@@ -18598,13 +18805,15 @@ snapshots:
caniuse-api@3.0.0:
dependencies:
- browserslist: 4.23.3
- caniuse-lite: 1.0.30001701
+ browserslist: 4.28.2
+ caniuse-lite: 1.0.30001793
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
caniuse-lite@1.0.30001701: {}
+ caniuse-lite@1.0.30001793: {}
+
capture-exit@2.0.0:
dependencies:
rsvp: 4.8.5
@@ -18978,6 +19187,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ compression@1.8.1:
+ dependencies:
+ bytes: 3.1.2
+ compressible: 2.0.18
+ debug: 2.6.9
+ negotiator: 0.6.4
+ on-headers: 1.1.0
+ safe-buffer: 5.2.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
compute-scroll-into-view@3.1.0: {}
concat-map@0.0.1: {}
@@ -19107,7 +19328,7 @@ snapshots:
core-js-compat@3.41.0:
dependencies:
- browserslist: 4.24.4
+ browserslist: 4.28.2
core-js-pure@3.41.0: {}
@@ -19240,30 +19461,30 @@ snapshots:
babel-runtime: 6.26.0
component-classes: 1.2.6
- css-blank-pseudo@7.0.1(postcss@8.5.3):
+ css-blank-pseudo@7.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- css-declaration-sorter@7.2.0(postcss@8.5.3):
+ css-declaration-sorter@7.2.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- css-has-pseudo@7.0.2(postcss@8.5.3):
+ css-has-pseudo@7.0.3(postcss@8.5.14):
dependencies:
'@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0)
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
postcss-value-parser: 4.2.0
css-loader@6.11.0(webpack@5.98.0):
dependencies:
- icss-utils: 5.1.0(postcss@8.5.3)
- postcss: 8.5.3
- postcss-modules-extract-imports: 3.1.0(postcss@8.5.3)
- postcss-modules-local-by-default: 4.2.0(postcss@8.5.3)
- postcss-modules-scope: 3.2.1(postcss@8.5.3)
- postcss-modules-values: 4.0.0(postcss@8.5.3)
+ icss-utils: 5.1.0(postcss@8.5.14)
+ postcss: 8.5.14
+ postcss-modules-extract-imports: 3.1.0(postcss@8.5.14)
+ postcss-modules-local-by-default: 4.2.0(postcss@8.5.14)
+ postcss-modules-scope: 3.2.1(postcss@8.5.14)
+ postcss-modules-values: 4.0.0(postcss@8.5.14)
postcss-value-parser: 4.2.0
semver: 7.6.3
optionalDependencies:
@@ -19272,18 +19493,18 @@ snapshots:
css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.98.0):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
- cssnano: 6.1.2(postcss@8.5.3)
+ cssnano: 6.1.2(postcss@8.5.14)
jest-worker: 29.7.0
- postcss: 8.5.3
+ postcss: 8.5.14
schema-utils: 4.3.0
serialize-javascript: 6.0.2
webpack: 5.98.0
optionalDependencies:
clean-css: 5.3.3
- css-prefers-color-scheme@10.0.0(postcss@8.5.3):
+ css-prefers-color-scheme@10.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
css-select@4.3.0:
dependencies:
@@ -19315,64 +19536,64 @@ snapshots:
css.escape@1.5.1: {}
- cssdb@8.2.3: {}
+ cssdb@8.9.0: {}
cssesc@3.0.0: {}
- cssnano-preset-advanced@6.1.2(postcss@8.5.3):
- dependencies:
- autoprefixer: 10.4.20(postcss@8.5.3)
- browserslist: 4.23.3
- cssnano-preset-default: 6.1.2(postcss@8.5.3)
- postcss: 8.5.3
- postcss-discard-unused: 6.0.5(postcss@8.5.3)
- postcss-merge-idents: 6.0.3(postcss@8.5.3)
- postcss-reduce-idents: 6.0.3(postcss@8.5.3)
- postcss-zindex: 6.0.2(postcss@8.5.3)
-
- cssnano-preset-default@6.1.2(postcss@8.5.3):
- dependencies:
- browserslist: 4.23.3
- css-declaration-sorter: 7.2.0(postcss@8.5.3)
- cssnano-utils: 4.0.2(postcss@8.5.3)
- postcss: 8.5.3
- postcss-calc: 9.0.1(postcss@8.5.3)
- postcss-colormin: 6.1.0(postcss@8.5.3)
- postcss-convert-values: 6.1.0(postcss@8.5.3)
- postcss-discard-comments: 6.0.2(postcss@8.5.3)
- postcss-discard-duplicates: 6.0.3(postcss@8.5.3)
- postcss-discard-empty: 6.0.3(postcss@8.5.3)
- postcss-discard-overridden: 6.0.2(postcss@8.5.3)
- postcss-merge-longhand: 6.0.5(postcss@8.5.3)
- postcss-merge-rules: 6.1.1(postcss@8.5.3)
- postcss-minify-font-values: 6.1.0(postcss@8.5.3)
- postcss-minify-gradients: 6.0.3(postcss@8.5.3)
- postcss-minify-params: 6.1.0(postcss@8.5.3)
- postcss-minify-selectors: 6.0.4(postcss@8.5.3)
- postcss-normalize-charset: 6.0.2(postcss@8.5.3)
- postcss-normalize-display-values: 6.0.2(postcss@8.5.3)
- postcss-normalize-positions: 6.0.2(postcss@8.5.3)
- postcss-normalize-repeat-style: 6.0.2(postcss@8.5.3)
- postcss-normalize-string: 6.0.2(postcss@8.5.3)
- postcss-normalize-timing-functions: 6.0.2(postcss@8.5.3)
- postcss-normalize-unicode: 6.1.0(postcss@8.5.3)
- postcss-normalize-url: 6.0.2(postcss@8.5.3)
- postcss-normalize-whitespace: 6.0.2(postcss@8.5.3)
- postcss-ordered-values: 6.0.2(postcss@8.5.3)
- postcss-reduce-initial: 6.1.0(postcss@8.5.3)
- postcss-reduce-transforms: 6.0.2(postcss@8.5.3)
- postcss-svgo: 6.0.3(postcss@8.5.3)
- postcss-unique-selectors: 6.0.4(postcss@8.5.3)
-
- cssnano-utils@4.0.2(postcss@8.5.3):
- dependencies:
- postcss: 8.5.3
-
- cssnano@6.1.2(postcss@8.5.3):
- dependencies:
- cssnano-preset-default: 6.1.2(postcss@8.5.3)
+ cssnano-preset-advanced@6.1.2(postcss@8.5.14):
+ dependencies:
+ autoprefixer: 10.5.0(postcss@8.5.14)
+ browserslist: 4.28.2
+ cssnano-preset-default: 6.1.2(postcss@8.5.14)
+ postcss: 8.5.14
+ postcss-discard-unused: 6.0.5(postcss@8.5.14)
+ postcss-merge-idents: 6.0.3(postcss@8.5.14)
+ postcss-reduce-idents: 6.0.3(postcss@8.5.14)
+ postcss-zindex: 6.0.2(postcss@8.5.14)
+
+ cssnano-preset-default@6.1.2(postcss@8.5.14):
+ dependencies:
+ browserslist: 4.28.2
+ css-declaration-sorter: 7.2.0(postcss@8.5.14)
+ cssnano-utils: 4.0.2(postcss@8.5.14)
+ postcss: 8.5.14
+ postcss-calc: 9.0.1(postcss@8.5.14)
+ postcss-colormin: 6.1.0(postcss@8.5.14)
+ postcss-convert-values: 6.1.0(postcss@8.5.14)
+ postcss-discard-comments: 6.0.2(postcss@8.5.14)
+ postcss-discard-duplicates: 6.0.3(postcss@8.5.14)
+ postcss-discard-empty: 6.0.3(postcss@8.5.14)
+ postcss-discard-overridden: 6.0.2(postcss@8.5.14)
+ postcss-merge-longhand: 6.0.5(postcss@8.5.14)
+ postcss-merge-rules: 6.1.1(postcss@8.5.14)
+ postcss-minify-font-values: 6.1.0(postcss@8.5.14)
+ postcss-minify-gradients: 6.0.3(postcss@8.5.14)
+ postcss-minify-params: 6.1.0(postcss@8.5.14)
+ postcss-minify-selectors: 6.0.4(postcss@8.5.14)
+ postcss-normalize-charset: 6.0.2(postcss@8.5.14)
+ postcss-normalize-display-values: 6.0.2(postcss@8.5.14)
+ postcss-normalize-positions: 6.0.2(postcss@8.5.14)
+ postcss-normalize-repeat-style: 6.0.2(postcss@8.5.14)
+ postcss-normalize-string: 6.0.2(postcss@8.5.14)
+ postcss-normalize-timing-functions: 6.0.2(postcss@8.5.14)
+ postcss-normalize-unicode: 6.1.0(postcss@8.5.14)
+ postcss-normalize-url: 6.0.2(postcss@8.5.14)
+ postcss-normalize-whitespace: 6.0.2(postcss@8.5.14)
+ postcss-ordered-values: 6.0.2(postcss@8.5.14)
+ postcss-reduce-initial: 6.1.0(postcss@8.5.14)
+ postcss-reduce-transforms: 6.0.2(postcss@8.5.14)
+ postcss-svgo: 6.0.3(postcss@8.5.14)
+ postcss-unique-selectors: 6.0.4(postcss@8.5.14)
+
+ cssnano-utils@4.0.2(postcss@8.5.14):
+ dependencies:
+ postcss: 8.5.14
+
+ cssnano@6.1.2(postcss@8.5.14):
+ dependencies:
+ cssnano-preset-default: 6.1.2(postcss@8.5.14)
lilconfig: 3.1.3
- postcss: 8.5.3
+ postcss: 8.5.14
csso@5.0.5:
dependencies:
@@ -19796,6 +20017,8 @@ snapshots:
electron-to-chromium@1.5.27: {}
+ electron-to-chromium@1.5.359: {}
+
elliptic@6.6.1:
dependencies:
bn.js: 4.12.3
@@ -20646,6 +20869,42 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ express@4.22.2:
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.5
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookie: 0.7.1
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 1.3.1
+ fresh: 0.5.2
+ http-errors: 2.0.1
+ merge-descriptors: 1.0.3
+ methods: 1.1.2
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ path-to-regexp: 0.1.12
+ proxy-addr: 2.0.7
+ qs: 6.15.2
+ range-parser: 1.2.1
+ safe-buffer: 5.2.1
+ send: 0.19.0
+ serve-static: 1.16.2
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ type-is: 1.6.18
+ utils-merge: 1.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
ext@1.7.0:
dependencies:
type: 2.7.3
@@ -20934,7 +21193,7 @@ snapshots:
fs-extra: 9.1.0
glob: 7.2.3
memfs: 3.5.3
- minimatch: 3.1.2
+ minimatch: 3.1.5
schema-utils: 2.7.0
semver: 7.6.3
tapable: 1.1.3
@@ -20976,7 +21235,7 @@ snapshots:
forwarded@0.2.0: {}
- fraction.js@4.3.7: {}
+ fraction.js@5.3.4: {}
fragment-cache@0.2.1:
dependencies:
@@ -21531,7 +21790,7 @@ snapshots:
html-minifier-terser: 6.1.0
lodash: 4.17.21
pretty-error: 4.0.0
- tapable: 2.2.1
+ tapable: 2.3.3
optionalDependencies:
webpack: 5.98.0
@@ -21584,6 +21843,14 @@ snapshots:
statuses: 2.0.1
toidentifier: 1.0.1
+ http-errors@2.0.1:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ toidentifier: 1.0.1
+
http-parser-js@0.5.9: {}
http-proxy-agent@7.0.2:
@@ -21593,7 +21860,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- http-proxy-middleware@2.0.7(@types/express@4.17.18):
+ http-proxy-middleware@2.0.9(@types/express@4.17.18):
dependencies:
'@types/http-proxy': 1.17.16
http-proxy: 1.18.1
@@ -21655,9 +21922,9 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
- icss-utils@5.1.0(postcss@8.5.3):
+ icss-utils@5.1.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
idb@7.1.1: {}
@@ -23825,7 +24092,7 @@ snapshots:
mini-css-extract-plugin@2.9.2(webpack@5.98.0):
dependencies:
schema-utils: 4.3.0
- tapable: 2.2.1
+ tapable: 2.3.3
webpack: 5.98.0
minimalistic-assert@1.0.1: {}
@@ -23836,6 +24103,10 @@ snapshots:
dependencies:
brace-expansion: 1.1.11
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.11
+
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
@@ -23946,6 +24217,8 @@ snapshots:
nan@2.20.0:
optional: true
+ nanoid@3.3.12: {}
+
nanoid@3.3.4: {}
nanoid@3.3.8: {}
@@ -23986,6 +24259,8 @@ snapshots:
negotiator@0.6.3: {}
+ negotiator@0.6.4: {}
+
neo-async@2.6.2: {}
netmask@2.1.1: {}
@@ -24099,7 +24374,7 @@ snapshots:
node-dir@0.1.17:
dependencies:
- minimatch: 3.1.2
+ minimatch: 3.1.5
node-domexception@1.0.0: {}
@@ -24189,6 +24464,8 @@ snapshots:
node-releases@2.0.19: {}
+ node-releases@2.0.44: {}
+
node-stream-zip@1.15.0: {}
nodemailer@6.10.1: {}
@@ -24211,8 +24488,6 @@ snapshots:
normalize-path@3.0.0: {}
- normalize-range@0.1.2: {}
-
normalize-url@8.0.1: {}
npm-run-path@2.0.2:
@@ -24372,6 +24647,8 @@ snapshots:
on-headers@1.0.2: {}
+ on-headers@1.1.0: {}
+
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -24567,7 +24844,7 @@ snapshots:
param-case@3.0.4:
dependencies:
dot-case: 3.0.4
- tslib: 2.7.0
+ tslib: 2.8.1
parent-module@1.0.1:
dependencies:
@@ -24631,7 +24908,7 @@ snapshots:
pascal-case@3.1.2:
dependencies:
no-case: 3.0.4
- tslib: 2.7.0
+ tslib: 2.8.1
pascalcase@0.1.1: {}
@@ -24832,399 +25109,407 @@ snapshots:
possible-typed-array-names@1.0.0: {}
- postcss-attribute-case-insensitive@7.0.1(postcss@8.5.3):
+ postcss-attribute-case-insensitive@7.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-calc@9.0.1(postcss@8.5.3):
+ postcss-calc@9.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 6.1.2
postcss-value-parser: 4.2.0
- postcss-clamp@4.1.0(postcss@8.5.3):
+ postcss-clamp@4.1.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-color-functional-notation@7.0.8(postcss@8.5.3):
+ postcss-color-functional-notation@7.0.12(postcss@8.5.14):
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- postcss-color-hex-alpha@10.0.0(postcss@8.5.3):
+ postcss-color-hex-alpha@10.0.0(postcss@8.5.14):
dependencies:
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-color-rebeccapurple@10.0.0(postcss@8.5.3):
+ postcss-color-rebeccapurple@10.0.0(postcss@8.5.14):
dependencies:
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-colormin@6.1.0(postcss@8.5.3):
+ postcss-colormin@6.1.0(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
+ browserslist: 4.28.2
caniuse-api: 3.0.0
colord: 2.9.3
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-convert-values@6.1.0(postcss@8.5.3):
+ postcss-convert-values@6.1.0(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
- postcss: 8.5.3
+ browserslist: 4.28.2
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-custom-media@11.0.5(postcss@8.5.3):
+ postcss-custom-media@11.0.6(postcss@8.5.14):
dependencies:
- '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- postcss: 8.5.3
+ '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ postcss: 8.5.14
- postcss-custom-properties@14.0.4(postcss@8.5.3):
+ postcss-custom-properties@14.0.6(postcss@8.5.14):
dependencies:
- '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-custom-selectors@8.0.4(postcss@8.5.3):
+ postcss-custom-selectors@8.0.5(postcss@8.5.14):
dependencies:
- '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- postcss: 8.5.3
+ '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-dir-pseudo-class@9.0.1(postcss@8.5.3):
+ postcss-dir-pseudo-class@9.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-discard-comments@6.0.2(postcss@8.5.3):
+ postcss-discard-comments@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-discard-duplicates@6.0.3(postcss@8.5.3):
+ postcss-discard-duplicates@6.0.3(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-discard-empty@6.0.3(postcss@8.5.3):
+ postcss-discard-empty@6.0.3(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-discard-overridden@6.0.2(postcss@8.5.3):
+ postcss-discard-overridden@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-discard-unused@6.0.5(postcss@8.5.3):
+ postcss-discard-unused@6.0.5(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 6.1.2
- postcss-double-position-gradients@6.0.0(postcss@8.5.3):
+ postcss-double-position-gradients@6.0.4(postcss@8.5.14):
dependencies:
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-focus-visible@10.0.1(postcss@8.5.3):
+ postcss-focus-visible@10.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-focus-within@9.0.1(postcss@8.5.3):
+ postcss-focus-within@9.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-font-variant@5.0.0(postcss@8.5.3):
+ postcss-font-variant@5.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-gap-properties@6.0.0(postcss@8.5.3):
+ postcss-gap-properties@6.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-image-set-function@7.0.0(postcss@8.5.3):
+ postcss-image-set-function@7.0.0(postcss@8.5.14):
dependencies:
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-lab-function@7.0.8(postcss@8.5.3):
+ postcss-lab-function@7.0.12(postcss@8.5.14):
dependencies:
- '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
- '@csstools/css-tokenizer': 3.0.3
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/utilities': 2.0.0(postcss@8.5.3)
- postcss: 8.5.3
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/utilities': 2.0.0(postcss@8.5.14)
+ postcss: 8.5.14
- postcss-loader@7.3.4(postcss@8.5.3)(typescript@5.6.3)(webpack@5.98.0):
+ postcss-loader@7.3.4(postcss@8.5.14)(typescript@5.6.3)(webpack@5.98.0):
dependencies:
cosmiconfig: 8.3.6(typescript@5.6.3)
jiti: 1.21.7
- postcss: 8.5.3
+ postcss: 8.5.14
semver: 7.6.3
webpack: 5.98.0
transitivePeerDependencies:
- typescript
- postcss-logical@8.1.0(postcss@8.5.3):
+ postcss-logical@8.1.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-merge-idents@6.0.3(postcss@8.5.3):
+ postcss-merge-idents@6.0.3(postcss@8.5.14):
dependencies:
- cssnano-utils: 4.0.2(postcss@8.5.3)
- postcss: 8.5.3
+ cssnano-utils: 4.0.2(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-merge-longhand@6.0.5(postcss@8.5.3):
+ postcss-merge-longhand@6.0.5(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- stylehacks: 6.1.1(postcss@8.5.3)
+ stylehacks: 6.1.1(postcss@8.5.14)
- postcss-merge-rules@6.1.1(postcss@8.5.3):
+ postcss-merge-rules@6.1.1(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
+ browserslist: 4.28.2
caniuse-api: 3.0.0
- cssnano-utils: 4.0.2(postcss@8.5.3)
- postcss: 8.5.3
+ cssnano-utils: 4.0.2(postcss@8.5.14)
+ postcss: 8.5.14
postcss-selector-parser: 6.1.2
- postcss-minify-font-values@6.1.0(postcss@8.5.3):
+ postcss-minify-font-values@6.1.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-minify-gradients@6.0.3(postcss@8.5.3):
+ postcss-minify-gradients@6.0.3(postcss@8.5.14):
dependencies:
colord: 2.9.3
- cssnano-utils: 4.0.2(postcss@8.5.3)
- postcss: 8.5.3
+ cssnano-utils: 4.0.2(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-minify-params@6.1.0(postcss@8.5.3):
+ postcss-minify-params@6.1.0(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
- cssnano-utils: 4.0.2(postcss@8.5.3)
- postcss: 8.5.3
+ browserslist: 4.28.2
+ cssnano-utils: 4.0.2(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-minify-selectors@6.0.4(postcss@8.5.3):
+ postcss-minify-selectors@6.0.4(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 6.1.2
- postcss-modules-extract-imports@3.1.0(postcss@8.5.3):
+ postcss-modules-extract-imports@3.1.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-modules-local-by-default@4.2.0(postcss@8.5.3):
+ postcss-modules-local-by-default@4.2.0(postcss@8.5.14):
dependencies:
- icss-utils: 5.1.0(postcss@8.5.3)
- postcss: 8.5.3
+ icss-utils: 5.1.0(postcss@8.5.14)
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
postcss-value-parser: 4.2.0
- postcss-modules-scope@3.2.1(postcss@8.5.3):
+ postcss-modules-scope@3.2.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-modules-values@4.0.0(postcss@8.5.3):
+ postcss-modules-values@4.0.0(postcss@8.5.14):
dependencies:
- icss-utils: 5.1.0(postcss@8.5.3)
- postcss: 8.5.3
+ icss-utils: 5.1.0(postcss@8.5.14)
+ postcss: 8.5.14
- postcss-nesting@13.0.1(postcss@8.5.3):
+ postcss-nesting@13.0.2(postcss@8.5.14):
dependencies:
- '@csstools/selector-resolve-nested': 3.0.0(postcss-selector-parser@7.1.0)
+ '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.0)
'@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0)
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-normalize-charset@6.0.2(postcss@8.5.3):
+ postcss-normalize-charset@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-normalize-display-values@6.0.2(postcss@8.5.3):
+ postcss-normalize-display-values@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-positions@6.0.2(postcss@8.5.3):
+ postcss-normalize-positions@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-repeat-style@6.0.2(postcss@8.5.3):
+ postcss-normalize-repeat-style@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-string@6.0.2(postcss@8.5.3):
+ postcss-normalize-string@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-timing-functions@6.0.2(postcss@8.5.3):
+ postcss-normalize-timing-functions@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-unicode@6.1.0(postcss@8.5.3):
+ postcss-normalize-unicode@6.1.0(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
- postcss: 8.5.3
+ browserslist: 4.28.2
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-url@6.0.2(postcss@8.5.3):
+ postcss-normalize-url@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-normalize-whitespace@6.0.2(postcss@8.5.3):
+ postcss-normalize-whitespace@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-opacity-percentage@3.0.0(postcss@8.5.3):
+ postcss-opacity-percentage@3.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-ordered-values@6.0.2(postcss@8.5.3):
+ postcss-ordered-values@6.0.2(postcss@8.5.14):
dependencies:
- cssnano-utils: 4.0.2(postcss@8.5.3)
- postcss: 8.5.3
+ cssnano-utils: 4.0.2(postcss@8.5.14)
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-overflow-shorthand@6.0.0(postcss@8.5.3):
+ postcss-overflow-shorthand@6.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-page-break@3.0.4(postcss@8.5.3):
+ postcss-page-break@3.0.4(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-place@10.0.0(postcss@8.5.3):
+ postcss-place@10.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-preset-env@10.1.5(postcss@8.5.3):
- dependencies:
- '@csstools/postcss-cascade-layers': 5.0.1(postcss@8.5.3)
- '@csstools/postcss-color-function': 4.0.8(postcss@8.5.3)
- '@csstools/postcss-color-mix-function': 3.0.8(postcss@8.5.3)
- '@csstools/postcss-content-alt-text': 2.0.4(postcss@8.5.3)
- '@csstools/postcss-exponential-functions': 2.0.7(postcss@8.5.3)
- '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.3)
- '@csstools/postcss-gamut-mapping': 2.0.8(postcss@8.5.3)
- '@csstools/postcss-gradients-interpolation-method': 5.0.8(postcss@8.5.3)
- '@csstools/postcss-hwb-function': 4.0.8(postcss@8.5.3)
- '@csstools/postcss-ic-unit': 4.0.0(postcss@8.5.3)
- '@csstools/postcss-initial': 2.0.1(postcss@8.5.3)
- '@csstools/postcss-is-pseudo-class': 5.0.1(postcss@8.5.3)
- '@csstools/postcss-light-dark-function': 2.0.7(postcss@8.5.3)
- '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.3)
- '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.3)
- '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.3)
- '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.3)
- '@csstools/postcss-logical-viewport-units': 3.0.3(postcss@8.5.3)
- '@csstools/postcss-media-minmax': 2.0.7(postcss@8.5.3)
- '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.4(postcss@8.5.3)
- '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.3)
- '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.3)
- '@csstools/postcss-oklab-function': 4.0.8(postcss@8.5.3)
- '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3)
- '@csstools/postcss-random-function': 1.0.3(postcss@8.5.3)
- '@csstools/postcss-relative-color-syntax': 3.0.8(postcss@8.5.3)
- '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.3)
- '@csstools/postcss-sign-functions': 1.1.2(postcss@8.5.3)
- '@csstools/postcss-stepped-value-functions': 4.0.7(postcss@8.5.3)
- '@csstools/postcss-text-decoration-shorthand': 4.0.2(postcss@8.5.3)
- '@csstools/postcss-trigonometric-functions': 4.0.7(postcss@8.5.3)
- '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.3)
- autoprefixer: 10.4.20(postcss@8.5.3)
- browserslist: 4.24.4
- css-blank-pseudo: 7.0.1(postcss@8.5.3)
- css-has-pseudo: 7.0.2(postcss@8.5.3)
- css-prefers-color-scheme: 10.0.0(postcss@8.5.3)
- cssdb: 8.2.3
- postcss: 8.5.3
- postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.3)
- postcss-clamp: 4.1.0(postcss@8.5.3)
- postcss-color-functional-notation: 7.0.8(postcss@8.5.3)
- postcss-color-hex-alpha: 10.0.0(postcss@8.5.3)
- postcss-color-rebeccapurple: 10.0.0(postcss@8.5.3)
- postcss-custom-media: 11.0.5(postcss@8.5.3)
- postcss-custom-properties: 14.0.4(postcss@8.5.3)
- postcss-custom-selectors: 8.0.4(postcss@8.5.3)
- postcss-dir-pseudo-class: 9.0.1(postcss@8.5.3)
- postcss-double-position-gradients: 6.0.0(postcss@8.5.3)
- postcss-focus-visible: 10.0.1(postcss@8.5.3)
- postcss-focus-within: 9.0.1(postcss@8.5.3)
- postcss-font-variant: 5.0.0(postcss@8.5.3)
- postcss-gap-properties: 6.0.0(postcss@8.5.3)
- postcss-image-set-function: 7.0.0(postcss@8.5.3)
- postcss-lab-function: 7.0.8(postcss@8.5.3)
- postcss-logical: 8.1.0(postcss@8.5.3)
- postcss-nesting: 13.0.1(postcss@8.5.3)
- postcss-opacity-percentage: 3.0.0(postcss@8.5.3)
- postcss-overflow-shorthand: 6.0.0(postcss@8.5.3)
- postcss-page-break: 3.0.4(postcss@8.5.3)
- postcss-place: 10.0.0(postcss@8.5.3)
- postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.3)
- postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.3)
- postcss-selector-not: 8.0.1(postcss@8.5.3)
-
- postcss-pseudo-class-any-link@10.0.1(postcss@8.5.3):
- dependencies:
- postcss: 8.5.3
+ postcss-preset-env@10.6.1(postcss@8.5.14):
+ dependencies:
+ '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.14)
+ '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.14)
+ '@csstools/postcss-color-function': 4.0.12(postcss@8.5.14)
+ '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.14)
+ '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.14)
+ '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.14)
+ '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.14)
+ '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.14)
+ '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.14)
+ '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.14)
+ '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.14)
+ '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.14)
+ '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.14)
+ '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.14)
+ '@csstools/postcss-initial': 2.0.1(postcss@8.5.14)
+ '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.14)
+ '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.14)
+ '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.14)
+ '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.14)
+ '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.14)
+ '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.14)
+ '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.14)
+ '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.14)
+ '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.14)
+ '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.14)
+ '@csstools/postcss-normalize-display-values': 4.0.1(postcss@8.5.14)
+ '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.14)
+ '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.14)
+ '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.14)
+ '@csstools/postcss-property-rule-prelude-list': 1.0.0(postcss@8.5.14)
+ '@csstools/postcss-random-function': 2.0.1(postcss@8.5.14)
+ '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.14)
+ '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.14)
+ '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.14)
+ '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.14)
+ '@csstools/postcss-syntax-descriptor-syntax-production': 1.0.1(postcss@8.5.14)
+ '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.14)
+ '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.14)
+ '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.14)
+ '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.14)
+ autoprefixer: 10.5.0(postcss@8.5.14)
+ browserslist: 4.28.2
+ css-blank-pseudo: 7.0.1(postcss@8.5.14)
+ css-has-pseudo: 7.0.3(postcss@8.5.14)
+ css-prefers-color-scheme: 10.0.0(postcss@8.5.14)
+ cssdb: 8.9.0
+ postcss: 8.5.14
+ postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.14)
+ postcss-clamp: 4.1.0(postcss@8.5.14)
+ postcss-color-functional-notation: 7.0.12(postcss@8.5.14)
+ postcss-color-hex-alpha: 10.0.0(postcss@8.5.14)
+ postcss-color-rebeccapurple: 10.0.0(postcss@8.5.14)
+ postcss-custom-media: 11.0.6(postcss@8.5.14)
+ postcss-custom-properties: 14.0.6(postcss@8.5.14)
+ postcss-custom-selectors: 8.0.5(postcss@8.5.14)
+ postcss-dir-pseudo-class: 9.0.1(postcss@8.5.14)
+ postcss-double-position-gradients: 6.0.4(postcss@8.5.14)
+ postcss-focus-visible: 10.0.1(postcss@8.5.14)
+ postcss-focus-within: 9.0.1(postcss@8.5.14)
+ postcss-font-variant: 5.0.0(postcss@8.5.14)
+ postcss-gap-properties: 6.0.0(postcss@8.5.14)
+ postcss-image-set-function: 7.0.0(postcss@8.5.14)
+ postcss-lab-function: 7.0.12(postcss@8.5.14)
+ postcss-logical: 8.1.0(postcss@8.5.14)
+ postcss-nesting: 13.0.2(postcss@8.5.14)
+ postcss-opacity-percentage: 3.0.0(postcss@8.5.14)
+ postcss-overflow-shorthand: 6.0.0(postcss@8.5.14)
+ postcss-page-break: 3.0.4(postcss@8.5.14)
+ postcss-place: 10.0.0(postcss@8.5.14)
+ postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.14)
+ postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.14)
+ postcss-selector-not: 8.0.1(postcss@8.5.14)
+
+ postcss-pseudo-class-any-link@10.0.1(postcss@8.5.14):
+ dependencies:
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
- postcss-reduce-idents@6.0.3(postcss@8.5.3):
+ postcss-reduce-idents@6.0.3(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-reduce-initial@6.1.0(postcss@8.5.3):
+ postcss-reduce-initial@6.1.0(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
+ browserslist: 4.28.2
caniuse-api: 3.0.0
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-reduce-transforms@6.0.2(postcss@8.5.3):
+ postcss-reduce-transforms@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-replace-overflow-wrap@4.0.0(postcss@8.5.3):
+ postcss-replace-overflow-wrap@4.0.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
- postcss-selector-not@8.0.1(postcss@8.5.3):
+ postcss-selector-not@8.0.1(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 7.1.0
postcss-selector-parser@6.1.2:
@@ -25237,27 +25522,27 @@ snapshots:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss-sort-media-queries@5.2.0(postcss@8.5.3):
+ postcss-sort-media-queries@5.2.0(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
sort-css-media-queries: 2.2.0
- postcss-svgo@6.0.3(postcss@8.5.3):
+ postcss-svgo@6.0.3(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-value-parser: 4.2.0
svgo: 3.3.2
- postcss-unique-selectors@6.0.4(postcss@8.5.3):
+ postcss-unique-selectors@6.0.4(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss-selector-parser: 6.1.2
postcss-value-parser@4.2.0: {}
- postcss-zindex@6.0.2(postcss@8.5.3):
+ postcss-zindex@6.0.2(postcss@8.5.14):
dependencies:
- postcss: 8.5.3
+ postcss: 8.5.14
postcss@8.4.14:
dependencies:
@@ -25265,6 +25550,12 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
+ postcss@8.5.14:
+ dependencies:
+ nanoid: 3.3.12
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
postcss@8.5.3:
dependencies:
nanoid: 3.3.8
@@ -25434,6 +25725,10 @@ snapshots:
dependencies:
side-channel: 1.1.0
+ qs@6.15.2:
+ dependencies:
+ side-channel: 1.1.0
+
qs@6.5.3: {}
qs@6.7.0: {}
@@ -25479,6 +25774,13 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
+ raw-body@2.5.3:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+
rc-animate@2.11.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2):
dependencies:
babel-runtime: 6.26.0
@@ -25884,7 +26186,7 @@ snapshots:
dependencies:
'@babel/code-frame': 7.26.2
address: 1.2.2
- browserslist: 4.23.3
+ browserslist: 4.28.2
chalk: 4.1.2
cross-spawn: 7.0.6
detect-port-alt: 1.1.6
@@ -25983,7 +26285,7 @@ snapshots:
react-lifecycles-compat@3.0.4: {}
- react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.98.0):
+ react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.98.0):
dependencies:
'@babel/runtime': 7.26.0
react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)'
@@ -26232,7 +26534,7 @@ snapshots:
ast-types: 0.15.2
esprima: 4.0.1
source-map: 0.6.1
- tslib: 2.7.0
+ tslib: 2.8.1
rechoir@0.6.2:
dependencies:
@@ -26270,7 +26572,7 @@ snapshots:
recursive-readdir@2.2.3:
dependencies:
- minimatch: 3.1.2
+ minimatch: 3.1.5
redent@3.0.0:
dependencies:
@@ -26820,12 +27122,12 @@ snapshots:
dependencies:
randombytes: 2.1.0
- serve-handler@6.1.6:
+ serve-handler@6.1.7:
dependencies:
bytes: 3.0.0
content-disposition: 0.5.2
mime-types: 2.1.18
- minimatch: 3.1.2
+ minimatch: 3.1.5
path-is-inside: 1.0.2
path-to-regexp: 3.3.0
range-parser: 1.2.0
@@ -27224,6 +27526,8 @@ snapshots:
statuses@2.0.1: {}
+ statuses@2.0.2: {}
+
std-env@3.8.1: {}
stdin-discarder@0.2.2: {}
@@ -27431,10 +27735,10 @@ snapshots:
optionalDependencies:
'@babel/core': 7.26.9
- stylehacks@6.1.1(postcss@8.5.3):
+ stylehacks@6.1.1(postcss@8.5.14):
dependencies:
- browserslist: 4.23.3
- postcss: 8.5.3
+ browserslist: 4.28.2
+ postcss: 8.5.14
postcss-selector-parser: 6.1.2
stylis@4.3.4: {}
@@ -27800,6 +28104,8 @@ snapshots:
tslib@2.7.0: {}
+ tslib@2.8.1: {}
+
tslint@5.20.1(typescript@4.1.6):
dependencies:
'@babel/code-frame': 7.26.2
@@ -28067,6 +28373,12 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
+ update-browserslist-db@1.2.3(browserslist@4.28.2):
+ dependencies:
+ browserslist: 4.28.2
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
update-notifier@6.0.2:
dependencies:
boxen: 7.1.1
@@ -28276,7 +28588,7 @@ snapshots:
webpack-bundle-analyzer@4.10.2:
dependencies:
'@discoveryjs/json-ext': 0.5.7
- acorn: 8.12.1
+ acorn: 8.15.0
acorn-walk: 8.3.4
commander: 7.2.0
debounce: 1.2.1
@@ -28313,13 +28625,13 @@ snapshots:
bonjour-service: 1.3.0
chokidar: 3.6.0
colorette: 2.0.20
- compression: 1.7.4
+ compression: 1.8.1
connect-history-api-fallback: 2.0.0
default-gateway: 6.0.3
- express: 4.21.2
+ express: 4.22.2
graceful-fs: 4.2.11
html-entities: 2.5.2
- http-proxy-middleware: 2.0.7(@types/express@4.17.18)
+ http-proxy-middleware: 2.0.9(@types/express@4.17.18)
ipaddr.js: 2.2.0
launch-editor: 2.10.0
open: 8.4.2