Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
246697fe9e |
@@ -135,7 +135,7 @@ export class CreateAppCommand {
|
||||
message: 'Select which example files to include:',
|
||||
choices: [
|
||||
{
|
||||
name: 'Example object (custom object definition)',
|
||||
name: 'Example object (with view and navigation menu item)',
|
||||
value: 'object',
|
||||
checked: true,
|
||||
},
|
||||
@@ -154,29 +154,18 @@ export class CreateAppCommand {
|
||||
value: 'frontComponent',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
name: 'Example view (saved view for the example object)',
|
||||
value: 'view',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
name: 'Example navigation menu item (sidebar link)',
|
||||
value: 'navigationMenuItem',
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const includeField = selectedExamples.includes('field');
|
||||
const includeView = selectedExamples.includes('view');
|
||||
const includeObject =
|
||||
selectedExamples.includes('object') || includeField || includeView;
|
||||
selectedExamples.includes('object') || includeField;
|
||||
|
||||
if ((includeField || includeView) && !selectedExamples.includes('object')) {
|
||||
if (includeField && !selectedExamples.includes('object')) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Note: Example object auto-included because example field/view depends on it.',
|
||||
'Note: Example object auto-included because example field depends on it.',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -186,9 +175,8 @@ export class CreateAppCommand {
|
||||
includeExampleField: includeField,
|
||||
includeExampleLogicFunction: selectedExamples.includes('logicFunction'),
|
||||
includeExampleFrontComponent: selectedExamples.includes('frontComponent'),
|
||||
includeExampleView: includeView,
|
||||
includeExampleNavigationMenuItem:
|
||||
selectedExamples.includes('navigationMenuItem'),
|
||||
includeExampleView: includeObject,
|
||||
includeExampleNavigationMenuItem: includeObject,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -380,9 +380,12 @@ const createExampleView = async ({
|
||||
const content = `import { defineView } from 'twenty-sdk';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
|
||||
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER =
|
||||
'${universalIdentifier}';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: 'example-view',
|
||||
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
name: 'All example items',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconList',
|
||||
position: 0,
|
||||
@@ -405,18 +408,14 @@ const createExampleNavigationMenuItem = async ({
|
||||
const universalIdentifier = v4();
|
||||
|
||||
const content = `import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
position: 0,
|
||||
// Link to a view:
|
||||
// viewUniversalIdentifier: '...',
|
||||
// Or link to an object:
|
||||
// targetObjectUniversalIdentifier: '...',
|
||||
// Or link to an external URL:
|
||||
// link: 'https://example.com',
|
||||
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { getFrontComponentBaseFile } from '@/cli/utilities/entity/entity-front-component-template';
|
||||
import { getLogicFunctionBaseFile } from '@/cli/utilities/entity/entity-logic-function-template';
|
||||
import { getNavigationMenuItemBaseFile } from '@/cli/utilities/entity/entity-navigation-menu-item-template';
|
||||
import {
|
||||
getNavigationMenuItemBaseFile,
|
||||
getNavigationMenuItemForViewBaseFile,
|
||||
} from '@/cli/utilities/entity/entity-navigation-menu-item-template';
|
||||
import { convertToLabel } from '@/cli/utilities/entity/entity-label';
|
||||
import { getObjectBaseFile } from '@/cli/utilities/entity/entity-object-template';
|
||||
import { getPageLayoutBaseFile } from '@/cli/utilities/entity/entity-page-layout-template';
|
||||
import { getRoleBaseFile } from '@/cli/utilities/entity/entity-role-template';
|
||||
import { getViewBaseFile } from '@/cli/utilities/entity/entity-view-template';
|
||||
import {
|
||||
getViewBaseFile,
|
||||
getViewForObjectBaseFile,
|
||||
} from '@/cli/utilities/entity/entity-view-template';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
@@ -19,6 +25,13 @@ import { getFieldBaseFile } from '@/cli/utilities/entity/entity-field-template';
|
||||
|
||||
const APP_FOLDER = 'src';
|
||||
|
||||
type ObjectMetadata = {
|
||||
constantName: string;
|
||||
labelPlural: string;
|
||||
namePlural: string;
|
||||
nameSingular: string;
|
||||
};
|
||||
|
||||
export class EntityAddCommand {
|
||||
async execute(entityType?: SyncableEntity, path?: string): Promise<void> {
|
||||
try {
|
||||
@@ -32,7 +45,8 @@ export class EntityAddCommand {
|
||||
|
||||
await fs.ensureDir(appPath);
|
||||
|
||||
const { name, file } = await this.getEntityData(entity);
|
||||
const { name, file, objectMetadata } =
|
||||
await this.getEntityData(entity);
|
||||
|
||||
const filePath = join(appPath, this.getFileName(name, entity));
|
||||
|
||||
@@ -49,6 +63,13 @@ export class EntityAddCommand {
|
||||
chalk.green(`✓ Created ${entityName}:`),
|
||||
chalk.cyan(relative(CURRENT_EXECUTION_DIRECTORY, filePath)),
|
||||
);
|
||||
|
||||
if (entity === SyncableEntity.Object && objectMetadata) {
|
||||
await this.promptAndCreateViewAndNavItem({
|
||||
objectMetadata,
|
||||
objectFileName: kebabcase(objectMetadata.nameSingular),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red(`Add new entity failed:`),
|
||||
@@ -58,19 +79,111 @@ export class EntityAddCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async getEntityData(entity: SyncableEntity) {
|
||||
private async promptAndCreateViewAndNavItem({
|
||||
objectMetadata,
|
||||
objectFileName,
|
||||
}: {
|
||||
objectMetadata: ObjectMetadata;
|
||||
objectFileName: string;
|
||||
}): Promise<void> {
|
||||
const { createViewAndNav } = await inquirer.prompt<{
|
||||
createViewAndNav: boolean;
|
||||
}>([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'createViewAndNav',
|
||||
message:
|
||||
'Create a view and navigation menu item for this object? (recommended)',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!createViewAndNav) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectImportPath = `src/objects/${objectFileName}`;
|
||||
const viewName = `all-${kebabcase(objectMetadata.namePlural)}`;
|
||||
|
||||
const { file: viewFile, constantName: viewConstantName } =
|
||||
getViewForObjectBaseFile({
|
||||
objectConstantName: objectMetadata.constantName,
|
||||
objectImportPath,
|
||||
labelPlural: objectMetadata.labelPlural,
|
||||
});
|
||||
|
||||
const viewsFolderPath = join(
|
||||
CURRENT_EXECUTION_DIRECTORY,
|
||||
APP_FOLDER,
|
||||
'views',
|
||||
);
|
||||
|
||||
await fs.ensureDir(viewsFolderPath);
|
||||
|
||||
const viewFilePath = join(viewsFolderPath, `${viewName}.ts`);
|
||||
|
||||
await fs.writeFile(viewFilePath, viewFile);
|
||||
|
||||
console.log(
|
||||
chalk.green('✓ Created views:'),
|
||||
chalk.cyan(relative(CURRENT_EXECUTION_DIRECTORY, viewFilePath)),
|
||||
);
|
||||
|
||||
const viewImportPath = `src/views/${viewName}`;
|
||||
const navItemName = kebabcase(objectMetadata.namePlural);
|
||||
|
||||
const navItemFile = getNavigationMenuItemForViewBaseFile({
|
||||
viewConstantName,
|
||||
viewImportPath,
|
||||
});
|
||||
|
||||
const navItemsFolderPath = join(
|
||||
CURRENT_EXECUTION_DIRECTORY,
|
||||
APP_FOLDER,
|
||||
'navigation-menu-items',
|
||||
);
|
||||
|
||||
await fs.ensureDir(navItemsFolderPath);
|
||||
|
||||
const navItemFilePath = join(
|
||||
navItemsFolderPath,
|
||||
`${navItemName}.ts`,
|
||||
);
|
||||
|
||||
await fs.writeFile(navItemFilePath, navItemFile);
|
||||
|
||||
console.log(
|
||||
chalk.green('✓ Created navigation-menu-items:'),
|
||||
chalk.cyan(relative(CURRENT_EXECUTION_DIRECTORY, navItemFilePath)),
|
||||
);
|
||||
}
|
||||
|
||||
private async getEntityData(entity: SyncableEntity): Promise<{
|
||||
name: string;
|
||||
file: string;
|
||||
objectMetadata?: ObjectMetadata;
|
||||
}> {
|
||||
switch (entity) {
|
||||
case SyncableEntity.Object: {
|
||||
const entityData = await this.getObjectData();
|
||||
|
||||
const name = entityData.nameSingular;
|
||||
|
||||
const file = getObjectBaseFile({
|
||||
const { file, constantName } = getObjectBaseFile({
|
||||
data: entityData,
|
||||
name,
|
||||
});
|
||||
|
||||
return { name, file };
|
||||
return {
|
||||
name,
|
||||
file,
|
||||
objectMetadata: {
|
||||
constantName,
|
||||
labelPlural: entityData.labelPlural,
|
||||
namePlural: entityData.namePlural,
|
||||
nameSingular: entityData.nameSingular,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case SyncableEntity.Field: {
|
||||
|
||||
@@ -26,3 +26,23 @@ export default defineNavigationMenuItem({
|
||||
});
|
||||
`;
|
||||
};
|
||||
|
||||
export const getNavigationMenuItemForViewBaseFile = ({
|
||||
viewConstantName,
|
||||
viewImportPath,
|
||||
}: {
|
||||
viewConstantName: string;
|
||||
viewImportPath: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
return `import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { ${viewConstantName} } from '${viewImportPath}';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
position: 0,
|
||||
viewUniversalIdentifier: ${viewConstantName},
|
||||
});
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const toScreamingSnakeCase = (value: string) =>
|
||||
kebabCase(value).replace(/-/g, '_').toUpperCase();
|
||||
|
||||
export const getObjectBaseFile = ({
|
||||
data,
|
||||
}: {
|
||||
@@ -12,11 +16,15 @@ export const getObjectBaseFile = ({
|
||||
name: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
const constantName = `${toScreamingSnakeCase(data.nameSingular)}_UNIVERSAL_IDENTIFIER`;
|
||||
|
||||
return `import { defineObject } from 'twenty-sdk';
|
||||
const file = `import { defineObject } from 'twenty-sdk';
|
||||
|
||||
export const ${constantName} =
|
||||
'${universalIdentifier}';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
universalIdentifier: ${constantName},
|
||||
nameSingular: '${data.nameSingular}',
|
||||
namePlural: '${data.namePlural}',
|
||||
labelSingular: '${data.labelSingular}',
|
||||
@@ -34,4 +42,6 @@ export default defineObject({
|
||||
],
|
||||
});
|
||||
`;
|
||||
|
||||
return { file, universalIdentifier, constantName };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const toScreamingSnakeCase = (value: string) =>
|
||||
kebabCase(value).replace(/-/g, '_').toUpperCase();
|
||||
|
||||
export const getViewBaseFile = ({
|
||||
name,
|
||||
universalIdentifier = v4(),
|
||||
@@ -37,3 +40,33 @@ export default defineView({
|
||||
});
|
||||
`;
|
||||
};
|
||||
|
||||
export const getViewForObjectBaseFile = ({
|
||||
objectConstantName,
|
||||
objectImportPath,
|
||||
labelPlural,
|
||||
}: {
|
||||
objectConstantName: string;
|
||||
objectImportPath: string;
|
||||
labelPlural: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
const constantName = `ALL_${toScreamingSnakeCase(labelPlural)}_VIEW_UNIVERSAL_IDENTIFIER`;
|
||||
|
||||
const file = `import { defineView } from 'twenty-sdk';
|
||||
import { ${objectConstantName} } from '${objectImportPath}';
|
||||
|
||||
export const ${constantName} =
|
||||
'${universalIdentifier}';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: ${constantName},
|
||||
name: 'All ${labelPlural}',
|
||||
objectUniversalIdentifier: ${objectConstantName},
|
||||
icon: 'IconList',
|
||||
position: 0,
|
||||
});
|
||||
`;
|
||||
|
||||
return { file, universalIdentifier, constantName };
|
||||
};
|
||||
|
||||
@@ -25,11 +25,7 @@ export type {
|
||||
RelationFieldManifest,
|
||||
FieldManifest,
|
||||
} from './fieldManifestType';
|
||||
export type {
|
||||
CommandMenuItemManifest,
|
||||
FrontComponentCommandManifest,
|
||||
FrontComponentManifest,
|
||||
} from './frontComponentManifestType';
|
||||
export type { FrontComponentManifest } from './frontComponentManifestType';
|
||||
export type {
|
||||
LogicFunctionManifest,
|
||||
CronTriggerSettings,
|
||||
|
||||
Reference in New Issue
Block a user