-
Notifications
You must be signed in to change notification settings - Fork 153
Expand file tree
/
Copy pathpowerpages-hook-utils.js
More file actions
146 lines (122 loc) · 4.28 KB
/
Copy pathpowerpages-hook-utils.js
File metadata and controls
146 lines (122 loc) · 4.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
const fs = require('fs');
const path = require('path');
const PLUGIN_ROOT = path.resolve(__dirname, '..', '..');
const SKILLS_DIR = path.join(PLUGIN_ROOT, 'skills');
// Skills that must never emit usage telemetry about themselves. The telemetry
// control skill is excluded so checking/toggling telemetry does not self-emit.
const EXCLUDED_FROM_TRACKING = new Set(['telemetry']);
function discoverValidatorScript(skillName) {
const scriptsDir = path.join(SKILLS_DIR, skillName, 'scripts');
if (!fs.existsSync(scriptsDir)) {
return null;
}
const validators = fs
.readdirSync(scriptsDir, { withFileTypes: true })
.filter((entry) => entry.isFile() && /^validate.*\.js$/.test(entry.name))
.map((entry) => entry.name)
.sort();
if (validators.length === 0) {
return null;
}
return path.posix.join('skills', skillName, 'scripts', validators[0]);
}
function discoverTrackedSkills() {
// Null-prototype map: membership is tested via bracket access (TRACKED_SKILLS[name]),
// so a plain {} would make inherited keys like "toString"/"constructor"/"__proto__"
// test truthy and emit bogus skill names. A null-proto object has no such keys.
const trackedSkills = Object.create(null);
const entries = fs
.readdirSync(SKILLS_DIR, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.sort((a, b) => a.name.localeCompare(b.name));
for (const entry of entries) {
const skillName = entry.name;
if (EXCLUDED_FROM_TRACKING.has(skillName)) {
continue;
}
if (!fs.existsSync(path.join(SKILLS_DIR, skillName, 'SKILL.md'))) {
continue;
}
const validatorScript = discoverValidatorScript(skillName);
trackedSkills[skillName] = validatorScript ? { validatorScript } : {};
}
return trackedSkills;
}
const TRACKED_SKILLS = discoverTrackedSkills();
function detectTrackedSkill(value) {
if (typeof value !== 'string') {
return null;
}
const trimmed = value.trim();
if (TRACKED_SKILLS[trimmed]) {
return trimmed;
}
// Strip leading slash and optional plugin prefix: /create-site, /power-pages:create-site
const normalized = trimmed.replace(/^\/?(?:power-pages:)?/, '').toLowerCase();
if (TRACKED_SKILLS[normalized]) {
return normalized;
}
// Fall back to searching for power-pages:<skill> anywhere in the string
const commandMatch = trimmed.match(/power-pages:([a-z0-9-]+)/i);
if (!commandMatch) {
return null;
}
const skillName = commandMatch[1].toLowerCase();
return TRACKED_SKILLS[skillName] ? skillName : null;
}
function getTrackedSkillFromToolInput(toolInput) {
if (!toolInput || typeof toolInput !== 'object') {
return null;
}
for (const field of ['skill', 'skill_name', 'skillName', 'name', 'commandName', 'command']) {
const skillName = detectTrackedSkill(toolInput[field]);
if (skillName) {
return skillName;
}
}
try {
return detectTrackedSkill(JSON.stringify(toolInput));
} catch {
return null;
}
}
function getValidatorScript(skillName) {
return TRACKED_SKILLS[skillName]?.validatorScript ?? null;
}
// Skills that write a `docs/alm/last-*.json` marker or otherwise consume the ALM
// plan. After any of these completes, the PostToolUse hook runs a plan reconcile
// (auto-heal) — so a refresh step skipped by ONE skill is caught when the NEXT
// ALM skill completes (covers manual/cross-session execution).
const ALM_PLAN_SKILLS = new Set([
'setup-solution',
'setup-pipeline',
'deploy-pipeline',
'export-solution',
'import-solution',
'configure-env-variables',
'activate-site',
'test-site',
'ensure-pipelines-host',
'force-link-environment',
]);
/**
* True when `value` (a raw skill name, `/skill`, or `power-pages:skill`) resolves
* to an ALM plan skill. Normalizes via `detectTrackedSkill`, so it also confirms
* the skill actually exists in this plugin.
* Accepts any value — non-strings (including null/undefined) resolve to false
* via detectTrackedSkill, so callers may pass an unvalidated skill name.
* @param {*} value
* @returns {boolean}
*/
function isAlmPlanSkill(value) {
const name = detectTrackedSkill(value);
return name != null && ALM_PLAN_SKILLS.has(name);
}
module.exports = {
TRACKED_SKILLS,
ALM_PLAN_SKILLS,
detectTrackedSkill,
getTrackedSkillFromToolInput,
getValidatorScript,
isAlmPlanSkill,
};