powerplatform-mcp
Version:
PowerPlatform Model Context Protocol server
426 lines (425 loc) • 24.7 kB
JavaScript
import { outputResult } from '../output.js';
export function registerEntityCommands(program, registry) {
program
.command('entity-metadata <entityName>')
.description('Get metadata for a Dataverse entity')
.action(async (entityName, _opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const metadata = await service.getEntityMetadata(entityName);
const displayName = metadata.DisplayName?.UserLocalizedLabel?.Label ?? 'N/A';
outputResult({
fileName: `${entityName}-metadata`,
data: metadata,
summary: [
`Entity: ${metadata.LogicalName} (${displayName})`,
` Schema Name: ${metadata.SchemaName}`,
` Primary Key: ${metadata.PrimaryIdAttribute}`,
` Primary Name: ${metadata.PrimaryNameAttribute}`,
` Ownership: ${metadata.OwnershipType}`,
` Is Custom: ${metadata.IsCustomEntity}`,
` Is Managed: ${metadata.IsManaged}`,
` Collection Name: ${metadata.LogicalCollectionName}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('entity-attributes <entityName>')
.description('Get all attributes/fields for a Dataverse entity')
.action(async (entityName, _opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.getEntityAttributes(entityName);
const attrs = result.value || [];
const keyAttrs = attrs
.slice(0, 10)
.map((a) => a.LogicalName)
.join(', ');
outputResult({
fileName: `${entityName}-attributes`,
data: result,
summary: [
`Entity '${entityName}' has ${attrs.length} attributes.`,
`Key attributes: ${keyAttrs}`,
attrs.length > 10 ? `+ ${attrs.length - 10} more in file` : '',
].filter(Boolean).join('\n'),
}, ctx.environmentName);
});
program
.command('entity-attribute <entityName> <attributeName>')
.description('Get a specific attribute/field for a Dataverse entity')
.action(async (entityName, attributeName, _opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const attribute = await service.getEntityAttribute(entityName, attributeName);
const displayName = attribute.DisplayName?.UserLocalizedLabel?.Label ?? 'N/A';
outputResult({
fileName: `${entityName}-attribute-${attributeName}`,
data: attribute,
summary: [
`Attribute: ${attribute.LogicalName} (${displayName})`,
` Type: ${attribute.AttributeType}`,
` Required: ${attribute.RequiredLevel?.Value ?? 'N/A'}`,
attribute.MaxLength !== undefined ? ` Max Length: ${attribute.MaxLength}` : '',
attribute.Format ? ` Format: ${attribute.Format}` : '',
].filter(Boolean).join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-string-attribute <entityName> <schemaName> <displayName>')
.description('Create a Single Line of Text attribute on a Dataverse entity')
.option('--max-length <n>', 'Maximum text length', '100')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createStringAttribute(entityName, schemaName, displayName, parseInt(opts.maxLength, 10), opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-attribute-${schemaName}`,
data: result,
summary: [
`Created string attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Max Length: ${opts.maxLength}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity <schemaName> <displayName> <displayCollectionName>')
.description('Create a new custom Dataverse entity (table) with a primary name attribute')
.option('--primary-name-schema <name>', 'Schema name for the primary name attribute', 'br_Name')
.option('--primary-name-display <name>', 'Display name for the primary name attribute', 'Name')
.option('--description <desc>', 'Description for the entity')
.option('--ownership <type>', 'Ownership type: UserOwned or OrganizationOwned', 'UserOwned')
.option('--has-activities', 'Enable activities on the entity', false)
.option('--has-notes', 'Enable notes on the entity', false)
.option('--solution <name>', 'Solution unique name to add the entity to')
.action(async (schemaName, displayName, displayCollectionName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createEntity(schemaName, displayName, displayCollectionName, opts.primaryNameSchema, opts.primaryNameDisplay, opts.description, opts.ownership, opts.hasActivities, opts.hasNotes, undefined, opts.solution);
outputResult({
fileName: `create-entity-${schemaName}`,
data: result,
summary: [
`Created entity:`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Collection Name: ${displayCollectionName}`,
` Primary Name: ${opts.primaryNameSchema} (${opts.primaryNameDisplay})`,
` Ownership: ${opts.ownership}`,
` Entity ID: ${result.entityId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-picklist-attribute <entityName> <schemaName> <displayName>')
.description('Create a local Picklist (Choice) attribute on a Dataverse entity')
.option('-o, --option <value:label>', 'Option in value:label format (repeatable)', (val, acc) => { acc.push(val); return acc; }, [])
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
if (opts.option.length === 0) {
console.error('Error: At least one --option value:label is required.');
process.exit(1);
}
const options = opts.option.map(o => {
const colonIdx = o.indexOf(':');
if (colonIdx < 1) {
console.error(`Error: Invalid option format "${o}". Expected value:label (e.g. 100000000:Active).`);
process.exit(1);
}
return { value: parseInt(o.substring(0, colonIdx), 10), label: o.substring(colonIdx + 1) };
});
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createPicklistAttribute(entityName, schemaName, displayName, options, opts.requiredLevel, opts.description, undefined, opts.solution);
const optionList = options.map(o => ` ${o.value} = ${o.label}`).join('\n');
outputResult({
fileName: `${entityName}-create-picklist-${schemaName}`,
data: result,
summary: [
`Created picklist attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Options:\n${optionList}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('delete-entity-attribute <entityName> <attributeName>')
.description('Delete an attribute from a Dataverse entity (irreversible)')
.action(async (entityName, attributeName, _opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
await service.deleteEntityAttribute(entityName, attributeName);
outputResult({
fileName: `${entityName}-delete-attribute-${attributeName}`,
data: { deleted: true, entityName, attributeName },
summary: `Deleted attribute '${attributeName}' from '${entityName}'.`,
}, ctx.environmentName);
});
program
.command('create-entity-money-attribute <entityName> <schemaName> <displayName>')
.description('Create a Money (Currency) attribute on a Dataverse entity')
.option('--precision-source <n>', 'Precision source: 0 = fixed (use --precision), 1 = pricing, 2 = currency', '2')
.option('--precision <n>', 'Display precision when precision-source is 0 (0-4)', '2')
.option('--min <n>', 'Minimum allowed value', '-100000000000')
.option('--max <n>', 'Maximum allowed value', '100000000000')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createMoneyAttribute(entityName, schemaName, displayName, parseInt(opts.precisionSource, 10), parseInt(opts.precision, 10), parseFloat(opts.min), parseFloat(opts.max), opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-money-${schemaName}`,
data: result,
summary: [
`Created money attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Precision Source: ${opts.precisionSource}`,
` Precision: ${opts.precision}`,
` Range: ${opts.min} to ${opts.max}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-decimal-attribute <entityName> <schemaName> <displayName>')
.description('Create a Decimal Number attribute on a Dataverse entity')
.option('--precision <n>', 'Number of digits after the decimal (0-10)', '2')
.option('--min <n>', 'Minimum allowed value', '-100000000000')
.option('--max <n>', 'Maximum allowed value', '100000000000')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createDecimalAttribute(entityName, schemaName, displayName, parseInt(opts.precision, 10), parseFloat(opts.min), parseFloat(opts.max), opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-decimal-${schemaName}`,
data: result,
summary: [
`Created decimal attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Precision: ${opts.precision}`,
` Range: ${opts.min} to ${opts.max}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-datetime-attribute <entityName> <schemaName> <displayName>')
.description('Create a DateTime attribute on a Dataverse entity')
.option('--format <fmt>', 'Format: DateOnly or DateAndTime', 'DateOnly')
.option('--behavior <beh>', 'Behavior: UserLocal, DateOnly, or TimeZoneIndependent', 'UserLocal')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createDateTimeAttribute(entityName, schemaName, displayName, opts.format, opts.behavior, opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-datetime-${schemaName}`,
data: result,
summary: [
`Created datetime attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Format: ${opts.format}`,
` Behavior: ${opts.behavior}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-integer-attribute <entityName> <schemaName> <displayName>')
.description('Create an Integer (Whole Number) attribute on a Dataverse entity')
.option('--min <n>', 'Minimum allowed value', '-2147483648')
.option('--max <n>', 'Maximum allowed value', '2147483647')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createIntegerAttribute(entityName, schemaName, displayName, parseInt(opts.min, 10), parseInt(opts.max, 10), opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-integer-${schemaName}`,
data: result,
summary: [
`Created integer attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Range: ${opts.min} to ${opts.max}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-boolean-attribute <entityName> <schemaName> <displayName>')
.description('Create a Boolean (Yes/No) attribute on a Dataverse entity')
.option('--true-label <label>', 'Label for the true/yes option', 'Yes')
.option('--false-label <label>', 'Label for the false/no option', 'No')
.option('--default-value <val>', 'Default value: true or false', 'false')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createBooleanAttribute(entityName, schemaName, displayName, opts.trueLabel, opts.falseLabel, opts.defaultValue === 'true', opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-boolean-${schemaName}`,
data: result,
summary: [
`Created boolean attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` True Label: ${opts.trueLabel}`,
` False Label: ${opts.falseLabel}`,
` Default: ${opts.defaultValue}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-memo-attribute <entityName> <schemaName> <displayName>')
.description('Create a Memo (Multi-Line Text) attribute on a Dataverse entity')
.option('--max-length <n>', 'Maximum text length', '2000')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the attribute')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createMemoAttribute(entityName, schemaName, displayName, parseInt(opts.maxLength, 10), opts.requiredLevel, opts.description, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-memo-${schemaName}`,
data: result,
summary: [
`Created memo attribute on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Max Length: ${opts.maxLength}`,
` Required Level: ${opts.requiredLevel}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-lookup <referencingEntity> <referencedEntity> <relationshipSchemaName> <lookupSchemaName> <displayName>')
.description('Create a lookup (N:1 relationship) column on a Dataverse entity')
.option('--required-level <level>', 'Required level: None, ApplicationRequired, SystemRequired', 'None')
.option('--description <desc>', 'Description for the lookup column')
.option('--cascade-delete <mode>', 'Cascade delete behavior: NoCascade, RemoveLink, Restrict, Cascade', 'RemoveLink')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (referencingEntity, referencedEntity, relationshipSchemaName, lookupSchemaName, displayName, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createLookupAttribute(referencingEntity, referencedEntity, relationshipSchemaName, lookupSchemaName, displayName, opts.requiredLevel, opts.description, opts.cascadeDelete, undefined, opts.solution);
outputResult({
fileName: `${referencingEntity}-create-lookup-${lookupSchemaName}`,
data: result,
summary: [
`Created lookup on '${referencingEntity}':`,
` Lookup Schema Name: ${lookupSchemaName}`,
` Display Name: ${displayName}`,
` Points to: ${referencedEntity}`,
` Relationship Name: ${relationshipSchemaName}`,
` Required Level: ${opts.requiredLevel}`,
` Cascade Delete: ${opts.cascadeDelete}`,
` Attribute ID: ${result.attributeId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('entity-keys <entityName>')
.description('Get alternate keys defined on a Dataverse entity')
.action(async (entityName, _opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.getEntityKeys(entityName);
const keys = result.value || [];
const keyList = keys
.slice(0, 10)
.map((k) => {
const dn = k.DisplayName?.UserLocalizedLabel?.Label ?? k.SchemaName;
const attrs = k.KeyAttributes?.join(', ') ?? 'N/A';
return `${dn} [${attrs}]`;
})
.join('\n ');
outputResult({
fileName: `${entityName}-keys`,
data: result,
summary: [
`Found ${keys.length} alternate keys on entity '${entityName}':`,
keys.length > 0 ? ` Keys:\n ${keyList}` : '',
].filter(Boolean).join('\n'),
}, ctx.environmentName);
});
program
.command('create-entity-alternate-key <entityName> <schemaName> <displayName>')
.description('Create an alternate key on a Dataverse entity')
.argument('<keyAttributes...>', 'Attribute logical names that make up the key')
.option('--solution <name>', 'Solution unique name to add the component to')
.action(async (entityName, schemaName, displayName, keyAttributes, opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const result = await service.createAlternateKey(entityName, schemaName, displayName, keyAttributes, undefined, opts.solution);
outputResult({
fileName: `${entityName}-create-key-${schemaName}`,
data: result,
summary: [
`Created alternate key on '${entityName}':`,
` Schema Name: ${schemaName}`,
` Display Name: ${displayName}`,
` Key Attributes: ${keyAttributes.join(', ')}`,
` Key ID: ${result.keyId}`,
].join('\n'),
}, ctx.environmentName);
});
program
.command('entity-relationships <entityName>')
.description('Get all relationships for a Dataverse entity')
.action(async (entityName, _opts, command) => {
const ctx = registry.getContext(command.optsWithGlobals().env);
const service = ctx.getEntityService();
const relationships = await service.getEntityRelationships(entityName);
const o2m = relationships.oneToMany?.value || [];
const m2m = relationships.manyToMany?.value || [];
const topRelated = o2m
.slice(0, 5)
.map((r) => `${r.SchemaName} (→${r.ReferencingEntity})`)
.join(', ');
outputResult({
fileName: `${entityName}-relationships`,
data: relationships,
summary: [
`Entity '${entityName}' relationships:`,
` One-to-Many: ${o2m.length}`,
` Many-to-Many: ${m2m.length}`,
o2m.length > 0 ? ` Top 1:N: ${topRelated}${o2m.length > 5 ? ', ...' : ''}` : '',
].filter(Boolean).join('\n'),
}, ctx.environmentName);
});
}