Skip to content

Commit

Permalink
Support running multiple handlers side-by-side in a custom server (#2882
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Janpot authored Nov 9, 2023
1 parent 5a0713e commit 4e68923
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 88 deletions.
11 changes: 9 additions & 2 deletions packages/toolpad-app/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ export interface RunOptions {
port?: number;
dev?: boolean;
base: string;
create: boolean;
}

async function runCommand(
cmd: 'dev' | 'start',
{ dir, dev: toolpadDevMode, ...args }: Omit<RunOptions, 'cmd'>,
{ dir, dev: toolpadDevMode, create: createIfNotExists, ...args }: Omit<RunOptions, 'cmd'>,
) {
const projectDir = path.resolve(process.cwd(), dir);

Expand All @@ -24,6 +25,7 @@ async function runCommand(
dir: projectDir,
dev: cmd !== 'start',
toolpadDevMode,
createIfNotExists,
});

process.once('SIGINT', () => {
Expand Down Expand Up @@ -85,13 +87,18 @@ export default async function cli(argv: string[]) {
dir: {
type: 'string',
describe: 'Directory of the Toolpad application',
default: '.',
default: './toolpad',
},
base: {
type: 'string',
describe: 'Public base path of the Toolpad application',
default: '/prod',
},
create: {
type: 'boolean',
describe: "Create the application directory if it doesn't exist",
default: false,
},
} as const;

const sharedRunOptions = {
Expand Down
18 changes: 9 additions & 9 deletions packages/toolpad-app/src/server/EnvManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ interface IToolpadProject {
invalidateQueries(): void;
}

/**
* Get file path for the env file.
*/
function getEnvFilePath() {
return path.resolve(process.cwd(), '.env');
}

/**
* Handles loading env files and watches for updates.
*/
Expand Down Expand Up @@ -54,7 +61,7 @@ export default class EnvManager {
}

private loadEnvFile() {
const envFilePath = this.getEnvFilePath();
const envFilePath = getEnvFilePath();
this.resetEnv();
const { parsed = {} } = dotenv.config({ path: envFilePath, override: true });
this.values = parsed;
Expand All @@ -67,19 +74,12 @@ export default class EnvManager {
);
}

/**
* Get file path for the env file.
*/
getEnvFilePath() {
return path.resolve(this.project.getRoot(), '.env');
}

private initWatcher() {
if (!this.project.options.dev) {
return;
}

this.watcher = chokidar.watch([this.getEnvFilePath()], {
this.watcher = chokidar.watch([getEnvFilePath()], {
usePolling: true,
ignoreInitial: true,
});
Expand Down
5 changes: 2 additions & 3 deletions packages/toolpad-app/src/server/FunctionsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ interface IToolpadProject {
options: ToolpadProjectOptions;
events: Emitter<ProjectEvents>;
getRoot(): string;
getToolpadFolder(): string;
getOutputFolder(): string;
envManager: EnvManager;
invalidateQueries(): void;
Expand All @@ -127,7 +126,7 @@ export default class FunctionsManager {
}

private getResourcesFolder(): string {
return path.join(this.project.getToolpadFolder(), './resources');
return path.join(this.project.getRoot(), './resources');
}

private getFunctionsFile(): string {
Expand All @@ -144,7 +143,7 @@ export default class FunctionsManager {
}

private async migrateLegacy() {
const legacyQueriesFile = path.resolve(this.project.getToolpadFolder(), 'queries.ts');
const legacyQueriesFile = path.resolve(this.project.getRoot(), 'queries.ts');
if (await fileExists(legacyQueriesFile)) {
const functionsFile = this.getFunctionsFile();
await fs.mkdir(path.dirname(functionsFile), { recursive: true });
Expand Down
12 changes: 9 additions & 3 deletions packages/toolpad-app/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export interface ToolpadHandlerConfig {

export async function createHandler({
dev = false,
dir = '.',
dir = './toolpad',
base = '/prod',
externalUrl = 'http://localhost:3000',
}: ToolpadHandlerConfig): Promise<AppHandler> {
Expand Down Expand Up @@ -425,6 +425,7 @@ export interface RunAppOptions {
dir?: string;
base?: string;
toolpadDevMode?: boolean;
createIfNotExists?: boolean;
}

export async function runApp({
Expand All @@ -433,11 +434,16 @@ export async function runApp({
base = '/prod',
port = 3000,
toolpadDevMode = false,
createIfNotExists,
}: RunAppOptions) {
const projectDir = resolveProjectDir(dir);

if (!(await folderExists(projectDir))) {
console.error(`${chalk.red('error')} - No project found at ${chalk.cyan(`"${projectDir}"`)}`);
if (!(await folderExists(projectDir)) && !createIfNotExists) {
console.error(
`${chalk.red('error')} - No project found at ${chalk.cyan(
`"${projectDir}"`,
)}. Use the --create option to initialize a new Toolpad project in this folder.`,
);
process.exit(1);
}

Expand Down
74 changes: 20 additions & 54 deletions packages/toolpad-app/src/server/localMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
readMaybeDir,
updateYamlFile,
fileExists,
folderExists,
readJsonFile,
} from '@mui/toolpad-utils/fs';
import { z } from 'zod';
Expand Down Expand Up @@ -68,22 +67,16 @@ invariant(
'localMode should be used only in the main thread. Use message passing to get data from the main thread.',
);

function getToolpadFolder(root: string): string {
return path.join(root, './toolpad');
}

function getThemeFile(root: string): string {
return path.join(getToolpadFolder(root), './theme.yml');
return path.join(root, './theme.yml');
}

function getComponentsFolder(root: string): string {
const toolpadFolder = getToolpadFolder(root);
return path.join(toolpadFolder, './components');
return path.join(root, './components');
}

function getPagesFolder(root: string): string {
const toolpadFolder = getToolpadFolder(root);
return path.join(toolpadFolder, './pages');
return path.join(root, './pages');
}

function getPageFolder(root: string, name: string): string {
Expand All @@ -103,21 +96,13 @@ function getComponentFilePath(componentsFolder: string, componentName: string):
}

function getOutputFolder(root: string) {
return path.join(getToolpadFolder(root), '.generated');
return path.join(root, '.generated');
}

function getAppOutputFolder(root: string) {
return path.join(getOutputFolder(root), 'app');
}

async function legacyConfigFileExists(root: string): Promise<boolean> {
const [yamlFileExists, ymlFileExists] = await Promise.all([
fileExists(path.join(root, './toolpad.yaml')),
fileExists(path.join(root, './toolpad.yml')),
]);
return yamlFileExists || ymlFileExists;
}

type ComponentsContent = Record<string, { code: string }>;

export interface ComponentEntry {
Expand Down Expand Up @@ -276,8 +261,7 @@ type BuildInfo = z.infer<typeof buildInfoSchema>;
const DEFAULT_GENERATED_GITIGNORE_FILE_CONTENT = '.generated\n';

async function initGitignore(root: string) {
const projectFolder = getToolpadFolder(root);
const generatedGitignorePath = path.resolve(projectFolder, '.gitignore');
const generatedGitignorePath = path.resolve(root, '.gitignore');
if (!(await fileExists(generatedGitignorePath))) {
// eslint-disable-next-line no-console
console.log(`${chalk.blue('info')} - Initializing .gitignore file`);
Expand Down Expand Up @@ -908,24 +892,11 @@ export async function loadDomFromDisk(root: string): Promise<appDom.AppDom> {
return projectFolderToAppDom(projectFolder);
}

async function migrateLegacyProject(root: string) {
const isLegacyProject = await legacyConfigFileExists(root);

if (isLegacyProject) {
console.error(
`${chalk.red(
'error',
)} - This project was created with a deprecated version of Toolpad, please use @mui/toolpad@0.1.17 to migrate this project`,
);
process.exit(1);
}
}

function getDomFilePatterns(root: string) {
return [
path.resolve(root, './toolpad/pages/*/page.yml'),
path.resolve(root, './toolpad/components'),
path.resolve(root, './toolpad/components/*.*'),
path.resolve(root, './pages/*/page.yml'),
path.resolve(root, './components'),
path.resolve(root, './components/*.*'),
];
}
/**
Expand Down Expand Up @@ -1067,10 +1038,6 @@ class ToolpadProject {
return this.root;
}

getToolpadFolder() {
return getToolpadFolder(this.getRoot());
}

getOutputFolder() {
return getOutputFolder(this.getRoot());
}
Expand Down Expand Up @@ -1260,7 +1227,7 @@ export type { ToolpadProject };

declare global {
// eslint-disable-next-line
var __toolpadProject: ToolpadProject | undefined;
var __toolpadProjects: Set<string> | undefined;
}

export function resolveProjectDir(dir: string) {
Expand All @@ -1273,14 +1240,19 @@ export interface InitProjectOptions extends Partial<ToolpadProjectOptions> {
}

export async function initProject({ dir: dirInput, ...config }: InitProjectOptions) {
// eslint-disable-next-line no-underscore-dangle
invariant(!global.__toolpadProject, 'A project is already running');
const dir = resolveProjectDir(dirInput);

const dir = await resolveProjectDir(dirInput);
invariant(
// eslint-disable-next-line no-underscore-dangle
!global.__toolpadProjects?.has(dir),
`A project is already running for "${dir}"`,
);
// eslint-disable-next-line no-underscore-dangle
global.__toolpadProjects ??= new Set();
// eslint-disable-next-line no-underscore-dangle
global.__toolpadProjects.add(dir);

if (!(await folderExists(dir))) {
throw new Error(`No Toolpad project found at ${chalk.cyan(`"${dir}"`)}`);
}
await initToolpadFolder(dir);

const resolvedConfig: ToolpadProjectOptions = {
dev: false,
Expand All @@ -1289,13 +1261,7 @@ export async function initProject({ dir: dirInput, ...config }: InitProjectOptio
...config,
};

await migrateLegacyProject(dir);

await initToolpadFolder(dir);

const project = new ToolpadProject(dir, resolvedConfig);
// eslint-disable-next-line no-underscore-dangle
globalThis.__toolpadProject = project;

return project;
}
2 changes: 1 addition & 1 deletion packages/toolpad-app/src/server/toolpadAppBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function toolpadVitePlugin({ root, base, getComponents }: ToolpadVitePluginParam
return resolvedComponentsId;
}
if (importer === resolvedRuntimeEntryPointId || importer === resolvedComponentsId) {
const newId = path.resolve(root, 'toolpad', id);
const newId = path.resolve(root, id);
return this.resolve(newId, importer);
}
return null;
Expand Down
19 changes: 19 additions & 0 deletions test/integration/backend-basic/fixture/server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { unstable_createHandler as createHandler } from '@mui/toolpad';
import express from 'express';
import * as path from 'path';

const app = express();

const base = process.env.BASE;
const dir = path.resolve(process.cwd(), './toolpad');

const handler = await createHandler({
dev: process.env.NODE_ENV === 'development',
dir,
base,
});
app.use(base, handler.handler);

app.listen(process.env.PORT, () => {
console.log(`Custom server listening at http://localhost:${process.env.PORT}`);
});
1 change: 1 addition & 0 deletions test/integration/editor/deleteLast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ToolpadEditor } from '../../models/ToolpadEditor';
test.use({
localAppConfig: {
cmd: 'dev',
create: true,
},
});

Expand Down
1 change: 1 addition & 0 deletions test/integration/editor/new.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ToolpadEditor } from '../../models/ToolpadEditor';
test.use({
localAppConfig: {
cmd: 'dev',
create: true,
},
});

Expand Down
Loading

0 comments on commit 4e68923

Please sign in to comment.