Remove twenty-app.jsonc (#14662)
As title, use package.json instead of twenty-app.jsonc
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
createAgentManifest,
|
||||
createManifest,
|
||||
createBasePackageJson,
|
||||
createReadmeContent,
|
||||
} from '../app-template';
|
||||
|
||||
@@ -10,43 +10,42 @@ jest.mock('crypto', () => ({
|
||||
}));
|
||||
|
||||
describe('app-template', () => {
|
||||
describe('createManifest', () => {
|
||||
it('should create a valid app manifest with correct structure', () => {
|
||||
describe('createBasePackageJson', () => {
|
||||
it('should create a valid app package.json with correct structure', () => {
|
||||
const appName = 'my-test-app';
|
||||
const manifest = createManifest(appName);
|
||||
const basePackageJson = createBasePackageJson(appName);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
expect(basePackageJson).toEqual({
|
||||
$schema:
|
||||
'https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-cli/schemas/app-manifest.schema.json',
|
||||
standardId: 'mocked-uuid-12345',
|
||||
label: 'My Test App',
|
||||
description: 'A Twenty application for my-test-app',
|
||||
version: '1.0.0',
|
||||
// agents will be discovered from the agents/ folder
|
||||
version: '0.0.1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle single word app names', () => {
|
||||
const appName = 'calculator';
|
||||
const manifest = createManifest(appName);
|
||||
const basePackageJson = createBasePackageJson(appName);
|
||||
|
||||
expect(manifest.label).toBe('Calculator');
|
||||
expect(manifest.standardId).toBe('mocked-uuid-12345');
|
||||
expect(basePackageJson.label).toBe('Calculator');
|
||||
expect(basePackageJson.standardId).toBe('mocked-uuid-12345');
|
||||
});
|
||||
|
||||
it('should handle kebab-case app names correctly', () => {
|
||||
const appName = 'user-management-system';
|
||||
const manifest = createManifest(appName);
|
||||
const basePackageJson = createBasePackageJson(appName);
|
||||
|
||||
expect(manifest.label).toBe('User Management System');
|
||||
expect(manifest.standardId).toBe('mocked-uuid-12345');
|
||||
expect(basePackageJson.label).toBe('User Management System');
|
||||
expect(basePackageJson.standardId).toBe('mocked-uuid-12345');
|
||||
});
|
||||
|
||||
it('should generate unique standardIds', () => {
|
||||
const manifest = createManifest('test-app');
|
||||
const basePackageJson = createBasePackageJson('test-app');
|
||||
|
||||
expect(manifest.standardId).toBeDefined();
|
||||
expect(typeof manifest.standardId).toBe('string');
|
||||
expect(basePackageJson.standardId).toBeDefined();
|
||||
expect(typeof basePackageJson.standardId).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -53,12 +53,12 @@ export const findNearbyApps = async (startDir: string): Promise<string[]> => {
|
||||
|
||||
for (const item of items) {
|
||||
if (item.isDirectory()) {
|
||||
const manifestPath = path.join(
|
||||
const packageJsonPath = path.join(
|
||||
searchPath,
|
||||
item.name,
|
||||
'twenty-app.json',
|
||||
'package.json',
|
||||
);
|
||||
if (await fs.pathExists(manifestPath)) {
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
apps.push(path.join(searchPath, item.name));
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,5 @@ export const findNearbyApps = async (startDir: string): Promise<string[]> => {
|
||||
};
|
||||
|
||||
export const isValidAppPath = async (appPath: string): Promise<boolean> => {
|
||||
const manifestPath = path.join(appPath, 'twenty-app.json');
|
||||
return fs.pathExists(manifestPath);
|
||||
return fs.pathExists(path.join(appPath, 'package.json'));
|
||||
};
|
||||
|
||||
@@ -24,31 +24,23 @@ export class AppManifestLoader {
|
||||
}
|
||||
|
||||
async loadManifest(): Promise<AppManifestWithMeta> {
|
||||
const manifestPath = await this.findManifestFile();
|
||||
const rawManifest = await parseJsoncFile(manifestPath);
|
||||
const packageJsonPath = await this.findPackageJsonFile();
|
||||
const rawPackageJson = await parseJsoncFile(packageJsonPath);
|
||||
|
||||
// Validate the raw manifest structure
|
||||
await schemaValidator.validateAppManifest(rawManifest, manifestPath);
|
||||
await schemaValidator.validateAppManifest(rawPackageJson, packageJsonPath);
|
||||
|
||||
return this.discoverAndLoadAgents(rawManifest, manifestPath);
|
||||
return this.discoverAndLoadAgents(rawPackageJson, packageJsonPath);
|
||||
}
|
||||
|
||||
private async findManifestFile(): Promise<string> {
|
||||
// Try JSONC first, then fall back to JSON for backward compatibility
|
||||
const jsoncPath = path.join(this.appPath, 'twenty-app.jsonc');
|
||||
const jsonPath = path.join(this.appPath, 'twenty-app.json');
|
||||
|
||||
if (await fs.pathExists(jsoncPath)) {
|
||||
return jsoncPath;
|
||||
}
|
||||
private async findPackageJsonFile(): Promise<string> {
|
||||
const jsonPath = path.join(this.appPath, 'package.json');
|
||||
|
||||
if (await fs.pathExists(jsonPath)) {
|
||||
return jsonPath;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`No manifest file found. Expected twenty-app.jsonc or twenty-app.json in ${this.appPath}`,
|
||||
);
|
||||
throw new Error(`package.json not found in ${this.appPath}`);
|
||||
}
|
||||
|
||||
private async discoverAndLoadAgents(
|
||||
@@ -79,11 +71,7 @@ export class AppManifestLoader {
|
||||
}
|
||||
|
||||
return {
|
||||
standardId: rawManifest.standardId,
|
||||
label: rawManifest.label,
|
||||
description: rawManifest.description,
|
||||
icon: rawManifest.icon,
|
||||
version: rawManifest.version,
|
||||
...rawManifest,
|
||||
agents,
|
||||
_meta: {
|
||||
agentFiles,
|
||||
@@ -91,59 +79,6 @@ export class AppManifestLoader {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Utility method to split agents from an existing manifest
|
||||
static async splitAgentsFromManifest(
|
||||
appPath: string,
|
||||
options: {
|
||||
agentsDir?: string;
|
||||
preserveOriginal?: boolean;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const loader = new AppManifestLoader(appPath);
|
||||
const manifest = await loader.loadManifest();
|
||||
|
||||
const agentsDir = options.agentsDir || 'agents';
|
||||
const agentsDirPath = path.join(appPath, agentsDir);
|
||||
|
||||
// Create agents directory
|
||||
await fs.ensureDir(agentsDirPath);
|
||||
|
||||
// Extract agents to separate files
|
||||
for (const agent of manifest.agents) {
|
||||
const agentFileName = `${agent.name}.jsonc`;
|
||||
const agentFilePath = path.join(agentsDirPath, agentFileName);
|
||||
|
||||
// Write agent to separate file
|
||||
await fs.writeFile(agentFilePath, JSON.stringify(agent, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
// Update main manifest (remove agents array since they're now discovered)
|
||||
const updatedManifest = {
|
||||
standardId: manifest.standardId,
|
||||
label: manifest.label,
|
||||
description: manifest.description,
|
||||
icon: manifest.icon,
|
||||
version: manifest.version,
|
||||
// No agents array - they will be discovered from the agents/ folder
|
||||
};
|
||||
|
||||
// Write updated manifest as JSONC
|
||||
const newManifestPath = path.join(appPath, 'twenty-app.jsonc');
|
||||
await fs.writeFile(
|
||||
newManifestPath,
|
||||
JSON.stringify(updatedManifest, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// Optionally remove original JSON file
|
||||
if (!options.preserveOriginal) {
|
||||
const oldManifestPath = path.join(appPath, 'twenty-app.json');
|
||||
if (await fs.pathExists(oldManifestPath)) {
|
||||
await fs.remove(oldManifestPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience function for backward compatibility
|
||||
|
||||
@@ -34,7 +34,7 @@ const resolveRelativePath = async (providedPath: string): Promise<string> => {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Cannot find twenty-app.json at any of these locations:
|
||||
throw new Error(`Cannot find package.json at any of these locations:
|
||||
- ${fromCwd}
|
||||
- ${projectRoot ? path.resolve(projectRoot, providedPath) : 'N/A (no project root found)'}
|
||||
|
||||
@@ -60,7 +60,7 @@ const autoDetectAppPath = async (): Promise<string> => {
|
||||
|
||||
const suggestions = await findNearbyApps(process.cwd());
|
||||
let errorMessage =
|
||||
'No twenty-app.json found in current directory or parent directories.';
|
||||
'No package.json found in current directory or parent directories.';
|
||||
|
||||
if (suggestions.length > 0) {
|
||||
errorMessage += '\n\nFound Twenty applications nearby:';
|
||||
@@ -77,14 +77,12 @@ const autoDetectAppPath = async (): Promise<string> => {
|
||||
};
|
||||
|
||||
const validateAppPath = async (appPath: string): Promise<string> => {
|
||||
const jsoncManifestPath = path.join(appPath, 'twenty-app.jsonc');
|
||||
const jsonManifestPath = path.join(appPath, 'twenty-app.json');
|
||||
const hasPackageJson = await fs.pathExists(
|
||||
path.join(appPath, 'package.json'),
|
||||
);
|
||||
|
||||
const hasJsoncManifest = await fs.pathExists(jsoncManifestPath);
|
||||
const hasJsonManifest = await fs.pathExists(jsonManifestPath);
|
||||
|
||||
if (!hasJsoncManifest && !hasJsonManifest) {
|
||||
let errorMessage = `No manifest file found. Expected twenty-app.jsonc or twenty-app.json in: ${appPath}`;
|
||||
if (!hasPackageJson) {
|
||||
let errorMessage = `package.json not found in: ${appPath}`;
|
||||
|
||||
if (await fs.pathExists(appPath)) {
|
||||
try {
|
||||
|
||||
@@ -11,7 +11,7 @@ export type AgentManifestTemplate = AgentManifest & {
|
||||
$schema?: string;
|
||||
};
|
||||
|
||||
export const createManifest = (appName: string): AppManifestTemplate => {
|
||||
export const createBasePackageJson = (appName: string): AppManifestTemplate => {
|
||||
const schemas = SchemaValidator.getSchemaUrls();
|
||||
|
||||
return {
|
||||
@@ -22,8 +22,7 @@ export const createManifest = (appName: string): AppManifestTemplate => {
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' '),
|
||||
description: `A Twenty application for ${appName}`,
|
||||
version: '1.0.0',
|
||||
// agents will be discovered from the agents/ folder
|
||||
version: '0.0.1',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,6 +46,12 @@ export const createAgentManifest = (appName: string): AgentManifestTemplate => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createGitignoreContent = () => {
|
||||
return `node_modules
|
||||
.yarn/install-state.gz
|
||||
`;
|
||||
};
|
||||
|
||||
export const createReadmeContent = (
|
||||
appName: string,
|
||||
appDir: string,
|
||||
|
||||
Reference in New Issue
Block a user