First Application POC (#14382)

Quick proof of concept for twenty-apps + twenty-cli, with local
development / hot reload

Let's discuss it!



https://github.com/user-attachments/assets/c6789936-cd5f-4110-a265-863a6ac1af2d
This commit is contained in:
Félix Malfait
2025-09-10 15:12:38 +02:00
committed by GitHub
parent 9a05daa624
commit 30a2164980
79 changed files with 5515 additions and 365 deletions
@@ -0,0 +1,78 @@
import * as fs from 'fs-extra';
import * as path from 'path';
export const findProjectRoot = async (): Promise<string | null> => {
let currentDir = process.cwd();
const maxDepth = 10;
let depth = 0;
while (depth < maxDepth) {
const nxConfig = path.join(currentDir, 'nx.json');
const packageJson = path.join(currentDir, 'package.json');
if (await fs.pathExists(nxConfig)) {
return currentDir;
}
if (await fs.pathExists(packageJson)) {
try {
const pkg = await fs.readJson(packageJson);
if (pkg.workspaces || pkg.name === 'twenty') {
return currentDir;
}
} catch {
// Ignore JSON parse errors
}
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) break;
currentDir = parentDir;
depth++;
}
return null;
};
export const findNearbyApps = async (startDir: string): Promise<string[]> => {
const apps: string[] = [];
try {
const searchPaths = [
startDir,
path.join(startDir, '..'),
path.join(startDir, '../..'),
path.join(startDir, 'packages/twenty-apps'),
path.join(startDir, '../../packages/twenty-apps'),
];
for (const searchPath of searchPaths) {
if (await fs.pathExists(searchPath)) {
const items = await fs.readdir(searchPath, { withFileTypes: true });
for (const item of items) {
if (item.isDirectory()) {
const manifestPath = path.join(
searchPath,
item.name,
'twenty-app.json',
);
if (await fs.pathExists(manifestPath)) {
apps.push(path.join(searchPath, item.name));
}
}
}
}
}
} catch {
// Ignore errors during search
}
return apps.slice(0, 5);
};
export const isValidAppPath = async (appPath: string): Promise<boolean> => {
const manifestPath = path.join(appPath, 'twenty-app.json');
return fs.pathExists(manifestPath);
};