@salesforce/afv-skills 1.57.0 → 1.59.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 (81) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-bot-upgrade/SKILL.md +3 -1
  3. package/skills/agentforce-generate/SKILL.md +21 -1
  4. package/skills/agentforce-generate/references/known-issues.md +51 -1
  5. package/skills/agentforce-generate/references/legacy-nga-regression-eval.md +547 -0
  6. package/skills/agentforce-generate/references/upgrade-legacy-agent-to-agentscript.md +577 -0
  7. package/skills/agentforce-generate/scripts/upgrade-legacy-nga-analyze.py +404 -0
  8. package/skills/agentforce-generate/scripts/upgrade-legacy-nga-run-trials.sh +58 -0
  9. package/skills/agentforce-persona-generate/SKILL.md +505 -0
  10. package/skills/agentforce-persona-generate/assets/checklist-template.md +100 -0
  11. package/skills/agentforce-persona-generate/assets/persona-encoding-template.md +252 -0
  12. package/skills/agentforce-persona-generate/assets/persona-template.md +229 -0
  13. package/skills/agentforce-persona-generate/assets/sample-dialog-template.md +50 -0
  14. package/skills/agentforce-persona-generate/assets/workshop-outline-template.md +97 -0
  15. package/skills/agentforce-persona-generate/examples/concierge-persona.md +189 -0
  16. package/skills/agentforce-persona-generate/examples/concierge-sample-dialog.md +70 -0
  17. package/skills/agentforce-persona-generate/examples/drover-persona.md +194 -0
  18. package/skills/agentforce-persona-generate/examples/drover-sample-dialog.md +62 -0
  19. package/skills/agentforce-persona-generate/examples/steady-hand-persona.md +188 -0
  20. package/skills/agentforce-persona-generate/examples/steady-hand-sample-dialog.md +68 -0
  21. package/skills/agentforce-persona-generate/examples/yt-persona.md +195 -0
  22. package/skills/agentforce-persona-generate/examples/yt-sample-dialog.md +66 -0
  23. package/skills/agentforce-persona-generate/references/attributions.md +36 -0
  24. package/skills/agentforce-persona-generate/references/interaction-design-notes.md +30 -0
  25. package/skills/agentforce-persona-generate/references/persona-encoding-guide.md +566 -0
  26. package/skills/agentforce-persona-generate/references/persona-framework.md +796 -0
  27. package/skills/agentforce-persona-generate/references/refinement-mappings.md +31 -0
  28. package/skills/agentforce-persona-generate/references/scoring-rubric.md +58 -0
  29. package/skills/agentforce-persona-generate/references/voice-catalog.json +2327 -0
  30. package/skills/agentforce-persona-generate/references/workshop-question-policy.md +90 -0
  31. package/skills/agentforce-persona-generate/references/workshop-scenario-seeds.md +119 -0
  32. package/skills/agentforce-persona-generate/scripts/parse_agent_yaml.py +223 -0
  33. package/skills/dx-app-analytics-query/SKILL.md +8 -8
  34. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +12 -12
  35. package/skills/experience-ui-bundle-app-coordinate/references/phase-completion-validation.md +3 -3
  36. package/skills/experience-ui-bundle-app-coordinate/references/phase-execution-pattern.md +9 -7
  37. package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.mjs +132 -0
  38. package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.mjs +62 -0
  39. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.mjs +71 -0
  40. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.mjs +95 -0
  41. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.mjs +78 -0
  42. package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.mjs +21 -0
  43. package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.mjs +28 -0
  44. package/skills/experience-ui-bundle-custom-app-generate/SKILL.md +3 -1
  45. package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.mjs +40 -0
  46. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +7 -5
  47. package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.mjs +70 -0
  48. package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.mjs +76 -0
  49. package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.mjs +115 -0
  50. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +7 -3
  51. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +1 -1
  52. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +1 -1
  53. package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.mjs +43 -0
  54. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.mjs +131 -0
  55. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.mjs +152 -0
  56. package/skills/experience-ui-bundle-mfa-configure/references/branding.md +10 -6
  57. package/skills/experience-ui-bundle-project-generate/SKILL.md +13 -39
  58. package/skills/experience-ui-bundle-project-generate/scripts/flatten-project.mjs +33 -11
  59. package/skills/experience-ui-bundle-project-generate/scripts/generate-project.mjs +77 -0
  60. package/skills/experience-ui-bundle-project-generate/scripts/install-deps.mjs +95 -0
  61. package/skills/service-omni-channel-setup-coordinate/SKILL.md +1 -0
  62. package/skills/service-omni-command-center-analyze/SKILL.md +1 -0
  63. package/skills/service-omni-command-center-configure/SKILL.md +92 -0
  64. package/skills/service-omni-command-center-configure/references/api-notes.md +50 -0
  65. package/skills/service-omni-command-center-configure/scripts/configure-and-report.sh +257 -0
  66. package/skills/service-omni-command-center-configure/scripts/settings_document.py +97 -0
  67. package/skills/service-omni-command-center-configure/scripts/tests/test_command_center_configure_contracts.py +206 -0
  68. package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.sh +0 -42
  69. package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh +0 -26
  70. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.sh +0 -35
  71. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.sh +0 -47
  72. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.sh +0 -33
  73. package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.sh +0 -20
  74. package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh +0 -15
  75. package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.sh +0 -30
  76. package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.sh +0 -37
  77. package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.sh +0 -66
  78. package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.sh +0 -58
  79. package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh +0 -30
  80. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +0 -73
  81. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +0 -118
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ // Verify no surviving file still imports/references a file deleted during
3
+ // scaffold pruning.
4
+ // Usage: node check-dangling-refs.mjs <deleted-basename> [<deleted-basename> ...]
5
+ // <deleted-basename> is the file name without extension, e.g. "codegen" for
6
+ // codegen.yml, "graphqlClient" for src/api/graphqlClient.ts, "useAsyncData"
7
+ // for the deleted hook.
8
+ //
9
+ // Always invoke via `node` (never as a bare executable) so this works on
10
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash. Quote each
11
+ // <deleted-basename> argument.
12
+ //
13
+ // Exit 0 if no dangling references found, exit 1 with error message(s) if
14
+ // any are found.
15
+
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+
19
+ const EXCLUDE_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
20
+ const UIBUNDLES_DIR = path.join("force-app", "main", "default", "uiBundles");
21
+
22
+ // Filesystem read errors encountered while walking the bundle or reading a
23
+ // file. A dangling-reference scan that could not read part of the tree has NOT
24
+ // proven the reference is gone, so any entry here must turn a would-be SUCCESS
25
+ // into a non-zero exit (see the end of the script).
26
+ const ioErrors = [];
27
+
28
+ function findBundleDir() {
29
+ let entries;
30
+ try {
31
+ entries = fs.readdirSync(UIBUNDLES_DIR, { withFileTypes: true });
32
+ } catch {
33
+ // uiBundles dir absent/unreadable: return "" so the caller emits an explicit
34
+ // "No UI bundle directory found" error and exits non-zero. This is a handled,
35
+ // expected case (e.g. a fresh project), not a silently-swallowed failure.
36
+ return "";
37
+ }
38
+ const dirs = entries
39
+ .filter((e) => e.isDirectory())
40
+ .map((e) => e.name)
41
+ .sort();
42
+ return dirs.length > 0 ? path.join(UIBUNDLES_DIR, dirs[0]) : "";
43
+ }
44
+
45
+ function collectFiles(dir, out) {
46
+ let entries;
47
+ try {
48
+ entries = fs.readdirSync(dir, { withFileTypes: true });
49
+ } catch (err) {
50
+ // A subdirectory we cannot list may hide a surviving reference. Record it so
51
+ // the run fails rather than reporting an untrustworthy SUCCESS.
52
+ ioErrors.push(`could not read directory '${dir}': ${err.message}`);
53
+ return;
54
+ }
55
+ for (const entry of entries) {
56
+ const full = path.join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ if (EXCLUDE_DIRS.has(entry.name)) continue;
59
+ collectFiles(full, out);
60
+ } else if (entry.isFile()) {
61
+ out.push(full);
62
+ }
63
+ }
64
+ }
65
+
66
+ const basenames = process.argv.slice(2);
67
+
68
+ if (basenames.length === 0) {
69
+ console.error(
70
+ "ERROR: check-dangling-refs.mjs requires at least one deleted-file basename as an argument",
71
+ );
72
+ process.exit(1);
73
+ }
74
+
75
+ const bundleDir = findBundleDir();
76
+
77
+ if (!bundleDir) {
78
+ console.error(`ERROR: No UI bundle directory found in ${UIBUNDLES_DIR}${path.sep}`);
79
+ process.exit(1);
80
+ }
81
+
82
+ const files = [];
83
+ collectFiles(bundleDir, files);
84
+
85
+ let foundDangling = false;
86
+
87
+ for (const basename of basenames) {
88
+ const matches = [];
89
+ for (const file of files) {
90
+ let content;
91
+ try {
92
+ content = fs.readFileSync(file, "utf8");
93
+ } catch (err) {
94
+ // Could not read a surviving file — record and skip. We cannot confirm it
95
+ // is free of the deleted reference, so the run must not report SUCCESS.
96
+ ioErrors.push(`could not read file '${file}': ${err.message}`);
97
+ continue;
98
+ }
99
+ if (content.includes(basename)) {
100
+ matches.push(file);
101
+ }
102
+ }
103
+
104
+ if (matches.length > 0) {
105
+ foundDangling = true;
106
+ console.error(`ERROR: Dangling reference to deleted file '${basename}' found in:`);
107
+ for (const match of matches) {
108
+ console.error(` - ${match}`);
109
+ }
110
+ }
111
+ }
112
+
113
+ if (foundDangling) {
114
+ process.exit(1);
115
+ }
116
+
117
+ // A read failure means the scan was incomplete — do not claim SUCCESS.
118
+ if (ioErrors.length > 0) {
119
+ console.error(
120
+ "ERROR: dangling-reference scan is incomplete — some paths could not be read:",
121
+ );
122
+ for (const e of ioErrors) {
123
+ console.error(` - ${e}`);
124
+ }
125
+ console.error(
126
+ "Resolve the read errors above and re-run; not reporting SUCCESS on an incomplete scan.",
127
+ );
128
+ process.exit(1);
129
+ }
130
+
131
+ console.log(`SUCCESS: No dangling references found for: ${basenames.join(" ")}`);
132
+ process.exit(0);
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ // Check which hosting target is configured in .uibundle-meta.xml files.
3
+ // Usage: node check-hosting-target.mjs
4
+ //
5
+ // Always invoke via `node` (never as a bare executable) so this works on
6
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
7
+ //
8
+ // Prints "ExperienceSite" or "CustomApplication" to stdout and exits 0 on
9
+ // success. Exits 1 with an error message if no .uibundle-meta.xml files
10
+ // are found, or none declare a valid target.
11
+
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+
15
+ const EXCLUDE_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
16
+
17
+ function findMetaFiles(dir, out) {
18
+ let entries;
19
+ try {
20
+ entries = fs.readdirSync(dir, { withFileTypes: true });
21
+ } catch {
22
+ return;
23
+ }
24
+ for (const entry of entries) {
25
+ const full = path.join(dir, entry.name);
26
+ if (entry.isDirectory()) {
27
+ if (EXCLUDE_DIRS.has(entry.name)) continue;
28
+ findMetaFiles(full, out);
29
+ } else if (entry.isFile() && entry.name.endsWith(".uibundle-meta.xml")) {
30
+ out.push(full);
31
+ }
32
+ }
33
+ }
34
+
35
+ const metaFiles = [];
36
+ findMetaFiles(".", metaFiles);
37
+
38
+ if (metaFiles.length === 0) {
39
+ console.error("ERROR: No .uibundle-meta.xml files found");
40
+ process.exit(1);
41
+ }
42
+
43
+ const contents = metaFiles.map((f) => {
44
+ try {
45
+ return fs.readFileSync(f, "utf8");
46
+ } catch {
47
+ return "";
48
+ }
49
+ });
50
+
51
+ if (contents.some((c) => c.includes("<target>ExperienceSite</target>"))) {
52
+ console.log("ExperienceSite");
53
+ process.exit(0);
54
+ }
55
+
56
+ if (contents.some((c) => c.includes("<target>CustomApplication</target>"))) {
57
+ console.log("CustomApplication");
58
+ process.exit(0);
59
+ }
60
+
61
+ console.error("ERROR: No valid hosting target found in .uibundle-meta.xml");
62
+ process.exit(1);
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ // Verify Phase 1 (Scaffolding) completed successfully.
3
+ // Usage: node check-phase-1-complete.mjs
4
+ //
5
+ // Always invoke via `node` (never as a bare executable) so this works on
6
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
7
+ //
8
+ // Exit 0 if complete, exit 1 with an error message if incomplete.
9
+
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+
13
+ const UIBUNDLES_DIR = path.join("force-app", "main", "default", "uiBundles");
14
+
15
+ function findBundleDir() {
16
+ let entries;
17
+ try {
18
+ entries = fs.readdirSync(UIBUNDLES_DIR, { withFileTypes: true });
19
+ } catch {
20
+ return "";
21
+ }
22
+ const dirs = entries
23
+ .filter((e) => e.isDirectory())
24
+ .map((e) => e.name)
25
+ .sort();
26
+ return dirs.length > 0 ? path.join(UIBUNDLES_DIR, dirs[0]) : "";
27
+ }
28
+
29
+ function fail(message) {
30
+ console.error(message);
31
+ process.exit(1);
32
+ }
33
+
34
+ const bundleDir = findBundleDir();
35
+
36
+ if (!bundleDir) {
37
+ fail(
38
+ `ERROR: Phase 1 incomplete: No UI bundle directory found in ${UIBUNDLES_DIR}${path.sep}`,
39
+ );
40
+ }
41
+
42
+ if (!fs.existsSync(path.join(bundleDir, "src"))) {
43
+ fail(`ERROR: Phase 1 incomplete: No src/ directory found in ${bundleDir}`);
44
+ }
45
+
46
+ const metaFiles = fs
47
+ .readdirSync(bundleDir)
48
+ .filter((name) => name.endsWith(".uibundle-meta.xml"));
49
+
50
+ if (metaFiles.length === 0) {
51
+ fail(`ERROR: Phase 1 incomplete: No .uibundle-meta.xml file found in ${bundleDir}`);
52
+ }
53
+
54
+ const hasTarget = metaFiles.some((name) => {
55
+ try {
56
+ return fs.readFileSync(path.join(bundleDir, name), "utf8").includes("<target>");
57
+ } catch {
58
+ return false;
59
+ }
60
+ });
61
+
62
+ if (!hasTarget) {
63
+ fail("ERROR: Phase 1 incomplete: .uibundle-meta.xml missing <target> element");
64
+ }
65
+
66
+ if (!fs.existsSync(path.join(bundleDir, "package.json"))) {
67
+ fail(`ERROR: Phase 1 incomplete: No package.json found in ${bundleDir}`);
68
+ }
69
+
70
+ console.log("SUCCESS: Phase 1 complete: Scaffold verified");
71
+ process.exit(0);
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ // Verify Phase 4 (UI) completed successfully.
3
+ // Usage: node check-phase-4-complete.mjs
4
+ //
5
+ // Always invoke via `node` (never as a bare executable) so this works on
6
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
7
+ //
8
+ // Exit 0 if complete, exit 1 with an error message if incomplete.
9
+
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+
13
+ const UIBUNDLES_DIR = path.join("force-app", "main", "default", "uiBundles");
14
+
15
+ function findBundleDir() {
16
+ let entries;
17
+ try {
18
+ entries = fs.readdirSync(UIBUNDLES_DIR, { withFileTypes: true });
19
+ } catch {
20
+ return "";
21
+ }
22
+ const dirs = entries
23
+ .filter((e) => e.isDirectory())
24
+ .map((e) => e.name)
25
+ .sort();
26
+ return dirs.length > 0 ? path.join(UIBUNDLES_DIR, dirs[0]) : "";
27
+ }
28
+
29
+ function fail(message) {
30
+ console.error(message);
31
+ process.exit(1);
32
+ }
33
+
34
+ function findTsxFiles(dir) {
35
+ let entries;
36
+ try {
37
+ entries = fs.readdirSync(dir, { withFileTypes: true });
38
+ } catch {
39
+ return [];
40
+ }
41
+ const out = [];
42
+ for (const entry of entries) {
43
+ const full = path.join(dir, entry.name);
44
+ if (entry.isDirectory()) {
45
+ out.push(...findTsxFiles(full));
46
+ } else if (entry.isFile() && entry.name.endsWith(".tsx")) {
47
+ out.push(full);
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ const bundleDir = findBundleDir();
54
+
55
+ if (!bundleDir) {
56
+ fail("ERROR: Phase 4 cannot run: No UI bundle directory found (Phase 1 not complete)");
57
+ }
58
+
59
+ // Check Phase 1 scaffold prerequisites
60
+ const packageJsonPath = path.join(bundleDir, "package.json");
61
+ if (!fs.existsSync(packageJsonPath)) {
62
+ fail("ERROR: Phase 4 cannot run: No package.json found (Phase 1 not complete)");
63
+ }
64
+
65
+ let packageJsonContent = "";
66
+ try {
67
+ packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
68
+ } catch {
69
+ packageJsonContent = "";
70
+ }
71
+
72
+ if (!packageJsonContent.includes('"react"')) {
73
+ fail("ERROR: Phase 4 cannot run: React dependencies not installed in package.json");
74
+ }
75
+
76
+ // Check Phase 4 completion
77
+ if (!fs.existsSync(path.join(bundleDir, "src", "App.tsx"))) {
78
+ fail(`ERROR: Phase 4 incomplete: No App.tsx found in ${path.join(bundleDir, "src")}${path.sep}`);
79
+ }
80
+
81
+ const pagesDir = path.join(bundleDir, "src", "pages");
82
+ if (!fs.existsSync(pagesDir) || !fs.statSync(pagesDir).isDirectory()) {
83
+ fail(`ERROR: Phase 4 incomplete: No pages/ directory found in ${path.join(bundleDir, "src")}${path.sep}`);
84
+ }
85
+
86
+ if (findTsxFiles(pagesDir).length === 0) {
87
+ fail("ERROR: Phase 4 incomplete: No .tsx files found in pages/ directory");
88
+ }
89
+
90
+ if (!fs.existsSync(path.join(bundleDir, "dist", "index.html"))) {
91
+ fail("ERROR: Phase 4 incomplete: No dist/index.html found (build not complete)");
92
+ }
93
+
94
+ console.log("SUCCESS: Phase 4 complete: UI verified");
95
+ process.exit(0);
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ // Verify Phase 6 (Deployment) prerequisites are met.
3
+ // Usage: node check-phase-6-ready.mjs
4
+ //
5
+ // Always invoke via `node` (never as a bare executable) so this works on
6
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
7
+ //
8
+ // Exit 0 if ready, exit 1 with an error message if not ready.
9
+
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { spawnSync } from "node:child_process";
13
+
14
+ const UIBUNDLES_DIR = path.join("force-app", "main", "default", "uiBundles");
15
+
16
+ function findBundleDir() {
17
+ let entries;
18
+ try {
19
+ entries = fs.readdirSync(UIBUNDLES_DIR, { withFileTypes: true });
20
+ } catch {
21
+ return "";
22
+ }
23
+ const dirs = entries
24
+ .filter((e) => e.isDirectory())
25
+ .map((e) => e.name)
26
+ .sort();
27
+ return dirs.length > 0 ? path.join(UIBUNDLES_DIR, dirs[0]) : "";
28
+ }
29
+
30
+ function fail(message) {
31
+ console.error(message);
32
+ process.exit(1);
33
+ }
34
+
35
+ const bundleDir = findBundleDir();
36
+
37
+ if (!bundleDir) {
38
+ fail("ERROR: Phase 6 cannot run: No UI bundle directory found (Phase 1 not complete)");
39
+ }
40
+
41
+ // Check build artifact exists
42
+ if (!fs.existsSync(path.join(bundleDir, "dist", "index.html"))) {
43
+ fail("ERROR: Phase 6 cannot run: No dist/index.html found (Phase 4 build not complete)");
44
+ }
45
+
46
+ // Check org authentication
47
+ // `shell: true` resolves `sf`/`sf.cmd` across macOS/Linux/Git Bash and native
48
+ // Windows shells alike.
49
+ const orgDisplay = spawnSync("sf", ["org", "display", "--json"], {
50
+ shell: true,
51
+ stdio: "ignore",
52
+ });
53
+
54
+ if (orgDisplay.status !== 0) {
55
+ fail("ERROR: Phase 6 cannot run: No authenticated org found (run: sf org login web)");
56
+ }
57
+
58
+ // Check hosting target is set
59
+ const metaFiles = fs
60
+ .readdirSync(bundleDir)
61
+ .filter((name) => name.endsWith(".uibundle-meta.xml"));
62
+
63
+ const hasTarget = metaFiles.some((name) => {
64
+ try {
65
+ return fs.readFileSync(path.join(bundleDir, name), "utf8").includes("<target>");
66
+ } catch {
67
+ return false;
68
+ }
69
+ });
70
+
71
+ if (!hasTarget) {
72
+ fail(
73
+ "ERROR: Phase 6 cannot run: .uibundle-meta.xml missing <target> element (Phase 1 not complete)",
74
+ );
75
+ }
76
+
77
+ console.log("SUCCESS: Phase 6 ready: All deployment prerequisites met");
78
+ process.exit(0);
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ // Check prerequisites for UI Bundle app building.
3
+ // Usage: node check-prerequisites.mjs
4
+ //
5
+ // Always invoke via `node` (never as a bare executable) so this works on
6
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
7
+ //
8
+ // Exit 0 if all prerequisites are met, exit 1 with an error message if any
9
+ // are missing.
10
+
11
+ const [major] = process.versions.node.split(".").map(Number);
12
+
13
+ if (!Number.isFinite(major) || major < 20) {
14
+ console.error(
15
+ `ERROR: Node.js version ${process.versions.node} detected. Requires >= 20.x`,
16
+ );
17
+ process.exit(1);
18
+ }
19
+
20
+ console.log(`SUCCESS: Node.js v${process.versions.node} meets the >= 20.x requirement`);
21
+ process.exit(0);
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // Check if sfdx-project.json exists and is valid JSON.
3
+ // Usage: node check-sfdx-project.mjs
4
+ //
5
+ // Always invoke via `node` (never as a bare executable) so this works on
6
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
7
+ //
8
+ // Exit 0 if sfdx-project.json exists and parses as JSON, exit 1 with an
9
+ // error message otherwise.
10
+
11
+ import fs from "node:fs";
12
+
13
+ const SFDX_PROJECT = "sfdx-project.json";
14
+
15
+ if (!fs.existsSync(SFDX_PROJECT)) {
16
+ console.error("ERROR: sfdx-project.json not found");
17
+ process.exit(1);
18
+ }
19
+
20
+ try {
21
+ JSON.parse(fs.readFileSync(SFDX_PROJECT, "utf8"));
22
+ } catch {
23
+ console.error("ERROR: sfdx-project.json is not valid JSON");
24
+ process.exit(1);
25
+ }
26
+
27
+ console.log("sfdx-project.json is valid");
28
+ process.exit(0);
@@ -8,6 +8,8 @@ metadata:
8
8
  relatedSkills:
9
9
  - "platform-custom-application-generate"
10
10
  cliTools:
11
+ - tool: ["node"]
12
+ semver: ">=18.0.0"
11
13
  - tool: ["sf"]
12
14
  semver: ">=2.0.0"
13
15
  mcpTools:
@@ -76,7 +78,7 @@ Use the default template in the doc below. Values in `{braces}` are resolved pro
76
78
  ### Step 5: Update UI Bundle Meta XML
77
79
  This step edits an **existing** file — it never creates a new one. `UIBundle` is a bundle-style metadata type: its meta XML normally lives inside its own folder at `uiBundles/{appName}/{appName}.uibundle-meta.xml`, alongside the bundle's other source files (e.g. `index.html`, `src/`). Some older or hand-authored projects may instead have it at the flat path `uiBundles/{appName}.uibundle-meta.xml` (no subfolder).
78
80
 
79
- Run `scripts/resolve-uibundle-path.sh "{appName}"` — it prints the path of the existing meta XML (preferring the nested layout, falling back to the flat layout). Edit that exact file in place; do not create a second file at the other path or migrate it to a different layout as part of this skill.
81
+ Run `node scripts/resolve-uibundle-path.mjs "{appName}"` — it prints the path of the existing meta XML (preferring the nested layout, falling back to the flat layout). Always invoke via `node` (never as a bare executable) so this works on Windows as well as macOS/Linux. Edit that exact file in place; do not create a second file at the other path or migrate it to a different layout as part of this skill.
80
82
 
81
83
  If Step 2 confirmed the `target` field exists on `UIBundle`, add `<target>CustomApplication</target>` inside the existing `<UIBundle>` element of that file, preserving every other existing element and value untouched (skip this step entirely if the field doesn't exist in the org's API version — do not create the file or add `<target>` in that case).
82
84
 
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ // Resolve the existing path of a UIBundle's meta XML file.
3
+ //
4
+ // UIBundle metadata may live at either the nested bundle-style path
5
+ // (uiBundles/{appName}/{appName}.uibundle-meta.xml) or, in older/hand-authored
6
+ // projects, the flat path (uiBundles/{appName}.uibundle-meta.xml). This script
7
+ // performs the deterministic file-existence check and prints whichever path
8
+ // already exists, preferring the nested layout.
9
+ //
10
+ // Always invoke via `node` (never as a bare executable) so this works on
11
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
12
+ //
13
+ // Usage: node scripts/resolve-uibundle-path.mjs <appName>
14
+ //
15
+ // Exit 0: existing meta XML found — its path is printed to stdout.
16
+ // Exit 1: appName argument missing, or no .uibundle-meta.xml found for it.
17
+
18
+ import fs from "node:fs";
19
+ import path from "node:path";
20
+
21
+ const APP = process.argv[2] ?? "";
22
+
23
+ if (!APP) {
24
+ console.error("ERROR: appName argument is required");
25
+ process.exit(1);
26
+ }
27
+
28
+ const NESTED = path.join("uiBundles", APP, `${APP}.uibundle-meta.xml`);
29
+ const FLAT = path.join("uiBundles", `${APP}.uibundle-meta.xml`);
30
+
31
+ if (fs.existsSync(NESTED) && fs.statSync(NESTED).isFile()) {
32
+ console.log(NESTED);
33
+ process.exit(0);
34
+ } else if (fs.existsSync(FLAT) && fs.statSync(FLAT).isFile()) {
35
+ console.log(FLAT);
36
+ process.exit(0);
37
+ } else {
38
+ console.error(`ERROR: no .uibundle-meta.xml found for ${APP}`);
39
+ process.exit(1);
40
+ }
@@ -10,6 +10,8 @@ metadata:
10
10
  - "experience-ui-bundle-metadata-generate"
11
11
  - "experience-ui-bundle-salesforce-data-access"
12
12
  cliTools:
13
+ - tool: ["node"]
14
+ semver: ">=18.0.0"
13
15
  - tool: ["npm"]
14
16
  semver: ">=8.0.0"
15
17
  - tool: ["npx"]
@@ -22,7 +24,7 @@ metadata:
22
24
 
23
25
  ## Resolve the Bundle Directory
24
26
 
25
- **MUST** run `scripts/resolve-ui-bundle.sh [project-root]` before applying any rule below or writing any file — an ad-hoc `find`/`ls` is not a substitute; it does not enforce the exit-code gate below. It reads `sfdx-project.json`'s `packageDirectories[0].path` (does not assume `force-app` — the source path is configurable) and looks under `<sourceDir>/main/default/uiBundles/`:
27
+ **MUST** run `node scripts/resolve-ui-bundle.mjs [project-root]` before applying any rule below or writing any file — an ad-hoc `find`/`ls` is not a substitute; it does not enforce the exit-code gate below. Always invoke via `node` (never as a bare executable) so this works on Windows as well as macOS/Linux. It reads `sfdx-project.json`'s `packageDirectories[0].path` (does not assume `force-app` — the source path is configurable) and looks under `<sourceDir>/main/default/uiBundles/`:
26
28
 
27
29
  - **Exit 0**, bundle path printed to stdout: exactly one bundle directory found — that is the bundle directory. Use that exact directory name; never substitute a different name (e.g. a generic "AcmePortal" example from a prompt template) for the one actually printed.
28
30
  - **Exit 2**, candidates printed to stderr: multiple `uiBundles/*` subdirectories exist — do not guess, and do not write to any of them, or to a bundle name not in the printed list. Ask the user which app/bundle they mean before editing or running any command.
@@ -32,10 +34,10 @@ Run all `npm`/lint/build/dev commands from inside the resolved bundle directory,
32
34
 
33
35
  ## Preconditions
34
36
 
35
- Before applying any rule below, confirm this is an existing, scaffolded UI bundle: **MUST** run `scripts/check-preconditions.sh <bundle-dir>` with the directory `resolve-ui-bundle.sh` printed.
37
+ Before applying any rule below, confirm this is an existing, scaffolded UI bundle: **MUST** run `node scripts/check-preconditions.mjs <bundle-dir>` with the directory `resolve-ui-bundle.mjs` printed.
36
38
 
37
39
  - **Single-bundle (exit 0) case**: run it once, on that bundle.
38
- - **Multi-bundle (exit 2) case**: do not run it yet — first ask the user which app/bundle they mean, per the Resolve step above. Once the user names the bundle, run `check-preconditions.sh` on that one bundle only. Never run it against multiple candidates speculatively before the user has chosen — that means touching/inspecting bundles the user didn't ask about.
40
+ - **Multi-bundle (exit 2) case**: do not run it yet — first ask the user which app/bundle they mean, per the Resolve step above. Once the user names the bundle, run `check-preconditions.mjs` on that one bundle only. Never run it against multiple candidates speculatively before the user has chosen — that means touching/inspecting bundles the user didn't ask about.
39
41
  - **Exit 0**: the bundle has `src/appLayout.tsx`, `src/routes.tsx`, and `src/components/ui/` — proceed.
40
42
  - **Exit 1**, missing pieces listed: this is a fresh SFDX project, a non-UI-bundle React project, or a partially-scaffolded bundle — **stop**. Do not fall back to generic React knowledge (e.g. `react-router-dom`, a hardcoded basename, or raw HTML), and do not hand-write `appLayout.tsx`/`routes.tsx`/a page/a component to "fill in" the missing scaffold, even for a casual, vague, or urgent-sounding request ("just change the header", "make the background blue"). Tell the user the bundle isn't scaffolded yet and direct them to `experience-ui-bundle-app-coordinate` (or `experience-ui-bundle-metadata-generate`) to scaffold it first. If, after the user names their intended bundle, that one turns out to be unscaffolded, stop and redirect for it — do not silently switch to scaffolding a different candidate instead.
41
43
 
@@ -80,7 +82,7 @@ When making any change that affects navigation, header, footer, sidebar, theme,
80
82
 
81
83
  `index.html` lives at the bundle root (not under `src/`), but it is still in scope for this skill whenever branding or the app name changes — the leftover `<title>React App</title>` / `Vite + React` boilerplate is a common ship-blocker that `npm run lint`/`npm run build` never catches.
82
84
 
83
- Before finishing, confirm: Did I update `appLayout.tsx` with real nav items and branding? Then run `scripts/verify-rules.sh` to check for residual boilerplate (see Verification below).
85
+ Before finishing, confirm: Did I update `appLayout.tsx` with real nav items and branding? Then run `node scripts/verify-rules.mjs` to check for residual boilerplate (see Verification below).
84
86
 
85
87
  | What | Where |
86
88
  |------|-------|
@@ -191,7 +193,7 @@ Before completing, run all of the following from the resolved UI bundle director
191
193
  2. `npm run build` — must succeed.
192
194
  3. `npm run dev` (or the project's dev-server script) — confirm the app starts cleanly so the change is verified at runtime, not just at build time.
193
195
 
194
- **`lint`/`build` alone do not catch the highest-risk rules in this skill** — a wrong `react-router-dom` import, a hardcoded basename, an inline `style={{}}`, or a stray `lightning/*` import can all lint and build clean while breaking at runtime. After any change that touches routing, layout, styling, or module imports, run `scripts/verify-rules.sh <files-or-dirs-you-edited>`:
196
+ **`lint`/`build` alone do not catch the highest-risk rules in this skill** — a wrong `react-router-dom` import, a hardcoded basename, an inline `style={{}}`, or a stray `lightning/*` import can all lint and build clean while breaking at runtime. After any change that touches routing, layout, styling, or module imports, run `node scripts/verify-rules.mjs <files-or-dirs-you-edited>`:
195
197
 
196
198
  - **Exit 0**: no violations found.
197
199
  - **Exit 1**, violations listed by rule and file: fix every one before considering the task complete, even if lint and build passed.
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ // Verify a resolved UI bundle directory is actually scaffolded before any
3
+ // rule in this skill applies.
4
+ // Usage: node check-preconditions.mjs <bundle-dir>
5
+ // <bundle-dir> is the path printed by resolve-ui-bundle.mjs.
6
+ //
7
+ // Always invoke via `node` (never as a bare executable) so this works on
8
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash. Quote
9
+ // <bundle-dir> so Windows paths and spaces survive the shell.
10
+ //
11
+ // Exit 0 if the bundle is scaffolded (src/appLayout.tsx, src/routes.tsx, and
12
+ // src/components/ui/ all present).
13
+ // Exit 1 with the missing pieces listed if the bundle is a fresh SFDX
14
+ // project, a non-UI-bundle React project, or only partially scaffolded —
15
+ // the caller must stop and redirect the user to
16
+ // experience-ui-bundle-app-coordinate (or experience-ui-bundle-metadata-generate)
17
+ // instead of falling back to generic React knowledge.
18
+
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+
22
+ const BUNDLE_DIR = process.argv[2] ?? "";
23
+
24
+ function fail(message) {
25
+ console.error(message);
26
+ process.exit(1);
27
+ }
28
+
29
+ if (!BUNDLE_DIR) {
30
+ fail("ERROR: check-preconditions.mjs requires a valid bundle directory argument");
31
+ }
32
+
33
+ let isDir = false;
34
+ try {
35
+ isDir = fs.statSync(BUNDLE_DIR).isDirectory();
36
+ } catch {
37
+ isDir = false;
38
+ }
39
+
40
+ if (!isDir) {
41
+ fail("ERROR: check-preconditions.mjs requires a valid bundle directory argument");
42
+ }
43
+
44
+ const missing = [];
45
+ if (!fs.existsSync(path.join(BUNDLE_DIR, "src", "appLayout.tsx"))) {
46
+ missing.push("src/appLayout.tsx");
47
+ }
48
+ if (!fs.existsSync(path.join(BUNDLE_DIR, "src", "routes.tsx"))) {
49
+ missing.push("src/routes.tsx");
50
+ }
51
+ const uiDir = path.join(BUNDLE_DIR, "src", "components", "ui");
52
+ if (!fs.existsSync(uiDir) || !fs.statSync(uiDir).isDirectory()) {
53
+ missing.push("src/components/ui/");
54
+ }
55
+
56
+ if (missing.length > 0) {
57
+ console.error(`ERROR: ${BUNDLE_DIR} is not a scaffolded UI bundle. Missing:`);
58
+ for (const item of missing) {
59
+ console.error(` - ${item}`);
60
+ }
61
+ console.error(
62
+ "Stop here. Tell the user the bundle isn't scaffolded yet and direct them to experience-ui-bundle-app-coordinate (or experience-ui-bundle-metadata-generate) to scaffold it first.",
63
+ );
64
+ process.exit(1);
65
+ }
66
+
67
+ console.log(
68
+ `SUCCESS: ${BUNDLE_DIR} is scaffolded (src/appLayout.tsx, src/routes.tsx, src/components/ui/ present)`,
69
+ );
70
+ process.exit(0);