@khester/create-dynamics-app 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-dynamics-app.js +1 -1
- package/dist/index.js +140 -15
- package/dist/index.js.map +1 -1
- package/dist/utils/consultingHelpers.d.ts +13 -0
- package/dist/utils/consultingHelpers.d.ts.map +1 -0
- package/dist/utils/consultingHelpers.js +569 -0
- package/dist/utils/consultingHelpers.js.map +1 -0
- package/dist/utils/copyTemplate.d.ts.map +1 -1
- package/dist/utils/copyTemplate.js.map +1 -1
- package/dist/utils/initGit.d.ts.map +1 -1
- package/dist/utils/initGit.js.map +1 -1
- package/dist/utils/installDependencies.d.ts.map +1 -1
- package/dist/utils/installDependencies.js +3 -2
- package/dist/utils/installDependencies.js.map +1 -1
- package/dist/utils/updatePackageJson.d.ts +1 -1
- package/dist/utils/updatePackageJson.d.ts.map +1 -1
- package/dist/utils/updatePackageJson.js +11 -1
- package/dist/utils/updatePackageJson.js.map +1 -1
- package/package.json +1 -1
- package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +302 -0
- package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +305 -0
- package/templates/dynamics-365-starter/README.md +566 -137
- package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +507 -0
- package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +372 -0
- package/templates/dynamics-365-starter/deployment/README.md +484 -0
- package/templates/dynamics-365-starter/deployment/pipelines/README.md +375 -0
- package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +330 -0
- package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +422 -0
- package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +636 -0
- package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +417 -0
- package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +582 -0
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +486 -0
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +567 -0
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +703 -0
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +671 -0
- package/templates/dynamics-365-starter/docs/ARCHITECTURE_OVERVIEW.md +506 -0
- package/templates/dynamics-365-starter/docs/BEST_PRACTICES.md +723 -0
- package/templates/dynamics-365-starter/docs/MIGRATION_GUIDE.md +447 -0
- package/templates/dynamics-365-starter/docs/team-standards/README.md +273 -0
- package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +577 -0
- package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +359 -0
- package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +700 -0
- package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +736 -0
- package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +727 -0
- package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +758 -0
- package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +878 -0
- package/templates/dynamics-365-starter/package.json +22 -1
- package/templates/dynamics-365-starter/public/index.html +8 -11
- package/templates/dynamics-365-starter/scripts/custom-build.js +255 -0
- package/templates/dynamics-365-starter/src/client-project-template/README.md +234 -0
- package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +114 -0
- package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +186 -0
- package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +667 -0
- package/templates/dynamics-365-starter/src/components/AccountForm.css +71 -0
- package/templates/dynamics-365-starter/src/components/AccountForm.tsx +541 -0
- package/templates/dynamics-365-starter/src/components/AccountManagement.css +86 -0
- package/templates/dynamics-365-starter/src/components/AccountManagement.tsx +370 -0
- package/templates/dynamics-365-starter/src/components/ContactForm.tsx +149 -63
- package/templates/dynamics-365-starter/src/components/ContactManagement.tsx +153 -63
- package/templates/dynamics-365-starter/src/components/Logging/LogDialog.tsx +291 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingContext.tsx +166 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.css +192 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.tsx +177 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingProvider.tsx +3 -0
- package/templates/dynamics-365-starter/src/components/Logging/logger.ts +193 -0
- package/templates/dynamics-365-starter/src/constants/account.ts +410 -0
- package/templates/dynamics-365-starter/src/constants/contact.ts +362 -0
- package/templates/dynamics-365-starter/src/examples/README.md +52 -0
- package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +625 -0
- package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +545 -0
- package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +722 -0
- package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +662 -0
- package/templates/dynamics-365-starter/src/index.tsx +107 -19
- package/templates/dynamics-365-starter/src/models/Account.ts +480 -0
- package/templates/dynamics-365-starter/src/models/BaseEntity.ts +204 -0
- package/templates/dynamics-365-starter/src/models/Contact.ts +580 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +519 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +456 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +406 -0
- package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +578 -0
- package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +629 -0
- package/templates/dynamics-365-starter/src/pcf/ContactControlWrapper.tsx +75 -22
- package/templates/dynamics-365-starter/src/pcf/MultiEntityControlWrapper.tsx +205 -0
- package/templates/dynamics-365-starter/src/providers/DynamicsProvider.tsx +297 -80
- package/templates/dynamics-365-starter/src/services/MockApiService.ts +260 -0
- package/templates/dynamics-365-starter/src/services/ServiceFactory.ts +65 -0
- package/templates/dynamics-365-starter/src/services/XrmApiService.ts +213 -0
- package/templates/dynamics-365-starter/src/styles/index.css +74 -7
- package/templates/dynamics-365-starter/tools/entity-generator/index.js +168 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +124 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +283 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +275 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +204 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +413 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +250 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +410 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/index.js +512 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +675 -0
- package/templates/dynamics-365-starter/tsconfig.json +11 -8
- package/templates/dynamics-365-starter/webpack.config.js +8 -9
- package/templates/power-pages-starter/README.md +7 -1
- package/templates/power-pages-starter/public/index.html +8 -11
- package/templates/power-pages-starter/src/components/ContactForm.tsx +60 -41
- package/templates/power-pages-starter/src/index.tsx +3 -3
- package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +46 -23
- package/templates/power-pages-starter/tsconfig.json +3 -9
- package/templates/power-pages-starter/webpack.config.js +8 -3
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata Sync Tool for Dynamics 365
|
|
5
|
+
* Pulls entity metadata from D365 environments and generates TypeScript interfaces
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { program } = require('commander');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('metadata-sync')
|
|
15
|
+
.description(
|
|
16
|
+
'Sync entity metadata from Dynamics 365 and generate TypeScript interfaces'
|
|
17
|
+
)
|
|
18
|
+
.version('1.0.0');
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command('pull')
|
|
22
|
+
.description('Pull entity metadata from Dynamics 365')
|
|
23
|
+
.requiredOption('-e, --environment <env>', 'Environment (dev, test, prod)')
|
|
24
|
+
.option(
|
|
25
|
+
'-t, --entities <entities>',
|
|
26
|
+
'Comma-separated list of entities to sync'
|
|
27
|
+
)
|
|
28
|
+
.option('--all', 'Sync all custom entities')
|
|
29
|
+
.option(
|
|
30
|
+
'--output <path>',
|
|
31
|
+
'Output directory for metadata files',
|
|
32
|
+
'src/metadata'
|
|
33
|
+
)
|
|
34
|
+
.action(pullMetadata);
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('generate')
|
|
38
|
+
.description('Generate TypeScript interfaces from metadata')
|
|
39
|
+
.option('-i, --input <path>', 'Input metadata directory', 'src/metadata')
|
|
40
|
+
.option(
|
|
41
|
+
'-o, --output <path>',
|
|
42
|
+
'Output directory for TypeScript files',
|
|
43
|
+
'src/types/generated'
|
|
44
|
+
)
|
|
45
|
+
.option('--entity <entity>', 'Generate for specific entity only')
|
|
46
|
+
.action(generateTypes);
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('validate')
|
|
50
|
+
.description('Validate generated code against D365 schema')
|
|
51
|
+
.option('-e, --environment <env>', 'Environment to validate against')
|
|
52
|
+
.option('--fix', 'Automatically fix validation issues')
|
|
53
|
+
.action(validateSchema);
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('sync')
|
|
57
|
+
.description('Pull metadata and generate types in one command')
|
|
58
|
+
.requiredOption('-e, --environment <env>', 'Environment to sync from')
|
|
59
|
+
.option(
|
|
60
|
+
'-t, --entities <entities>',
|
|
61
|
+
'Comma-separated list of entities to sync'
|
|
62
|
+
)
|
|
63
|
+
.option('--all', 'Sync all custom entities')
|
|
64
|
+
.action(syncAll);
|
|
65
|
+
|
|
66
|
+
async function pullMetadata(options) {
|
|
67
|
+
console.log(
|
|
68
|
+
chalk.cyan(
|
|
69
|
+
`\n🔄 Pulling metadata from ${options.environment} environment...\n`
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const config = await loadEnvironmentConfig(options.environment);
|
|
75
|
+
const entities = await getEntitiesToSync(options);
|
|
76
|
+
|
|
77
|
+
console.log(chalk.blue(`📋 Syncing ${entities.length} entities...`));
|
|
78
|
+
|
|
79
|
+
for (const entity of entities) {
|
|
80
|
+
await pullEntityMetadata(entity, config, options.output);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(chalk.green('\n✅ Metadata sync completed successfully!'));
|
|
84
|
+
console.log(chalk.cyan('\n📝 Next steps:'));
|
|
85
|
+
console.log(
|
|
86
|
+
' npm run metadata:generate # Generate TypeScript interfaces'
|
|
87
|
+
);
|
|
88
|
+
console.log(' npm run metadata:validate # Validate against schema');
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(chalk.red('❌ Error pulling metadata:'), error.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function generateTypes(options) {
|
|
96
|
+
console.log(chalk.cyan('\n🔧 Generating TypeScript interfaces...\n'));
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const metadataDir = options.input;
|
|
100
|
+
const outputDir = options.output;
|
|
101
|
+
|
|
102
|
+
await fs.ensureDir(outputDir);
|
|
103
|
+
|
|
104
|
+
if (options.entity) {
|
|
105
|
+
await generateEntityTypes(options.entity, metadataDir, outputDir);
|
|
106
|
+
} else {
|
|
107
|
+
const metadataFiles = await fs.readdir(metadataDir);
|
|
108
|
+
const entityFiles = metadataFiles.filter((file) =>
|
|
109
|
+
file.endsWith('.metadata.json')
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
for (const file of entityFiles) {
|
|
113
|
+
const entityName = file.replace('.metadata.json', '');
|
|
114
|
+
await generateEntityTypes(entityName, metadataDir, outputDir);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Generate index file
|
|
118
|
+
await generateIndexFile(
|
|
119
|
+
entityFiles.map((f) => f.replace('.metadata.json', '')),
|
|
120
|
+
outputDir
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(
|
|
125
|
+
chalk.green('\n✅ TypeScript interfaces generated successfully!')
|
|
126
|
+
);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(chalk.red('❌ Error generating types:'), error.message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function validateSchema(options) {
|
|
134
|
+
console.log(
|
|
135
|
+
chalk.cyan(`\n🔍 Validating schema against ${options.environment}...\n`)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const config = await loadEnvironmentConfig(options.environment);
|
|
140
|
+
const validationResults = await runSchemaValidation(config);
|
|
141
|
+
|
|
142
|
+
if (validationResults.isValid) {
|
|
143
|
+
console.log(chalk.green('✅ Schema validation passed!'));
|
|
144
|
+
} else {
|
|
145
|
+
console.log(chalk.yellow('⚠️ Schema validation issues found:'));
|
|
146
|
+
validationResults.issues.forEach((issue) => {
|
|
147
|
+
console.log(chalk.yellow(` • ${issue}`));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (options.fix) {
|
|
151
|
+
console.log(chalk.cyan('\n🔧 Attempting to fix issues...'));
|
|
152
|
+
await fixValidationIssues(validationResults.issues);
|
|
153
|
+
console.log(chalk.green('✅ Issues fixed automatically'));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(chalk.red('❌ Error validating schema:'), error.message);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function syncAll(options) {
|
|
163
|
+
console.log(chalk.cyan('\n🚀 Starting full metadata sync...\n'));
|
|
164
|
+
|
|
165
|
+
// Pull metadata
|
|
166
|
+
await pullMetadata(options);
|
|
167
|
+
|
|
168
|
+
// Generate types
|
|
169
|
+
await generateTypes({ input: 'src/metadata', output: 'src/types/generated' });
|
|
170
|
+
|
|
171
|
+
// Validate
|
|
172
|
+
await validateSchema({ environment: options.environment });
|
|
173
|
+
|
|
174
|
+
console.log(chalk.green('\n🎉 Complete metadata sync finished!'));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function loadEnvironmentConfig(environment) {
|
|
178
|
+
const configPath = path.join(
|
|
179
|
+
process.cwd(),
|
|
180
|
+
'config',
|
|
181
|
+
'environments',
|
|
182
|
+
`${environment}.json`
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (!(await fs.pathExists(configPath))) {
|
|
186
|
+
throw new Error(`Environment configuration not found: ${configPath}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return await fs.readJson(configPath);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function getEntitiesToSync(options) {
|
|
193
|
+
if (options.all) {
|
|
194
|
+
// Get all custom entities from D365
|
|
195
|
+
return await getAllCustomEntities(options.environment);
|
|
196
|
+
} else if (options.entities) {
|
|
197
|
+
return options.entities.split(',').map((e) => e.trim());
|
|
198
|
+
} else {
|
|
199
|
+
// Default to common entities
|
|
200
|
+
return ['account', 'contact', 'opportunity', 'quote', 'invoice'];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function pullEntityMetadata(entityName, config, outputDir) {
|
|
205
|
+
console.log(chalk.blue(` 📊 Pulling metadata for ${entityName}...`));
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Simulate D365 Web API call to get entity metadata
|
|
209
|
+
const metadata = await fetchEntityMetadata(entityName, config);
|
|
210
|
+
|
|
211
|
+
// Save metadata to file
|
|
212
|
+
const metadataPath = path.join(outputDir, `${entityName}.metadata.json`);
|
|
213
|
+
await fs.ensureDir(outputDir);
|
|
214
|
+
await fs.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
215
|
+
|
|
216
|
+
console.log(chalk.green(` ✓ Saved ${entityName} metadata`));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.log(
|
|
219
|
+
chalk.red(` ✗ Failed to pull ${entityName}: ${error.message}`)
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function fetchEntityMetadata(entityName, config) {
|
|
225
|
+
// Mock implementation - in real usage, this would make actual D365 Web API calls
|
|
226
|
+
const mockMetadata = {
|
|
227
|
+
LogicalName: entityName,
|
|
228
|
+
SchemaName: entityName.charAt(0).toUpperCase() + entityName.slice(1),
|
|
229
|
+
DisplayName: entityName.charAt(0).toUpperCase() + entityName.slice(1),
|
|
230
|
+
PrimaryIdAttribute: `${entityName}id`,
|
|
231
|
+
PrimaryNameAttribute: 'name',
|
|
232
|
+
CollectionSchemaName: `${entityName}s`,
|
|
233
|
+
EntitySetName: `${entityName}s`,
|
|
234
|
+
IsCustomEntity: !['account', 'contact', 'opportunity'].includes(entityName),
|
|
235
|
+
Attributes: [
|
|
236
|
+
{
|
|
237
|
+
LogicalName: `${entityName}id`,
|
|
238
|
+
SchemaName: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)}Id`,
|
|
239
|
+
DisplayName: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} ID`,
|
|
240
|
+
AttributeType: 'Uniqueidentifier',
|
|
241
|
+
IsPrimaryId: true,
|
|
242
|
+
RequiredLevel: 'SystemRequired',
|
|
243
|
+
IsValidForCreate: false,
|
|
244
|
+
IsValidForUpdate: false,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
LogicalName: 'name',
|
|
248
|
+
SchemaName: 'Name',
|
|
249
|
+
DisplayName: 'Name',
|
|
250
|
+
AttributeType: 'String',
|
|
251
|
+
MaxLength: 160,
|
|
252
|
+
RequiredLevel: 'ApplicationRequired',
|
|
253
|
+
IsValidForCreate: true,
|
|
254
|
+
IsValidForUpdate: true,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
LogicalName: 'createdon',
|
|
258
|
+
SchemaName: 'CreatedOn',
|
|
259
|
+
DisplayName: 'Created On',
|
|
260
|
+
AttributeType: 'DateTime',
|
|
261
|
+
RequiredLevel: 'None',
|
|
262
|
+
IsValidForCreate: false,
|
|
263
|
+
IsValidForUpdate: false,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
LogicalName: 'modifiedon',
|
|
267
|
+
SchemaName: 'ModifiedOn',
|
|
268
|
+
DisplayName: 'Modified On',
|
|
269
|
+
AttributeType: 'DateTime',
|
|
270
|
+
RequiredLevel: 'None',
|
|
271
|
+
IsValidForCreate: false,
|
|
272
|
+
IsValidForUpdate: false,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
Relationships: [],
|
|
276
|
+
OptionSets: [],
|
|
277
|
+
Keys: [],
|
|
278
|
+
LastModified: new Date().toISOString(),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Add entity-specific attributes
|
|
282
|
+
if (entityName === 'account') {
|
|
283
|
+
mockMetadata.Attributes.push(
|
|
284
|
+
{
|
|
285
|
+
LogicalName: 'revenue',
|
|
286
|
+
SchemaName: 'Revenue',
|
|
287
|
+
DisplayName: 'Annual Revenue',
|
|
288
|
+
AttributeType: 'Money',
|
|
289
|
+
RequiredLevel: 'None',
|
|
290
|
+
IsValidForCreate: true,
|
|
291
|
+
IsValidForUpdate: true,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
LogicalName: 'industrycode',
|
|
295
|
+
SchemaName: 'IndustryCode',
|
|
296
|
+
DisplayName: 'Industry',
|
|
297
|
+
AttributeType: 'Picklist',
|
|
298
|
+
RequiredLevel: 'None',
|
|
299
|
+
IsValidForCreate: true,
|
|
300
|
+
IsValidForUpdate: true,
|
|
301
|
+
OptionSet: 'account_industrycode',
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return mockMetadata;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function generateEntityTypes(entityName, metadataDir, outputDir) {
|
|
310
|
+
console.log(chalk.blue(` 🔧 Generating types for ${entityName}...`));
|
|
311
|
+
|
|
312
|
+
const metadataPath = path.join(metadataDir, `${entityName}.metadata.json`);
|
|
313
|
+
|
|
314
|
+
if (!(await fs.pathExists(metadataPath))) {
|
|
315
|
+
console.log(
|
|
316
|
+
chalk.yellow(
|
|
317
|
+
` ⚠ Metadata file not found: ${entityName}.metadata.json`
|
|
318
|
+
)
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const metadata = await fs.readJson(metadataPath);
|
|
324
|
+
|
|
325
|
+
// Generate interface
|
|
326
|
+
const interfaceContent = generateInterfaceContent(metadata);
|
|
327
|
+
const interfacePath = path.join(outputDir, `${entityName}.types.ts`);
|
|
328
|
+
await fs.writeFile(interfacePath, interfaceContent);
|
|
329
|
+
|
|
330
|
+
// Generate constants
|
|
331
|
+
const constantsContent = generateConstantsContent(metadata);
|
|
332
|
+
const constantsPath = path.join(outputDir, `${entityName}.constants.ts`);
|
|
333
|
+
await fs.writeFile(constantsPath, constantsContent);
|
|
334
|
+
|
|
335
|
+
console.log(
|
|
336
|
+
chalk.green(` ✓ Generated ${entityName} types and constants`)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function generateInterfaceContent(metadata) {
|
|
341
|
+
const interfaceName = `I${metadata.SchemaName}`;
|
|
342
|
+
const attributes = metadata.Attributes || [];
|
|
343
|
+
|
|
344
|
+
let content = `// Auto-generated interface for ${metadata.DisplayName} entity
|
|
345
|
+
// Generated on: ${new Date().toISOString()}
|
|
346
|
+
// Source: ${metadata.LogicalName} metadata
|
|
347
|
+
|
|
348
|
+
export interface ${interfaceName} {
|
|
349
|
+
`;
|
|
350
|
+
|
|
351
|
+
attributes.forEach((attr) => {
|
|
352
|
+
const tsType = mapAttributeTypeToTypeScript(attr);
|
|
353
|
+
const optional =
|
|
354
|
+
attr.RequiredLevel !== 'ApplicationRequired' &&
|
|
355
|
+
attr.RequiredLevel !== 'SystemRequired';
|
|
356
|
+
const optionalMarker = optional ? '?' : '';
|
|
357
|
+
|
|
358
|
+
content += ` /** ${attr.DisplayName} - Type: ${attr.AttributeType}${attr.MaxLength ? `, MaxLength: ${attr.MaxLength}` : ''} */\n`;
|
|
359
|
+
content += ` ${attr.LogicalName}${optionalMarker}: ${tsType};\n\n`;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
content += `}
|
|
363
|
+
|
|
364
|
+
export interface ${interfaceName}Create extends Omit<${interfaceName}, '${metadata.PrimaryIdAttribute}' | 'createdon' | 'modifiedon'> {}
|
|
365
|
+
|
|
366
|
+
export interface ${interfaceName}Update extends Partial<Omit<${interfaceName}, '${metadata.PrimaryIdAttribute}' | 'createdon' | 'modifiedon'>> {}
|
|
367
|
+
`;
|
|
368
|
+
|
|
369
|
+
return content;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function generateConstantsContent(metadata) {
|
|
373
|
+
const className = `${metadata.SchemaName}Constants`;
|
|
374
|
+
const attributes = metadata.Attributes || [];
|
|
375
|
+
|
|
376
|
+
let content = `// Auto-generated constants for ${metadata.DisplayName} entity
|
|
377
|
+
// Generated on: ${new Date().toISOString()}
|
|
378
|
+
// Source: ${metadata.LogicalName} metadata
|
|
379
|
+
|
|
380
|
+
export class ${className} {
|
|
381
|
+
// Entity metadata
|
|
382
|
+
public static readonly LogicalName = '${metadata.LogicalName}';
|
|
383
|
+
public static readonly SchemaName = '${metadata.SchemaName}';
|
|
384
|
+
public static readonly CollectionName = '${metadata.EntitySetName}';
|
|
385
|
+
public static readonly DisplayName = '${metadata.DisplayName}';
|
|
386
|
+
public static readonly PrimaryIdAttribute = '${metadata.PrimaryIdAttribute}';
|
|
387
|
+
public static readonly PrimaryNameAttribute = '${metadata.PrimaryNameAttribute}';
|
|
388
|
+
|
|
389
|
+
// Attribute names
|
|
390
|
+
`;
|
|
391
|
+
|
|
392
|
+
attributes.forEach((attr) => {
|
|
393
|
+
const constantName = attr.SchemaName;
|
|
394
|
+
content += ` /** Type: ${attr.AttributeType}, RequiredLevel: ${attr.RequiredLevel}${attr.MaxLength ? `, MaxLength: ${attr.MaxLength}` : ''} */\n`;
|
|
395
|
+
content += ` public static readonly ${constantName} = '${attr.LogicalName}';\n\n`;
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Add option sets if any
|
|
399
|
+
const optionSets = attributes.filter(
|
|
400
|
+
(attr) => attr.AttributeType === 'Picklist'
|
|
401
|
+
);
|
|
402
|
+
if (optionSets.length > 0) {
|
|
403
|
+
content += ` // Option Sets\n`;
|
|
404
|
+
optionSets.forEach((attr) => {
|
|
405
|
+
content += ` public static readonly ${attr.SchemaName}Options = {\n`;
|
|
406
|
+
content += ` // TODO: Add actual option values from metadata\n`;
|
|
407
|
+
content += ` };\n\n`;
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Add display names
|
|
412
|
+
content += ` // Display names for UI
|
|
413
|
+
public static readonly FieldDisplayNames = {\n`;
|
|
414
|
+
attributes.forEach((attr) => {
|
|
415
|
+
content += ` [this.${attr.SchemaName}]: '${attr.DisplayName}',\n`;
|
|
416
|
+
});
|
|
417
|
+
content += ` };\n\n`;
|
|
418
|
+
|
|
419
|
+
// Add required fields
|
|
420
|
+
const requiredFields = attributes.filter(
|
|
421
|
+
(attr) =>
|
|
422
|
+
attr.RequiredLevel === 'ApplicationRequired' ||
|
|
423
|
+
attr.RequiredLevel === 'SystemRequired'
|
|
424
|
+
);
|
|
425
|
+
content += ` // Required fields\n`;
|
|
426
|
+
content += ` public static readonly RequiredFields = [\n`;
|
|
427
|
+
requiredFields.forEach((attr) => {
|
|
428
|
+
content += ` this.${attr.SchemaName},\n`;
|
|
429
|
+
});
|
|
430
|
+
content += ` ];\n`;
|
|
431
|
+
|
|
432
|
+
content += `}\n`;
|
|
433
|
+
|
|
434
|
+
return content;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function mapAttributeTypeToTypeScript(attribute) {
|
|
438
|
+
switch (attribute.AttributeType) {
|
|
439
|
+
case 'String':
|
|
440
|
+
case 'Memo':
|
|
441
|
+
return 'string';
|
|
442
|
+
case 'Integer':
|
|
443
|
+
case 'BigInt':
|
|
444
|
+
case 'Double':
|
|
445
|
+
case 'Decimal':
|
|
446
|
+
case 'Money':
|
|
447
|
+
return 'number';
|
|
448
|
+
case 'Boolean':
|
|
449
|
+
return 'boolean';
|
|
450
|
+
case 'DateTime':
|
|
451
|
+
return 'Date';
|
|
452
|
+
case 'Uniqueidentifier':
|
|
453
|
+
return 'string';
|
|
454
|
+
case 'Picklist':
|
|
455
|
+
case 'State':
|
|
456
|
+
case 'Status':
|
|
457
|
+
return 'number';
|
|
458
|
+
case 'Lookup':
|
|
459
|
+
case 'Customer':
|
|
460
|
+
case 'Owner':
|
|
461
|
+
return 'string';
|
|
462
|
+
case 'MultiSelectPicklist':
|
|
463
|
+
return 'number[]';
|
|
464
|
+
default:
|
|
465
|
+
return 'unknown';
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function generateIndexFile(entityNames, outputDir) {
|
|
470
|
+
let content = `// Auto-generated index file for entity types
|
|
471
|
+
// Generated on: ${new Date().toISOString()}
|
|
472
|
+
|
|
473
|
+
`;
|
|
474
|
+
|
|
475
|
+
entityNames.forEach((entityName) => {
|
|
476
|
+
const schemaName = entityName.charAt(0).toUpperCase() + entityName.slice(1);
|
|
477
|
+
content += `export * from './${entityName}.types';\n`;
|
|
478
|
+
content += `export * from './${entityName}.constants';\n`;
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const indexPath = path.join(outputDir, 'index.ts');
|
|
482
|
+
await fs.writeFile(indexPath, content);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function getAllCustomEntities(environment) {
|
|
486
|
+
// Mock implementation - would make actual D365 Web API call
|
|
487
|
+
return ['account', 'contact', 'opportunity', 'quote', 'invoice'];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function runSchemaValidation(config) {
|
|
491
|
+
// Mock validation - would compare generated types with actual D365 schema
|
|
492
|
+
return {
|
|
493
|
+
isValid: true,
|
|
494
|
+
issues: [],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function fixValidationIssues(issues) {
|
|
499
|
+
// Mock fix implementation
|
|
500
|
+
console.log('Fixing validation issues...');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (require.main === module) {
|
|
504
|
+
program.parse();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
module.exports = {
|
|
508
|
+
pullMetadata,
|
|
509
|
+
generateTypes,
|
|
510
|
+
validateSchema,
|
|
511
|
+
syncAll,
|
|
512
|
+
};
|