@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.
Files changed (107) hide show
  1. package/bin/create-dynamics-app.js +1 -1
  2. package/dist/index.js +140 -15
  3. package/dist/index.js.map +1 -1
  4. package/dist/utils/consultingHelpers.d.ts +13 -0
  5. package/dist/utils/consultingHelpers.d.ts.map +1 -0
  6. package/dist/utils/consultingHelpers.js +569 -0
  7. package/dist/utils/consultingHelpers.js.map +1 -0
  8. package/dist/utils/copyTemplate.d.ts.map +1 -1
  9. package/dist/utils/copyTemplate.js.map +1 -1
  10. package/dist/utils/initGit.d.ts.map +1 -1
  11. package/dist/utils/initGit.js.map +1 -1
  12. package/dist/utils/installDependencies.d.ts.map +1 -1
  13. package/dist/utils/installDependencies.js +3 -2
  14. package/dist/utils/installDependencies.js.map +1 -1
  15. package/dist/utils/updatePackageJson.d.ts +1 -1
  16. package/dist/utils/updatePackageJson.d.ts.map +1 -1
  17. package/dist/utils/updatePackageJson.js +11 -1
  18. package/dist/utils/updatePackageJson.js.map +1 -1
  19. package/package.json +1 -1
  20. package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +302 -0
  21. package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +305 -0
  22. package/templates/dynamics-365-starter/README.md +566 -137
  23. package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +507 -0
  24. package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +372 -0
  25. package/templates/dynamics-365-starter/deployment/README.md +484 -0
  26. package/templates/dynamics-365-starter/deployment/pipelines/README.md +375 -0
  27. package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +330 -0
  28. package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +422 -0
  29. package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +636 -0
  30. package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +417 -0
  31. package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +582 -0
  32. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +486 -0
  33. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +567 -0
  34. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +703 -0
  35. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +671 -0
  36. package/templates/dynamics-365-starter/docs/ARCHITECTURE_OVERVIEW.md +506 -0
  37. package/templates/dynamics-365-starter/docs/BEST_PRACTICES.md +723 -0
  38. package/templates/dynamics-365-starter/docs/MIGRATION_GUIDE.md +447 -0
  39. package/templates/dynamics-365-starter/docs/team-standards/README.md +273 -0
  40. package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +577 -0
  41. package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +359 -0
  42. package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +700 -0
  43. package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +736 -0
  44. package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +727 -0
  45. package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +758 -0
  46. package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +878 -0
  47. package/templates/dynamics-365-starter/package.json +22 -1
  48. package/templates/dynamics-365-starter/public/index.html +8 -11
  49. package/templates/dynamics-365-starter/scripts/custom-build.js +255 -0
  50. package/templates/dynamics-365-starter/src/client-project-template/README.md +234 -0
  51. package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +114 -0
  52. package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +186 -0
  53. package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +667 -0
  54. package/templates/dynamics-365-starter/src/components/AccountForm.css +71 -0
  55. package/templates/dynamics-365-starter/src/components/AccountForm.tsx +541 -0
  56. package/templates/dynamics-365-starter/src/components/AccountManagement.css +86 -0
  57. package/templates/dynamics-365-starter/src/components/AccountManagement.tsx +370 -0
  58. package/templates/dynamics-365-starter/src/components/ContactForm.tsx +149 -63
  59. package/templates/dynamics-365-starter/src/components/ContactManagement.tsx +153 -63
  60. package/templates/dynamics-365-starter/src/components/Logging/LogDialog.tsx +291 -0
  61. package/templates/dynamics-365-starter/src/components/Logging/LoggingContext.tsx +166 -0
  62. package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.css +192 -0
  63. package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.tsx +177 -0
  64. package/templates/dynamics-365-starter/src/components/Logging/LoggingProvider.tsx +3 -0
  65. package/templates/dynamics-365-starter/src/components/Logging/logger.ts +193 -0
  66. package/templates/dynamics-365-starter/src/constants/account.ts +410 -0
  67. package/templates/dynamics-365-starter/src/constants/contact.ts +362 -0
  68. package/templates/dynamics-365-starter/src/examples/README.md +52 -0
  69. package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +625 -0
  70. package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +545 -0
  71. package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +722 -0
  72. package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +662 -0
  73. package/templates/dynamics-365-starter/src/index.tsx +107 -19
  74. package/templates/dynamics-365-starter/src/models/Account.ts +480 -0
  75. package/templates/dynamics-365-starter/src/models/BaseEntity.ts +204 -0
  76. package/templates/dynamics-365-starter/src/models/Contact.ts +580 -0
  77. package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +519 -0
  78. package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +456 -0
  79. package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +406 -0
  80. package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +578 -0
  81. package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +629 -0
  82. package/templates/dynamics-365-starter/src/pcf/ContactControlWrapper.tsx +75 -22
  83. package/templates/dynamics-365-starter/src/pcf/MultiEntityControlWrapper.tsx +205 -0
  84. package/templates/dynamics-365-starter/src/providers/DynamicsProvider.tsx +297 -80
  85. package/templates/dynamics-365-starter/src/services/MockApiService.ts +260 -0
  86. package/templates/dynamics-365-starter/src/services/ServiceFactory.ts +65 -0
  87. package/templates/dynamics-365-starter/src/services/XrmApiService.ts +213 -0
  88. package/templates/dynamics-365-starter/src/styles/index.css +74 -7
  89. package/templates/dynamics-365-starter/tools/entity-generator/index.js +168 -0
  90. package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +124 -0
  91. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +283 -0
  92. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +275 -0
  93. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +204 -0
  94. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +413 -0
  95. package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +250 -0
  96. package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +410 -0
  97. package/templates/dynamics-365-starter/tools/metadata-sync/index.js +512 -0
  98. package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +675 -0
  99. package/templates/dynamics-365-starter/tsconfig.json +11 -8
  100. package/templates/dynamics-365-starter/webpack.config.js +8 -9
  101. package/templates/power-pages-starter/README.md +7 -1
  102. package/templates/power-pages-starter/public/index.html +8 -11
  103. package/templates/power-pages-starter/src/components/ContactForm.tsx +60 -41
  104. package/templates/power-pages-starter/src/index.tsx +3 -3
  105. package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +46 -23
  106. package/templates/power-pages-starter/tsconfig.json +3 -9
  107. package/templates/power-pages-starter/webpack.config.js +8 -3
@@ -0,0 +1,582 @@
1
+ #!/bin/bash
2
+
3
+ # Dynamics 365 Deployment Automation Script (Bash)
4
+ # Cross-platform deployment script for Linux/macOS environments
5
+
6
+ set -euo pipefail
7
+
8
+ # Script configuration
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
11
+ DEPLOYMENT_DIR="${PROJECT_ROOT}/deployment"
12
+ LOG_DIR="${DEPLOYMENT_DIR}/logs"
13
+ BACKUP_DIR="${DEPLOYMENT_DIR}/backups"
14
+
15
+ # Default values
16
+ ENVIRONMENT=""
17
+ CONFIG_PATH="config/environments"
18
+ DRY_RUN=false
19
+ FORCE=false
20
+ SOLUTION_PATH="solutions"
21
+ SKIP_BACKUP=false
22
+ VERBOSE=false
23
+
24
+ # Colors for output
25
+ RED='\033[0;31m'
26
+ GREEN='\033[0;32m'
27
+ YELLOW='\033[1;33m'
28
+ BLUE='\033[0;34m'
29
+ MAGENTA='\033[0;35m'
30
+ CYAN='\033[0;36m'
31
+ NC='\033[0m' # No Color
32
+
33
+ # Logging functions
34
+ log() {
35
+ local level="${1:-INFO}"
36
+ local message="$2"
37
+ local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
38
+ local log_entry="[$timestamp] [$level] $message"
39
+
40
+ echo -e "$log_entry" | tee -a "$LOG_FILE"
41
+
42
+ case "$level" in
43
+ ERROR) echo -e "${RED}$log_entry${NC}" >&2 ;;
44
+ WARN) echo -e "${YELLOW}$log_entry${NC}" ;;
45
+ SUCCESS) echo -e "${GREEN}$log_entry${NC}" ;;
46
+ INFO) echo -e "$log_entry" ;;
47
+ esac
48
+ }
49
+
50
+ show_banner() {
51
+ echo -e "${CYAN}"
52
+ echo "╔══════════════════════════════════════════════════════════════╗"
53
+ echo "║ Dynamics 365 Deployment Automation ║"
54
+ echo "║ Enterprise Consulting Edition ║"
55
+ echo "╚══════════════════════════════════════════════════════════════╝"
56
+ echo -e "${NC}"
57
+ echo
58
+ echo -e "${YELLOW}Environment: $ENVIRONMENT${NC}"
59
+ echo -e "${YELLOW}Timestamp: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
60
+ if [ "$DRY_RUN" = true ]; then
61
+ echo -e "${MAGENTA}Mode: DRY RUN (no changes will be made)${NC}"
62
+ fi
63
+ echo
64
+ }
65
+
66
+ show_usage() {
67
+ cat << EOF
68
+ Usage: $0 [OPTIONS] <environment>
69
+
70
+ Deploy Dynamics 365 application to specified environment.
71
+
72
+ ARGUMENTS:
73
+ environment Target environment (dev, test, staging, prod)
74
+
75
+ OPTIONS:
76
+ -c, --config-path PATH Configuration directory path (default: config/environments)
77
+ -s, --solution-path PATH Solution files directory path (default: solutions)
78
+ -n, --dry-run Perform a dry run without making changes
79
+ -f, --force Force deployment even if errors occur
80
+ --skip-backup Skip environment backup
81
+ -v, --verbose Enable verbose output
82
+ -h, --help Show this help message
83
+
84
+ EXAMPLES:
85
+ $0 dev Deploy to development environment
86
+ $0 prod --dry-run Dry run deployment to production
87
+ $0 test --force Force deployment to test environment
88
+ $0 staging --skip-backup Deploy to staging without backup
89
+
90
+ EOF
91
+ }
92
+
93
+ parse_arguments() {
94
+ while [[ $# -gt 0 ]]; do
95
+ case $1 in
96
+ -c|--config-path)
97
+ CONFIG_PATH="$2"
98
+ shift 2
99
+ ;;
100
+ -s|--solution-path)
101
+ SOLUTION_PATH="$2"
102
+ shift 2
103
+ ;;
104
+ -n|--dry-run)
105
+ DRY_RUN=true
106
+ shift
107
+ ;;
108
+ -f|--force)
109
+ FORCE=true
110
+ shift
111
+ ;;
112
+ --skip-backup)
113
+ SKIP_BACKUP=true
114
+ shift
115
+ ;;
116
+ -v|--verbose)
117
+ VERBOSE=true
118
+ shift
119
+ ;;
120
+ -h|--help)
121
+ show_usage
122
+ exit 0
123
+ ;;
124
+ -*)
125
+ echo "Unknown option: $1" >&2
126
+ show_usage
127
+ exit 1
128
+ ;;
129
+ *)
130
+ if [ -z "$ENVIRONMENT" ]; then
131
+ ENVIRONMENT="$1"
132
+ else
133
+ echo "Unexpected argument: $1" >&2
134
+ show_usage
135
+ exit 1
136
+ fi
137
+ shift
138
+ ;;
139
+ esac
140
+ done
141
+
142
+ if [ -z "$ENVIRONMENT" ]; then
143
+ echo "Error: Environment argument is required" >&2
144
+ show_usage
145
+ exit 1
146
+ fi
147
+
148
+ case "$ENVIRONMENT" in
149
+ dev|test|staging|prod)
150
+ ;;
151
+ *)
152
+ echo "Error: Invalid environment '$ENVIRONMENT'. Must be one of: dev, test, staging, prod" >&2
153
+ exit 1
154
+ ;;
155
+ esac
156
+ }
157
+
158
+ setup_logging() {
159
+ # Create log directory
160
+ mkdir -p "$LOG_DIR"
161
+
162
+ # Set log file
163
+ LOG_FILE="${LOG_DIR}/deploy-${ENVIRONMENT}-$(date '+%Y%m%d-%H%M%S').log"
164
+
165
+ # Create log file
166
+ touch "$LOG_FILE"
167
+
168
+ log "INFO" "Deployment log initialized: $LOG_FILE"
169
+ }
170
+
171
+ load_configuration() {
172
+ log "INFO" "Loading configuration for environment: $ENVIRONMENT"
173
+
174
+ local config_file="${PROJECT_ROOT}/${CONFIG_PATH}/${ENVIRONMENT}.json"
175
+
176
+ if [ ! -f "$config_file" ]; then
177
+ log "ERROR" "Configuration file not found: $config_file"
178
+ exit 1
179
+ fi
180
+
181
+ # Validate JSON
182
+ if ! jq empty "$config_file" 2>/dev/null; then
183
+ log "ERROR" "Invalid JSON in configuration file: $config_file"
184
+ exit 1
185
+ fi
186
+
187
+ # Export configuration as environment variables
188
+ eval "$(jq -r '
189
+ def to_env_var(prefix):
190
+ to_entries[] | "\(prefix)_\(.key | ascii_upcase)=\(.value | @sh)";
191
+
192
+ to_env_var("D365")
193
+ ' "$config_file")"
194
+
195
+ log "INFO" "Configuration loaded successfully"
196
+ }
197
+
198
+ check_prerequisites() {
199
+ log "INFO" "Checking deployment prerequisites..."
200
+
201
+ # Check required tools
202
+ local required_tools=("node" "npm" "jq" "curl")
203
+
204
+ for tool in "${required_tools[@]}"; do
205
+ if ! command -v "$tool" &> /dev/null; then
206
+ log "ERROR" "Required tool not found: $tool"
207
+ exit 1
208
+ fi
209
+ done
210
+
211
+ # Check if build artifacts exist
212
+ local dist_path="${PROJECT_ROOT}/dist"
213
+ if [ ! -d "$dist_path" ]; then
214
+ log "ERROR" "Build artifacts not found. Please run 'npm run build:prod' first."
215
+ exit 1
216
+ fi
217
+
218
+ # Check if solution files exist (optional)
219
+ local solution_dir="${PROJECT_ROOT}/${SOLUTION_PATH}"
220
+ if [ ! -d "$solution_dir" ]; then
221
+ log "WARN" "Solution directory not found, will deploy web resources only"
222
+ fi
223
+
224
+ log "INFO" "Prerequisites check completed successfully"
225
+ }
226
+
227
+ build_application() {
228
+ log "INFO" "Building application for deployment..."
229
+
230
+ cd "$PROJECT_ROOT"
231
+
232
+ if [ "$DRY_RUN" = true ]; then
233
+ log "INFO" "DRY RUN: Would build application with 'npm run build:prod'"
234
+ return 0
235
+ fi
236
+
237
+ # Run build
238
+ if npm run build:prod; then
239
+ log "SUCCESS" "Application built successfully"
240
+ else
241
+ log "ERROR" "Failed to build application"
242
+ exit 1
243
+ fi
244
+ }
245
+
246
+ create_backup() {
247
+ if [ "$SKIP_BACKUP" = true ]; then
248
+ log "INFO" "Skipping environment backup as requested"
249
+ return 0
250
+ fi
251
+
252
+ log "INFO" "Creating environment backup..."
253
+
254
+ mkdir -p "$BACKUP_DIR"
255
+
256
+ local backup_name="backup-${ENVIRONMENT}-$(date '+%Y%m%d-%H%M%S')"
257
+ local backup_file="${BACKUP_DIR}/${backup_name}.tar.gz"
258
+
259
+ if [ "$DRY_RUN" = true ]; then
260
+ log "INFO" "DRY RUN: Would create backup: $backup_file"
261
+ return 0
262
+ fi
263
+
264
+ # In real implementation, you would create an actual backup
265
+ # For now, create a placeholder backup
266
+ if tar -czf "$backup_file" -C "$PROJECT_ROOT" dist/ config/ 2>/dev/null; then
267
+ log "SUCCESS" "Backup created successfully: $backup_file"
268
+ else
269
+ if [ "$FORCE" = true ]; then
270
+ log "WARN" "Backup failed but continuing due to --force flag"
271
+ else
272
+ log "ERROR" "Failed to create backup"
273
+ exit 1
274
+ fi
275
+ fi
276
+ }
277
+
278
+ deploy_web_resources() {
279
+ log "INFO" "Deploying web resources..."
280
+
281
+ local dist_path="${PROJECT_ROOT}/dist"
282
+ local web_resources=()
283
+
284
+ # Find all files in dist directory
285
+ while IFS= read -r -d '' file; do
286
+ local relative_path="${file#$dist_path/}"
287
+ local web_resource_name="dynamics_ui_kit_$(echo "$relative_path" | tr '/' '_' | tr '.' '_')"
288
+ web_resources+=("$web_resource_name|$file|$relative_path")
289
+ done < <(find "$dist_path" -type f -print0)
290
+
291
+ log "INFO" "Found ${#web_resources[@]} web resources to deploy"
292
+
293
+ for resource in "${web_resources[@]}"; do
294
+ IFS='|' read -r name filepath relativepath <<< "$resource"
295
+
296
+ if [ "$DRY_RUN" = true ]; then
297
+ log "INFO" "DRY RUN: Would deploy web resource: $name"
298
+ else
299
+ if deploy_single_web_resource "$name" "$filepath" "$relativepath"; then
300
+ log "SUCCESS" "Web resource deployed successfully: $name"
301
+ else
302
+ log "ERROR" "Failed to deploy web resource: $name"
303
+ if [ "$FORCE" != true ]; then
304
+ exit 1
305
+ fi
306
+ fi
307
+ fi
308
+ done
309
+
310
+ # Publish customizations
311
+ if [ "$DRY_RUN" != true ]; then
312
+ log "INFO" "Publishing customizations..."
313
+ publish_customizations
314
+ log "SUCCESS" "Customizations published successfully"
315
+ fi
316
+ }
317
+
318
+ deploy_single_web_resource() {
319
+ local name="$1"
320
+ local filepath="$2"
321
+ local relativepath="$3"
322
+
323
+ # In real implementation, you would use D365 Web API
324
+ # This is a placeholder for the actual deployment logic
325
+
326
+ local api_url="${D365_DYNAMICS365_WEBAPIURL}/webresourceset"
327
+ local content_base64=$(base64 -w 0 "$filepath")
328
+ local file_type=$(get_web_resource_type "$filepath")
329
+
330
+ local json_payload=$(jq -n \
331
+ --arg name "$name" \
332
+ --arg displayname "Dynamics UI Kit - $relativepath" \
333
+ --arg content "$content_base64" \
334
+ --arg webresourcetype "$file_type" \
335
+ '{
336
+ name: $name,
337
+ displayname: $displayname,
338
+ content: $content,
339
+ webresourcetype: ($webresourcetype | tonumber)
340
+ }')
341
+
342
+ # Simulate API call (in real implementation, use actual D365 Web API)
343
+ if [ "$VERBOSE" = true ]; then
344
+ log "INFO" "Deploying: $name (type: $file_type)"
345
+ fi
346
+
347
+ # Return success for simulation
348
+ return 0
349
+ }
350
+
351
+ get_web_resource_type() {
352
+ local filepath="$1"
353
+ local extension="${filepath##*.}"
354
+
355
+ case "${extension,,}" in
356
+ js) echo "3" ;; # JavaScript
357
+ css) echo "2" ;; # CSS
358
+ html) echo "1" ;; # HTML
359
+ htm) echo "1" ;; # HTML
360
+ xml) echo "4" ;; # XML
361
+ png) echo "5" ;; # PNG
362
+ jpg) echo "6" ;; # JPG
363
+ jpeg) echo "6" ;; # JPG
364
+ gif) echo "7" ;; # GIF
365
+ ico) echo "10";; # ICO
366
+ svg) echo "11";; # SVG
367
+ *) echo "8" ;; # Other
368
+ esac
369
+ }
370
+
371
+ publish_customizations() {
372
+ # In real implementation, call D365 PublishAllXml action
373
+ log "INFO" "Publishing all customizations..."
374
+ return 0
375
+ }
376
+
377
+ deploy_solutions() {
378
+ local solution_dir="${PROJECT_ROOT}/${SOLUTION_PATH}"
379
+
380
+ if [ ! -d "$solution_dir" ]; then
381
+ log "INFO" "No solutions directory found, skipping solution deployment"
382
+ return 0
383
+ fi
384
+
385
+ log "INFO" "Deploying solutions..."
386
+
387
+ # Find all solution files
388
+ local solution_files=()
389
+ while IFS= read -r -d '' file; do
390
+ solution_files+=("$file")
391
+ done < <(find "$solution_dir" -name "*.zip" -print0 | sort -z)
392
+
393
+ if [ ${#solution_files[@]} -eq 0 ]; then
394
+ log "INFO" "No solution files found in $solution_dir"
395
+ return 0
396
+ fi
397
+
398
+ for solution_file in "${solution_files[@]}"; do
399
+ local solution_name=$(basename "$solution_file")
400
+
401
+ if [ "$DRY_RUN" = true ]; then
402
+ log "INFO" "DRY RUN: Would deploy solution: $solution_name"
403
+ else
404
+ if deploy_single_solution "$solution_file"; then
405
+ log "SUCCESS" "Solution deployed successfully: $solution_name"
406
+ else
407
+ log "ERROR" "Failed to deploy solution: $solution_name"
408
+ if [ "$FORCE" != true ]; then
409
+ exit 1
410
+ fi
411
+ fi
412
+ fi
413
+ done
414
+ }
415
+
416
+ deploy_single_solution() {
417
+ local solution_file="$1"
418
+ local solution_name=$(basename "$solution_file")
419
+
420
+ log "INFO" "Deploying solution: $solution_name"
421
+
422
+ # In real implementation, use D365 Web API or PowerShell
423
+ # This is a placeholder for actual solution import
424
+
425
+ return 0
426
+ }
427
+
428
+ run_post_deployment_tests() {
429
+ log "INFO" "Running post-deployment tests..."
430
+
431
+ if [ "$DRY_RUN" = true ]; then
432
+ log "INFO" "DRY RUN: Would run post-deployment tests"
433
+ return 0
434
+ fi
435
+
436
+ # Test environment connectivity
437
+ if test_environment_connectivity; then
438
+ log "SUCCESS" "Environment connectivity test passed"
439
+ else
440
+ log "ERROR" "Environment connectivity test failed"
441
+ return 1
442
+ fi
443
+
444
+ # Test web resources accessibility
445
+ if test_web_resources_accessibility; then
446
+ log "SUCCESS" "Web resources accessibility test passed"
447
+ else
448
+ log "ERROR" "Web resources accessibility test failed"
449
+ return 1
450
+ fi
451
+
452
+ log "SUCCESS" "All post-deployment tests passed"
453
+ return 0
454
+ }
455
+
456
+ test_environment_connectivity() {
457
+ local org_url="${D365_DYNAMICS365_ORGURL}"
458
+
459
+ if curl -s --head "$org_url" | head -n 1 | grep -q "200 OK"; then
460
+ return 0
461
+ else
462
+ return 1
463
+ fi
464
+ }
465
+
466
+ test_web_resources_accessibility() {
467
+ # Test a sample web resource
468
+ local test_url="${D365_DYNAMICS365_ORGURL}/WebResources/dynamics_ui_kit_index_js"
469
+
470
+ if curl -s --head "$test_url" | head -n 1 | grep -q "200 OK"; then
471
+ return 0
472
+ else
473
+ return 1
474
+ fi
475
+ }
476
+
477
+ send_notification() {
478
+ local status="$1"
479
+ local error_message="${2:-}"
480
+
481
+ log "INFO" "Sending deployment notification..."
482
+
483
+ local subject="Dynamics 365 Deployment - $ENVIRONMENT - $status"
484
+ local body="Deployment Details:
485
+ - Environment: $ENVIRONMENT
486
+ - Status: $status
487
+ - Timestamp: $(date '+%Y-%m-%d %H:%M:%S')
488
+ - Log File: $LOG_FILE
489
+
490
+ $([ -n "$error_message" ] && echo "Error Details:" && echo "$error_message")"
491
+
492
+ # In real implementation, send actual notifications
493
+ # For now, just log the notification
494
+ log "INFO" "Notification: $subject"
495
+
496
+ return 0
497
+ }
498
+
499
+ cleanup() {
500
+ log "INFO" "Performing cleanup..."
501
+
502
+ # Clean up temporary files, restore original state if needed
503
+
504
+ log "INFO" "Cleanup completed"
505
+ }
506
+
507
+ # Trap for cleanup on exit
508
+ trap cleanup EXIT
509
+
510
+ # Main deployment workflow
511
+ main() {
512
+ parse_arguments "$@"
513
+ setup_logging
514
+ show_banner
515
+
516
+ log "INFO" "Starting deployment to $ENVIRONMENT environment"
517
+ log "INFO" "Log file: $LOG_FILE"
518
+
519
+ # Load configuration
520
+ load_configuration
521
+
522
+ # Check prerequisites
523
+ check_prerequisites
524
+
525
+ # Build application
526
+ build_application
527
+
528
+ # Create backup
529
+ create_backup
530
+
531
+ # Deploy web resources
532
+ deploy_web_resources
533
+
534
+ # Deploy solutions
535
+ deploy_solutions
536
+
537
+ # Run post-deployment tests
538
+ if run_post_deployment_tests; then
539
+ log "SUCCESS" "Deployment completed successfully!"
540
+ send_notification "SUCCESS"
541
+
542
+ echo
543
+ echo -e "${GREEN}✅ Deployment completed successfully!${NC}"
544
+ echo -e "${CYAN}📋 Log file: $LOG_FILE${NC}"
545
+
546
+ if [ "$DRY_RUN" != true ]; then
547
+ echo -e "${CYAN}🌐 Environment URL: ${D365_DYNAMICS365_ORGURL}${NC}"
548
+ fi
549
+
550
+ exit 0
551
+ else
552
+ log "ERROR" "Post-deployment tests failed"
553
+ send_notification "FAILED" "Post-deployment tests failed"
554
+
555
+ echo
556
+ echo -e "${RED}❌ Deployment failed!${NC}"
557
+ echo -e "${RED}Error: Post-deployment tests failed${NC}"
558
+ echo -e "${CYAN}📋 Log file: $LOG_FILE${NC}"
559
+
560
+ exit 1
561
+ fi
562
+ }
563
+
564
+ # Error handling
565
+ handle_error() {
566
+ local error_message="$1"
567
+ log "ERROR" "Deployment failed: $error_message"
568
+ send_notification "FAILED" "$error_message"
569
+
570
+ echo
571
+ echo -e "${RED}❌ Deployment failed!${NC}"
572
+ echo -e "${RED}Error: $error_message${NC}"
573
+ echo -e "${CYAN}📋 Log file: $LOG_FILE${NC}"
574
+
575
+ exit 1
576
+ }
577
+
578
+ # Set error trap
579
+ trap 'handle_error "Unexpected error occurred"' ERR
580
+
581
+ # Run main function
582
+ main "$@"