sfdx-hardis 6.5.0 → 6.5.2
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/CHANGELOG.md +12 -0
- package/defaults/ci/.gitlab-ci.yml +1 -1
- package/defaults/ci/Jenkinsfile +3 -1
- package/defaults/lintonly/.gitlab-ci.yml +1 -1
- package/defaults/monitoring/.gitlab-ci.yml +1 -1
- package/lib/commands/hardis/doc/project2markdown.js +179 -15
- package/lib/commands/hardis/doc/project2markdown.js.map +1 -1
- package/lib/commands/hardis/org/monitor/backup.js +2 -0
- package/lib/commands/hardis/org/monitor/backup.js.map +1 -1
- package/lib/common/utils/deployUtils.js +5 -1
- package/lib/common/utils/deployUtils.js.map +1 -1
- package/lib/common/utils/index.js +24 -2
- package/lib/common/utils/index.js.map +1 -1
- package/lib/common/utils/orgUtils.js +4 -1
- package/lib/common/utils/orgUtils.js.map +1 -1
- package/oclif.lock +22 -22
- package/oclif.manifest.json +389 -389
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
Note: Can be used with `sfdx plugins:install sfdx-hardis@beta` and docker image `hardisgroupcom/sfdx-hardis@beta`
|
|
6
6
|
|
|
7
|
+
- Display alias is org selection
|
|
8
|
+
|
|
9
|
+
## [6.5.1] 2025-09-20
|
|
10
|
+
|
|
11
|
+
- [hardis:org:monitor:backup](https://sfdx-hardis.cloudity.com/hardis/org/monitor/backup/) enhancements:
|
|
12
|
+
- Creates the 'force-app/main/default' directory if it doesn't exist before retrieving metadatas
|
|
13
|
+
- [hardis:org:configure:monitoring](https://sfdx-hardis.cloudity.com/hardis/org/configure/monitoring/):
|
|
14
|
+
- Display the connected App XML in logs (while hiding sensitive info)
|
|
15
|
+
- When production org, run the first found test class (or allow to force its selection using ENV variable `SFDX_HARDIS_TECH_DEPLOY_TEST_CLASS` )
|
|
16
|
+
- Add instructions to use ghcr.io Docker image in case of rate limits reached on Docker Hub
|
|
17
|
+
- Handle progress component in UI when generating documentation
|
|
18
|
+
|
|
7
19
|
## [6.5.0] 2025-09-17
|
|
8
20
|
|
|
9
21
|
- Files export enhancements:
|
|
@@ -20,7 +20,7 @@ stages:
|
|
|
20
20
|
|
|
21
21
|
# Jobs are run on sfdx-hardis image, that includes all required dependencies.
|
|
22
22
|
# You can use latest, beta or latest-recommended
|
|
23
|
-
image: hardisgroupcom/sfdx-hardis:latest
|
|
23
|
+
image: hardisgroupcom/sfdx-hardis:latest # If rate limits reached, use ghcr.io/hardisgroupcom/sfdx-hardis:latest
|
|
24
24
|
|
|
25
25
|
# Force color for output logs for better readability
|
|
26
26
|
variables:
|
package/defaults/ci/Jenkinsfile
CHANGED
|
@@ -54,7 +54,8 @@ pipeline {
|
|
|
54
54
|
stage('Validation') {
|
|
55
55
|
agent {
|
|
56
56
|
docker {
|
|
57
|
-
|
|
57
|
+
// If rate limits reached, use ghcr.io/hardisgroupcom/sfdx-hardis:latest
|
|
58
|
+
image 'hardisgroupcom/sfdx-hardis:latest'
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
when { changeRequest() }
|
|
@@ -74,6 +75,7 @@ pipeline {
|
|
|
74
75
|
stage('Deployment') {
|
|
75
76
|
agent {
|
|
76
77
|
docker {
|
|
78
|
+
// If rate limits reached, use ghcr.io/hardisgroupcom/sfdx-hardis:latest
|
|
77
79
|
image 'hardisgroupcom/sfdx-hardis:latest'
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -7,7 +7,7 @@ stages:
|
|
|
7
7
|
|
|
8
8
|
# On execute les jobs sur l'image hardisgroupcom/sfdx-hardis qui contient les applications necessaires
|
|
9
9
|
# Version latest recommandée, cependant beta et alpha peuvent être utilisées pour les tests
|
|
10
|
-
image: hardisgroupcom/sfdx-hardis:latest
|
|
10
|
+
image: hardisgroupcom/sfdx-hardis:latest # If rate limits reached, use ghcr.io/hardisgroupcom/sfdx-hardis:latest
|
|
11
11
|
|
|
12
12
|
# Variables globales aux jobs
|
|
13
13
|
variables:
|
|
@@ -16,7 +16,7 @@ stages:
|
|
|
16
16
|
- monitor
|
|
17
17
|
|
|
18
18
|
# Use sfdx-hardis docker image to always be up to date with latest version
|
|
19
|
-
image: hardisgroupcom/sfdx-hardis:latest #
|
|
19
|
+
image: hardisgroupcom/sfdx-hardis:latest # If rate limits reached, use ghcr.io/hardisgroupcom/sfdx-hardis:latest
|
|
20
20
|
|
|
21
21
|
##############################################
|
|
22
22
|
### Sfdx Sources Backup + Push new commit ####
|
|
@@ -261,7 +261,7 @@ ${this.htmlInstructions}
|
|
|
261
261
|
await fs.writeFile(path.join(this.outputMarkdownRoot, "manifests.md"), getMetaHideLines() + packageLines.join("\n") + `\n${this.footer}\n`);
|
|
262
262
|
this.tempDir = await createTempDir();
|
|
263
263
|
// Convert source to metadata API format to build prompts
|
|
264
|
-
uxLog("action", this, c.cyan("Converting source to metadata API format..."));
|
|
264
|
+
uxLog("action", this, c.cyan("Converting source to metadata API format to ease the build of LLM prompts..."));
|
|
265
265
|
await execCommand(`sf project convert source --metadata CustomObject --output-dir ${this.tempDir}`, this, { fail: true, output: true, debug: this.debugMode });
|
|
266
266
|
this.objectFiles = (await glob("**/*.object", { cwd: this.tempDir, ignore: GLOB_IGNORE_PATTERNS }));
|
|
267
267
|
sortCrossPlatform(this.objectFiles);
|
|
@@ -351,7 +351,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
351
351
|
return { outputPackageXmlMarkdownFiles: this.outputPackageXmlMarkdownFiles };
|
|
352
352
|
}
|
|
353
353
|
async generateApexDocumentation() {
|
|
354
|
-
uxLog("action", this, c.cyan("
|
|
354
|
+
uxLog("action", this, c.cyan("Calling ApexDocGen to initialize Apex documentation... (if you don't want it, define GENERATE_APEX_DOC=false in your environment variables)"));
|
|
355
355
|
const tempDir = await createTempDir();
|
|
356
356
|
uxLog("log", this, c.grey(`Using temp directory ${tempDir}`));
|
|
357
357
|
const packageDirs = this.project?.getPackageDirectories() || [];
|
|
@@ -396,7 +396,13 @@ ${Project2Markdown.htmlInstructions}
|
|
|
396
396
|
});
|
|
397
397
|
}
|
|
398
398
|
// Complete generated documentation
|
|
399
|
+
if (apexFiles.length === 0) {
|
|
400
|
+
uxLog("log", this, c.yellow("No Apex class found in the project"));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
399
403
|
const apexForMenu = { "All Apex Classes": "apex/index.md" };
|
|
404
|
+
WebSocketClient.sendProgressStartMessage("Generating Apex documentation...", apexFiles.length);
|
|
405
|
+
let counter = 0;
|
|
400
406
|
for (const apexFile of apexFiles) {
|
|
401
407
|
const apexName = path.basename(apexFile, ".cls").replace(".trigger", "");
|
|
402
408
|
const apexContent = await fs.readFile(apexFile, "utf8");
|
|
@@ -431,7 +437,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
431
437
|
await generatePdfFileFromMarkdown(mdFile);
|
|
432
438
|
}
|
|
433
439
|
}
|
|
440
|
+
counter++;
|
|
441
|
+
WebSocketClient.sendProgressStepMessage(counter, apexFiles.length);
|
|
434
442
|
}
|
|
443
|
+
WebSocketClient.sendProgressEndMessage();
|
|
435
444
|
this.addNavNode("Apex", apexForMenu);
|
|
436
445
|
// Write index file for apex folder
|
|
437
446
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "apex"));
|
|
@@ -439,7 +448,6 @@ ${Project2Markdown.htmlInstructions}
|
|
|
439
448
|
await fs.writeFile(apexIndexFile, getMetaHideLines() + DocBuilderApex.buildIndexTable('', this.apexDescriptions).join("\n") + `\n\n${this.footer}\n`);
|
|
440
449
|
}
|
|
441
450
|
async generatePackagesDocumentation() {
|
|
442
|
-
uxLog("action", this, c.cyan("Generating Installed Packages documentation..."));
|
|
443
451
|
const packagesForMenu = { "All Packages": "packages/index.md" };
|
|
444
452
|
// List packages
|
|
445
453
|
const packages = this.sfdxHardisConfig.installedPackages || []; // CI/CD context
|
|
@@ -456,6 +464,8 @@ ${Project2Markdown.htmlInstructions}
|
|
|
456
464
|
packages.push(pckg);
|
|
457
465
|
}
|
|
458
466
|
}
|
|
467
|
+
WebSocketClient.sendProgressStartMessage("Generating Installed Packages documentation...", packages.length);
|
|
468
|
+
let counter = 0;
|
|
459
469
|
// Process packages
|
|
460
470
|
for (const pckg of packages) {
|
|
461
471
|
const packageName = pckg.SubscriberPackageName;
|
|
@@ -491,17 +501,22 @@ ${Project2Markdown.htmlInstructions}
|
|
|
491
501
|
if (mdFileBad !== mdFile && fs.existsSync(mdFileBad)) {
|
|
492
502
|
await fs.remove(mdFileBad);
|
|
493
503
|
}
|
|
504
|
+
counter++;
|
|
505
|
+
WebSocketClient.sendProgressStepMessage(counter, packages.length);
|
|
494
506
|
}
|
|
495
507
|
this.addNavNode("Packages", packagesForMenu);
|
|
496
508
|
// Write index file for packages folder
|
|
497
509
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "packages"));
|
|
498
510
|
const packagesIndexFile = path.join(this.outputMarkdownRoot, "packages", "index.md");
|
|
499
511
|
await fs.writeFile(packagesIndexFile, getMetaHideLines() + DocBuilderPackage.buildIndexTable('', this.packageDescriptions).join("\n") + `\n\n${this.footer}\n`);
|
|
512
|
+
WebSocketClient.sendProgressEndMessage();
|
|
500
513
|
}
|
|
501
514
|
async generatePagesDocumentation() {
|
|
502
515
|
const packageDirs = this.project?.getPackageDirectories() || [];
|
|
503
516
|
const pageFiles = await listPageFiles(packageDirs);
|
|
504
517
|
const pagesForMenu = { "All Lightning pages": "pages/index.md" };
|
|
518
|
+
WebSocketClient.sendProgressStartMessage("Generating Lightning Pages documentation...", pageFiles.length);
|
|
519
|
+
let counter = 0;
|
|
505
520
|
for (const pagefile of pageFiles) {
|
|
506
521
|
const pageName = path.basename(pagefile, ".flexipage-meta.xml");
|
|
507
522
|
const mdFile = path.join(this.outputMarkdownRoot, "pages", pageName + ".md");
|
|
@@ -518,7 +533,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
518
533
|
if (this.withPdf) {
|
|
519
534
|
await generatePdfFileFromMarkdown(mdFile);
|
|
520
535
|
}
|
|
536
|
+
counter++;
|
|
537
|
+
WebSocketClient.sendProgressStepMessage(counter, pageFiles.length);
|
|
521
538
|
}
|
|
539
|
+
WebSocketClient.sendProgressEndMessage();
|
|
522
540
|
this.addNavNode("Lightning Pages", pagesForMenu);
|
|
523
541
|
// Write index file for pages folder
|
|
524
542
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "pages"));
|
|
@@ -526,10 +544,16 @@ ${Project2Markdown.htmlInstructions}
|
|
|
526
544
|
await fs.writeFile(pagesIndexFile, getMetaHideLines() + DocBuilderPage.buildIndexTable('', this.pageDescriptions).join("\n") + `\n\n${this.footer}\n`);
|
|
527
545
|
}
|
|
528
546
|
async generateProfilesDocumentation() {
|
|
529
|
-
uxLog("action", this, c.cyan("
|
|
547
|
+
uxLog("action", this, c.cyan("Preparing generation of Profiles documentation... (if you don't want it, define GENERATE_PROFILES_DOC=false in your environment variables)"));
|
|
530
548
|
const profilesForMenu = { "All Profiles": "profiles/index.md" };
|
|
531
549
|
const profilesFiles = (await glob("**/profiles/**.profile-meta.xml", { cwd: process.cwd(), ignore: GLOB_IGNORE_PATTERNS }));
|
|
532
550
|
sortCrossPlatform(profilesFiles);
|
|
551
|
+
if (profilesFiles.length === 0) {
|
|
552
|
+
uxLog("log", this, c.yellow("No profile found in the project"));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
WebSocketClient.sendProgressStartMessage("Generating Profiles documentation...", profilesFiles.length);
|
|
556
|
+
let counter = 0;
|
|
533
557
|
for (const profileFile of profilesFiles) {
|
|
534
558
|
const profileName = path.basename(profileFile, ".profile-meta.xml");
|
|
535
559
|
const mdFile = path.join(this.outputMarkdownRoot, "profiles", profileName + ".md");
|
|
@@ -546,7 +570,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
546
570
|
if (this.withPdf) {
|
|
547
571
|
await generatePdfFileFromMarkdown(mdFile);
|
|
548
572
|
}
|
|
573
|
+
counter++;
|
|
574
|
+
WebSocketClient.sendProgressStepMessage(counter, profilesFiles.length);
|
|
549
575
|
}
|
|
576
|
+
WebSocketClient.sendProgressEndMessage();
|
|
550
577
|
this.addNavNode("Profiles", profilesForMenu);
|
|
551
578
|
// Write index file for profiles folder
|
|
552
579
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "profiles"));
|
|
@@ -554,10 +581,16 @@ ${Project2Markdown.htmlInstructions}
|
|
|
554
581
|
await fs.writeFile(profilesIndexFile, getMetaHideLines() + DocBuilderProfile.buildIndexTable('', this.profileDescriptions).join("\n") + `\n\n${this.footer}\n`);
|
|
555
582
|
}
|
|
556
583
|
async generatePermissionSetsDocumentation() {
|
|
557
|
-
uxLog("action", this, c.cyan("
|
|
584
|
+
uxLog("action", this, c.cyan("Preparing generation of Permission Sets documentation... (if you don't want it, define GENERATE_PROFILES_DOC=false in your environment variables)"));
|
|
558
585
|
const psForMenu = { "All Permission Sets": "permissionsets/index.md" };
|
|
559
586
|
const psFiles = (await glob("**/permissionsets/**.permissionset-meta.xml", { cwd: process.cwd(), ignore: GLOB_IGNORE_PATTERNS }));
|
|
560
587
|
sortCrossPlatform(psFiles);
|
|
588
|
+
if (psFiles.length === 0) {
|
|
589
|
+
uxLog("log", this, c.yellow("No permission set found in the project"));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
WebSocketClient.sendProgressStartMessage("Generating Permission Sets documentation...", psFiles.length);
|
|
593
|
+
let counter = 0;
|
|
561
594
|
for (const psFile of psFiles) {
|
|
562
595
|
const psName = path.basename(psFile, ".permissionset-meta.xml");
|
|
563
596
|
const mdFile = path.join(this.outputMarkdownRoot, "permissionsets", psName + ".md");
|
|
@@ -577,7 +610,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
577
610
|
if (this.withPdf) {
|
|
578
611
|
await generatePdfFileFromMarkdown(mdFile);
|
|
579
612
|
}
|
|
613
|
+
counter++;
|
|
614
|
+
WebSocketClient.sendProgressStepMessage(counter, psFiles.length);
|
|
580
615
|
}
|
|
616
|
+
WebSocketClient.sendProgressEndMessage();
|
|
581
617
|
this.addNavNode("Permission Sets", psForMenu);
|
|
582
618
|
// Write index file for permission sets folder
|
|
583
619
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "permissionsets"));
|
|
@@ -585,10 +621,16 @@ ${Project2Markdown.htmlInstructions}
|
|
|
585
621
|
await fs.writeFile(psIndexFile, getMetaHideLines() + DocBuilderPermissionSet.buildIndexTable('', this.permissionSetsDescriptions).join("\n") + `\n\n${this.footer}\n`);
|
|
586
622
|
}
|
|
587
623
|
async generatePermissionSetGroupsDocumentation() {
|
|
588
|
-
uxLog("action", this, c.cyan("
|
|
624
|
+
uxLog("action", this, c.cyan("Preparing generation of Permission Set Groups documentation..."));
|
|
589
625
|
const psgForMenu = { "All Permission Set Groups": "permissionsetgroups/index.md" };
|
|
590
626
|
const psgFiles = (await glob("**/permissionsetgroups/**.permissionsetgroup-meta.xml", { cwd: process.cwd(), ignore: GLOB_IGNORE_PATTERNS }));
|
|
591
627
|
sortCrossPlatform(psgFiles);
|
|
628
|
+
if (psgFiles.length === 0) {
|
|
629
|
+
uxLog("log", this, c.yellow("No permission set group found in the project"));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
WebSocketClient.sendProgressStartMessage("Generating Permission Set Groups documentation...", psgFiles.length);
|
|
633
|
+
let counter = 0;
|
|
592
634
|
for (const psgFile of psgFiles) {
|
|
593
635
|
const psgName = path.basename(psgFile, ".permissionsetgroup-meta.xml");
|
|
594
636
|
const mdFile = path.join(this.outputMarkdownRoot, "permissionsetgroups", psgName + ".md");
|
|
@@ -608,7 +650,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
608
650
|
if (this.withPdf) {
|
|
609
651
|
await generatePdfFileFromMarkdown(mdFile);
|
|
610
652
|
}
|
|
653
|
+
counter++;
|
|
654
|
+
WebSocketClient.sendProgressStepMessage(counter, psgFiles.length);
|
|
611
655
|
}
|
|
656
|
+
WebSocketClient.sendProgressEndMessage();
|
|
612
657
|
this.addNavNode("Permission Set Groups", psgForMenu);
|
|
613
658
|
// Write index file for permission set groups folder
|
|
614
659
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "permissionsetgroups"));
|
|
@@ -619,6 +664,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
619
664
|
uxLog("action", this, c.cyan("Generating Roles documentation... (if you don't want it, define GENERATE_PROFILES_DOC=false in your environment variables)"));
|
|
620
665
|
const roleFiles = (await glob("**/roles/**.role-meta.xml", { cwd: process.cwd(), ignore: GLOB_IGNORE_PATTERNS }));
|
|
621
666
|
sortCrossPlatform(roleFiles);
|
|
667
|
+
if (roleFiles.length === 0) {
|
|
668
|
+
uxLog("log", this, c.yellow("No role found in the project"));
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
622
671
|
for (const roleFile of roleFiles) {
|
|
623
672
|
const roleApiName = path.basename(roleFile, ".role-meta.xml");
|
|
624
673
|
const roleXml = await fs.readFile(roleFile, "utf8");
|
|
@@ -639,7 +688,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
639
688
|
}
|
|
640
689
|
}
|
|
641
690
|
async generateAssignmentRulesDocumentation() {
|
|
642
|
-
uxLog("action", this, c.cyan("
|
|
691
|
+
uxLog("action", this, c.cyan("Preparing generation of Assignment Rules documentation... " +
|
|
643
692
|
"(if you don't want it, define GENERATE_AUTOMATIONS_DOC=false in your environment variables)"));
|
|
644
693
|
const assignmentRulesForMenu = { "All Assignment Rules": "assignmentRules/index.md" };
|
|
645
694
|
const assignmentRulesFiles = (await glob("**/assignmentRules/**.assignmentRules-meta.xml", {
|
|
@@ -648,6 +697,23 @@ ${Project2Markdown.htmlInstructions}
|
|
|
648
697
|
}));
|
|
649
698
|
sortCrossPlatform(assignmentRulesFiles);
|
|
650
699
|
const builder = new XMLBuilder();
|
|
700
|
+
// Count total rules for progress tracking
|
|
701
|
+
let totalRules = 0;
|
|
702
|
+
for (const assignmentRulesFile of assignmentRulesFiles) {
|
|
703
|
+
const assignmentRulesXml = await fs.readFile(assignmentRulesFile, "utf8");
|
|
704
|
+
const assignmentRulesXmlParsed = new XMLParser().parse(assignmentRulesXml);
|
|
705
|
+
let rulesList = assignmentRulesXmlParsed?.AssignmentRules?.assignmentRule || [];
|
|
706
|
+
if (!Array.isArray(rulesList)) {
|
|
707
|
+
rulesList = [rulesList];
|
|
708
|
+
}
|
|
709
|
+
totalRules += rulesList.length;
|
|
710
|
+
}
|
|
711
|
+
if (totalRules === 0) {
|
|
712
|
+
uxLog("log", this, c.yellow("No assignment rule found in the project"));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
WebSocketClient.sendProgressStartMessage("Generating Assignment Rules documentation...", totalRules);
|
|
716
|
+
let counter = 0;
|
|
651
717
|
for (const assignmentRulesFile of assignmentRulesFiles) {
|
|
652
718
|
const assignmentRulesXml = await fs.readFile(assignmentRulesFile, "utf8");
|
|
653
719
|
const assignmentRulesXmlParsed = new XMLParser().parse(assignmentRulesXml);
|
|
@@ -670,15 +736,18 @@ ${Project2Markdown.htmlInstructions}
|
|
|
670
736
|
if (this.withPdf) {
|
|
671
737
|
await generatePdfFileFromMarkdown(mdFile);
|
|
672
738
|
}
|
|
739
|
+
counter++;
|
|
740
|
+
WebSocketClient.sendProgressStepMessage(counter, totalRules);
|
|
673
741
|
}
|
|
674
742
|
}
|
|
743
|
+
WebSocketClient.sendProgressEndMessage();
|
|
675
744
|
this.addNavNode("Assignment Rules", assignmentRulesForMenu);
|
|
676
745
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "assignmentRules"));
|
|
677
746
|
const psgIndexFile = path.join(this.outputMarkdownRoot, "assignmentRules", "index.md");
|
|
678
747
|
await fs.writeFile(psgIndexFile, getMetaHideLines() + DocBuilderAssignmentRules.buildIndexTable('', this.assignmentRulesDescriptions).join("\n") + `\n${this.footer}\n`);
|
|
679
748
|
}
|
|
680
749
|
async generateApprovalProcessDocumentation() {
|
|
681
|
-
uxLog("action", this, c.cyan("
|
|
750
|
+
uxLog("action", this, c.cyan("Preparing generation of Approval Processes documentation... " +
|
|
682
751
|
"(if you don't want it, define GENERATE_AUTOMATIONS_DOC=false in your environment variables)"));
|
|
683
752
|
const approvalProcessesForMenu = { "All Approval Processes": "approvalProcesses/index.md" };
|
|
684
753
|
const approvalProcessFiles = (await glob("**/approvalProcesses/**.approvalProcess-meta.xml", {
|
|
@@ -686,6 +755,12 @@ ${Project2Markdown.htmlInstructions}
|
|
|
686
755
|
ignore: GLOB_IGNORE_PATTERNS
|
|
687
756
|
}));
|
|
688
757
|
sortCrossPlatform(approvalProcessFiles);
|
|
758
|
+
if (approvalProcessFiles.length === 0) {
|
|
759
|
+
uxLog("log", this, c.yellow("No approval process found in the project"));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
WebSocketClient.sendProgressStartMessage("Generating Approval Processes documentation...", approvalProcessFiles.length);
|
|
763
|
+
let counter = 0;
|
|
689
764
|
for (const approvalProcessFile of approvalProcessFiles) {
|
|
690
765
|
const approvalProcessName = path.basename(approvalProcessFile, ".approvalProcess-meta.xml");
|
|
691
766
|
const mdFile = path.join(this.outputMarkdownRoot, "approvalProcesses", approvalProcessName + ".md");
|
|
@@ -701,14 +776,17 @@ ${Project2Markdown.htmlInstructions}
|
|
|
701
776
|
if (this.withPdf) {
|
|
702
777
|
await generatePdfFileFromMarkdown(mdFile);
|
|
703
778
|
}
|
|
779
|
+
counter++;
|
|
780
|
+
WebSocketClient.sendProgressStepMessage(counter, approvalProcessFiles.length);
|
|
704
781
|
}
|
|
782
|
+
WebSocketClient.sendProgressEndMessage();
|
|
705
783
|
this.addNavNode("Approval Processes", approvalProcessesForMenu);
|
|
706
784
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "approvalProcesses"));
|
|
707
785
|
const approvalProcessesIndexFile = path.join(this.outputMarkdownRoot, "approvalProcesses", "index.md");
|
|
708
786
|
await fs.writeFile(approvalProcessesIndexFile, getMetaHideLines() + DocBuilderApprovalProcess.buildIndexTable('', this.approvalProcessesDescriptions).join("\n") + `\n\n${this.footer}\n`);
|
|
709
787
|
}
|
|
710
788
|
async generateAutoResponseRulesDocumentation() {
|
|
711
|
-
uxLog("action", this, c.cyan("
|
|
789
|
+
uxLog("action", this, c.cyan("Preparing generation of AutoResponse Rules documentation... " +
|
|
712
790
|
"(if you don't want it, define GENERATE_AUTOMATIONS_DOC=false in your environment variables)"));
|
|
713
791
|
const autoResponseRulesForMenu = { "All AutoResponse Rules": "autoResponseRules/index.md" };
|
|
714
792
|
const autoResponseRulesFiles = (await glob("**/autoResponseRules/**.autoResponseRules-meta.xml", {
|
|
@@ -717,6 +795,23 @@ ${Project2Markdown.htmlInstructions}
|
|
|
717
795
|
}));
|
|
718
796
|
sortCrossPlatform(autoResponseRulesFiles);
|
|
719
797
|
const builder = new XMLBuilder();
|
|
798
|
+
// Count total rules for progress tracking
|
|
799
|
+
let totalRules = 0;
|
|
800
|
+
for (const autoResponseRulesFile of autoResponseRulesFiles) {
|
|
801
|
+
const autoResponseRulesXml = await fs.readFile(autoResponseRulesFile, "utf8");
|
|
802
|
+
const autoResponseRulesXmlParsed = new XMLParser().parse(autoResponseRulesXml);
|
|
803
|
+
let rulesList = autoResponseRulesXmlParsed?.AutoResponseRules?.autoResponseRule || [];
|
|
804
|
+
if (!Array.isArray(rulesList)) {
|
|
805
|
+
rulesList = [rulesList];
|
|
806
|
+
}
|
|
807
|
+
totalRules += rulesList.length;
|
|
808
|
+
}
|
|
809
|
+
if (totalRules === 0) {
|
|
810
|
+
uxLog("log", this, c.yellow("No auto-response rules found in the project"));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
WebSocketClient.sendProgressStartMessage("Generating AutoResponse Rules documentation...", totalRules);
|
|
814
|
+
let counter = 0;
|
|
720
815
|
for (const autoResponseRulesFile of autoResponseRulesFiles) {
|
|
721
816
|
const autoResponseRulesXml = await fs.readFile(autoResponseRulesFile, "utf8");
|
|
722
817
|
const autoResponseRulesXmlParsed = new XMLParser().parse(autoResponseRulesXml);
|
|
@@ -739,8 +834,11 @@ ${Project2Markdown.htmlInstructions}
|
|
|
739
834
|
if (this.withPdf) {
|
|
740
835
|
await generatePdfFileFromMarkdown(mdFile);
|
|
741
836
|
}
|
|
837
|
+
counter++;
|
|
838
|
+
WebSocketClient.sendProgressStepMessage(counter, totalRules);
|
|
742
839
|
}
|
|
743
840
|
}
|
|
841
|
+
WebSocketClient.sendProgressEndMessage();
|
|
744
842
|
this.addNavNode("AutoResponse Rules", autoResponseRulesForMenu);
|
|
745
843
|
// Write index file for permission set groups folder
|
|
746
844
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "autoResponseRules"));
|
|
@@ -748,7 +846,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
748
846
|
await fs.writeFile(psgIndexFile, getMetaHideLines() + DocBuilderAutoResponseRules.buildIndexTable('', this.autoResponseRulesDescriptions).join("\n") + `\n${this.footer}\n`);
|
|
749
847
|
}
|
|
750
848
|
async generateEscalationRulesDocumentation() {
|
|
751
|
-
uxLog("action", this, c.cyan("
|
|
849
|
+
uxLog("action", this, c.cyan("Preparing generation of Escalation Rules documentation... " +
|
|
752
850
|
"(if you don't want it, define GENERATE_AUTOMATIONS_DOC=false in your environment variables)"));
|
|
753
851
|
const escalationRulesForMenu = { "All Escalation Rules": "escalationRules/index.md" };
|
|
754
852
|
const escalationRulesFiles = (await glob("**/escalationRules/**.escalationRules-meta.xml", {
|
|
@@ -757,6 +855,23 @@ ${Project2Markdown.htmlInstructions}
|
|
|
757
855
|
}));
|
|
758
856
|
sortCrossPlatform(escalationRulesFiles);
|
|
759
857
|
const builder = new XMLBuilder();
|
|
858
|
+
// Count total rules for progress tracking
|
|
859
|
+
let totalRules = 0;
|
|
860
|
+
for (const escalationRulesFile of escalationRulesFiles) {
|
|
861
|
+
const escalationRulesXml = await fs.readFile(escalationRulesFile, "utf8");
|
|
862
|
+
const escalationRulesXmlParsed = new XMLParser().parse(escalationRulesXml);
|
|
863
|
+
let rulesList = escalationRulesXmlParsed?.EscalationRules?.escalationRule || [];
|
|
864
|
+
if (!Array.isArray(rulesList)) {
|
|
865
|
+
rulesList = [rulesList];
|
|
866
|
+
}
|
|
867
|
+
totalRules += rulesList.length;
|
|
868
|
+
}
|
|
869
|
+
if (totalRules === 0) {
|
|
870
|
+
uxLog("log", this, c.yellow("No escalation rules found in the project"));
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
WebSocketClient.sendProgressStartMessage("Generating Escalation Rules documentation...", totalRules);
|
|
874
|
+
let counter = 0;
|
|
760
875
|
for (const escalationRulesFile of escalationRulesFiles) {
|
|
761
876
|
const escalationRulesXml = await fs.readFile(escalationRulesFile, "utf8");
|
|
762
877
|
const escalationRulesXmlParsed = new XMLParser().parse(escalationRulesXml);
|
|
@@ -767,6 +882,8 @@ ${Project2Markdown.htmlInstructions}
|
|
|
767
882
|
rulesList = [rulesList];
|
|
768
883
|
}
|
|
769
884
|
for (const rule of rulesList) {
|
|
885
|
+
counter++;
|
|
886
|
+
WebSocketClient.sendProgressStepMessage(counter);
|
|
770
887
|
const currentRuleName = escalationRulesName + "." + rule?.fullName;
|
|
771
888
|
escalationRulesForMenu[currentRuleName] = "escalationRules/" + currentRuleName + ".md";
|
|
772
889
|
const mdFile = path.join(this.outputMarkdownRoot, "escalationRules", currentRuleName + ".md");
|
|
@@ -781,6 +898,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
781
898
|
}
|
|
782
899
|
}
|
|
783
900
|
}
|
|
901
|
+
WebSocketClient.sendProgressEndMessage();
|
|
784
902
|
this.addNavNode("Escalation Rules", escalationRulesForMenu);
|
|
785
903
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "escalationRules"));
|
|
786
904
|
const psgIndexFile = path.join(this.outputMarkdownRoot, "escalationRules", "index.md");
|
|
@@ -924,14 +1042,18 @@ ${Project2Markdown.htmlInstructions}
|
|
|
924
1042
|
uxLog("action", this, c.cyan(`To generate a HTML WebSite with this documentation with a single command, see instructions at ${CONSTANTS.DOC_URL_ROOT}/hardis/doc/project2markdown/`));
|
|
925
1043
|
}
|
|
926
1044
|
async generateObjectsDocumentation() {
|
|
927
|
-
uxLog("action", this, c.cyan("
|
|
1045
|
+
uxLog("action", this, c.cyan("Preparing generation of Objects AI documentation... (if you don't want it, define GENERATE_OBJECTS_DOC=false in your environment variables)"));
|
|
928
1046
|
const objectLinksInfo = await this.generateLinksInfo();
|
|
929
1047
|
const objectsForMenu = { "All objects": "objects/index.md" };
|
|
930
1048
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "objects"));
|
|
1049
|
+
WebSocketClient.sendProgressStartMessage("Generating Objects documentation...", this.objectFiles.length);
|
|
1050
|
+
let counter = 0;
|
|
931
1051
|
for (const objectFile of this.objectFiles) {
|
|
932
1052
|
const objectName = path.basename(objectFile, ".object");
|
|
933
1053
|
if ((objectName.endsWith("__dlm") || objectName.endsWith("__dll")) && !(process.env?.INCLUDE_DATA_CLOUD_DOC === "true")) {
|
|
934
1054
|
uxLog("log", this, c.grey(`Skip Data Cloud Object ${objectName}... (use INCLUDE_DATA_CLOUD_DOC=true to enforce it)`));
|
|
1055
|
+
counter++;
|
|
1056
|
+
WebSocketClient.sendProgressStepMessage(counter, this.objectFiles.length);
|
|
935
1057
|
continue;
|
|
936
1058
|
}
|
|
937
1059
|
uxLog("log", this, c.grey(`Generating markdown for Object ${objectName}...`));
|
|
@@ -989,7 +1111,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
989
1111
|
if (this.withPdf) {
|
|
990
1112
|
await generatePdfFileFromMarkdown(objectMdFile);
|
|
991
1113
|
}
|
|
1114
|
+
counter++;
|
|
1115
|
+
WebSocketClient.sendProgressStepMessage(counter, this.objectFiles.length);
|
|
992
1116
|
}
|
|
1117
|
+
WebSocketClient.sendProgressEndMessage();
|
|
993
1118
|
this.addNavNode("Objects", objectsForMenu);
|
|
994
1119
|
// Write index file for objects folder
|
|
995
1120
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "objects"));
|
|
@@ -1005,7 +1130,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1005
1130
|
await replaceInFile(objectMdFile, '<!-- Attributes tables -->', attributesMarkdown);
|
|
1006
1131
|
}
|
|
1007
1132
|
async generateLinksInfo() {
|
|
1008
|
-
uxLog("
|
|
1133
|
+
uxLog("log", this, c.cyan("Generate MasterDetail and Lookup infos to provide context to AI prompt"));
|
|
1009
1134
|
const findFieldsPattern = `**/objects/**/fields/**.field-meta.xml`;
|
|
1010
1135
|
const matchingFieldFiles = (await glob(findFieldsPattern, { cwd: process.cwd(), ignore: GLOB_IGNORE_PATTERNS })).map(file => file.replace(/\\/g, '/'));
|
|
1011
1136
|
const customFieldsLinks = [];
|
|
@@ -1022,7 +1147,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1022
1147
|
return customFieldsLinks.join("\n") + "\n";
|
|
1023
1148
|
}
|
|
1024
1149
|
async generateFlowsDocumentation() {
|
|
1025
|
-
uxLog("action", this, c.cyan("
|
|
1150
|
+
uxLog("action", this, c.cyan("Preparing generation of Flows Visual documentation... (if you don't want it, define GENERATE_FLOW_DOC=false in your environment variables)"));
|
|
1026
1151
|
const flowsForMenu = { "All flows": "flows/index.md" };
|
|
1027
1152
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "flows"));
|
|
1028
1153
|
const packageDirs = this.project?.getPackageDirectories();
|
|
@@ -1043,7 +1168,13 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1043
1168
|
const extractedNames = [...flowXml.matchAll(regex)].map(match => match[1]);
|
|
1044
1169
|
flowDeps[flowName] = extractedNames;
|
|
1045
1170
|
}
|
|
1171
|
+
if (flowFiles.length === 0) {
|
|
1172
|
+
uxLog("log", this, c.yellow("No flow found in the project"));
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1046
1175
|
// Generate Flows documentation
|
|
1176
|
+
WebSocketClient.sendProgressStartMessage("Generating Flows documentation...", flowFiles.length);
|
|
1177
|
+
let counter = 0;
|
|
1047
1178
|
for (const flowFile of flowFiles) {
|
|
1048
1179
|
const flowName = path.basename(flowFile, ".flow-meta.xml");
|
|
1049
1180
|
const flowXml = (await fs.readFile(flowFile, "utf8")).toString();
|
|
@@ -1059,12 +1190,16 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1059
1190
|
const outputFlowMdFile = path.join(this.outputMarkdownRoot, "flows", flowName + ".md");
|
|
1060
1191
|
if (this.diffOnly && !updatedFlowNames.includes(flowName) && fs.existsSync(outputFlowMdFile)) {
|
|
1061
1192
|
flowSkips.push(flowFile);
|
|
1193
|
+
counter++;
|
|
1194
|
+
WebSocketClient.sendProgressStepMessage(counter, flowFiles.length);
|
|
1062
1195
|
continue;
|
|
1063
1196
|
}
|
|
1064
1197
|
uxLog("log", this, c.grey(`Generating markdown for Flow ${flowFile}...`));
|
|
1065
1198
|
const genRes = await generateFlowMarkdownFile(flowName, flowXml, outputFlowMdFile, { collapsedDetails: false, describeWithAi: true, flowDependencies: flowDeps });
|
|
1066
1199
|
if (!genRes) {
|
|
1067
1200
|
flowErrors.push(flowFile);
|
|
1201
|
+
counter++;
|
|
1202
|
+
WebSocketClient.sendProgressStepMessage(counter, flowFiles.length);
|
|
1068
1203
|
continue;
|
|
1069
1204
|
}
|
|
1070
1205
|
if (this.debugMode) {
|
|
@@ -1073,17 +1208,25 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1073
1208
|
const gen2res = await generateMarkdownFileWithMermaid(outputFlowMdFile, outputFlowMdFile, null, this.withPdf);
|
|
1074
1209
|
if (!gen2res) {
|
|
1075
1210
|
flowWarnings.push(flowFile);
|
|
1211
|
+
counter++;
|
|
1212
|
+
WebSocketClient.sendProgressStepMessage(counter, flowFiles.length);
|
|
1076
1213
|
continue;
|
|
1077
1214
|
}
|
|
1215
|
+
counter++;
|
|
1216
|
+
WebSocketClient.sendProgressStepMessage(counter, flowFiles.length);
|
|
1078
1217
|
}
|
|
1218
|
+
WebSocketClient.sendProgressEndMessage();
|
|
1079
1219
|
this.flowDescriptions = sortArray(this.flowDescriptions, { by: ['object', 'name'], order: ['asc', 'asc'] });
|
|
1080
1220
|
// History
|
|
1081
1221
|
if (this.withHistory) {
|
|
1082
|
-
|
|
1222
|
+
WebSocketClient.sendProgressStartMessage("Generating Flows History documentation...", flowFiles.length);
|
|
1223
|
+
let counter1 = 0;
|
|
1083
1224
|
for (const flowFile of flowFiles) {
|
|
1084
1225
|
const flowName = path.basename(flowFile, ".flow-meta.xml");
|
|
1085
1226
|
const diffMdFile = path.join("docs", "flows", path.basename(flowFile).replace(".flow-meta.xml", "-history.md"));
|
|
1086
1227
|
if (this.diffOnly && !updatedFlowNames.includes(flowName) && fs.existsSync(diffMdFile)) {
|
|
1228
|
+
counter1++;
|
|
1229
|
+
WebSocketClient.sendProgressStepMessage(counter1, flowFiles.length);
|
|
1087
1230
|
continue;
|
|
1088
1231
|
}
|
|
1089
1232
|
try {
|
|
@@ -1092,7 +1235,10 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1092
1235
|
catch (e) {
|
|
1093
1236
|
uxLog("warning", this, c.yellow(`Error generating history diff markdown: ${e.message}`));
|
|
1094
1237
|
}
|
|
1238
|
+
counter1++;
|
|
1239
|
+
WebSocketClient.sendProgressStepMessage(counter1, flowFiles.length);
|
|
1095
1240
|
}
|
|
1241
|
+
WebSocketClient.sendProgressEndMessage();
|
|
1096
1242
|
}
|
|
1097
1243
|
// Summary
|
|
1098
1244
|
if (flowSkips.length > 0) {
|
|
@@ -1217,11 +1363,26 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1217
1363
|
}
|
|
1218
1364
|
}
|
|
1219
1365
|
async generateLwcDocumentation() {
|
|
1220
|
-
uxLog("action", this, c.cyan("
|
|
1366
|
+
uxLog("action", this, c.cyan("Preparing generation of Lightning Web Components documentation... " +
|
|
1221
1367
|
"(if you don't want it, define GENERATE_LWC_DOC=false in your environment variables)"));
|
|
1222
1368
|
const lwcForMenu = { "All Lightning Web Components": "lwc/index.md" };
|
|
1223
1369
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "lwc"));
|
|
1224
1370
|
const packageDirs = this.project?.getPackageDirectories() || [];
|
|
1371
|
+
// Count total LWC components for progress tracking
|
|
1372
|
+
let totalLwcComponents = 0;
|
|
1373
|
+
for (const packageDir of packageDirs) {
|
|
1374
|
+
const lwcMetaFiles = await glob(`${packageDir.path}/**/lwc/**/*.js-meta.xml`, {
|
|
1375
|
+
cwd: process.cwd(),
|
|
1376
|
+
ignore: GLOB_IGNORE_PATTERNS
|
|
1377
|
+
});
|
|
1378
|
+
totalLwcComponents += lwcMetaFiles.length;
|
|
1379
|
+
}
|
|
1380
|
+
if (totalLwcComponents === 0) {
|
|
1381
|
+
uxLog("log", this, c.yellow("No Lightning Web Component found in the project"));
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
WebSocketClient.sendProgressStartMessage("Generating Lightning Web Components documentation...", totalLwcComponents);
|
|
1385
|
+
let counter = 0;
|
|
1225
1386
|
// Find all LWC components in all package directories
|
|
1226
1387
|
for (const packageDir of packageDirs) {
|
|
1227
1388
|
// Find LWC components (directories with .js-meta.xml files)
|
|
@@ -1230,6 +1391,8 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1230
1391
|
ignore: GLOB_IGNORE_PATTERNS
|
|
1231
1392
|
});
|
|
1232
1393
|
for (const lwcMetaFile of lwcMetaFiles) {
|
|
1394
|
+
counter++;
|
|
1395
|
+
WebSocketClient.sendProgressStepMessage(counter);
|
|
1233
1396
|
const lwcDirPath = path.dirname(lwcMetaFile);
|
|
1234
1397
|
const lwcName = path.basename(lwcDirPath);
|
|
1235
1398
|
const mdFile = path.join(this.outputMarkdownRoot, "lwc", lwcName + ".md");
|
|
@@ -1274,6 +1437,7 @@ ${Project2Markdown.htmlInstructions}
|
|
|
1274
1437
|
}
|
|
1275
1438
|
}
|
|
1276
1439
|
}
|
|
1440
|
+
WebSocketClient.sendProgressEndMessage();
|
|
1277
1441
|
this.addNavNode("Lightning Web Components", lwcForMenu);
|
|
1278
1442
|
// Write index file for LWC folder
|
|
1279
1443
|
await fs.ensureDir(path.join(this.outputMarkdownRoot, "lwc"));
|