@forwardimpact/pathway 0.25.0 → 0.25.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/bin/fit-pathway.js +18 -47
- package/package.json +4 -3
- package/src/commands/update.js +8 -4
- package/src/components/skill-matrix.js +6 -2
- package/src/lib/radar.js +19 -5
- package/src/slide-main.js +16 -2
package/bin/fit-pathway.js
CHANGED
|
@@ -30,11 +30,13 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { join, resolve, dirname } from "path";
|
|
33
|
-
import { existsSync } from "fs";
|
|
34
|
-
import { homedir } from "os";
|
|
35
33
|
import { fileURLToPath } from "url";
|
|
34
|
+
import fs from "fs/promises";
|
|
35
|
+
import { homedir } from "os";
|
|
36
36
|
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
37
37
|
import { validateAllData } from "@forwardimpact/map/validation";
|
|
38
|
+
import { Finder } from "@forwardimpact/libutil";
|
|
39
|
+
import { createLogger } from "@forwardimpact/libtelemetry";
|
|
38
40
|
import { formatError } from "../src/lib/cli-output.js";
|
|
39
41
|
import { createTemplateLoader } from "@forwardimpact/libtemplate";
|
|
40
42
|
|
|
@@ -336,50 +338,6 @@ function printHelp() {
|
|
|
336
338
|
console.log(HELP_TEXT);
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
/**
|
|
340
|
-
* Resolve the data directory path.
|
|
341
|
-
* @param {Object} options - Parsed command options
|
|
342
|
-
* @returns {string} Resolved absolute path to data directory
|
|
343
|
-
*/
|
|
344
|
-
function resolveDataPath(options) {
|
|
345
|
-
if (options.data) {
|
|
346
|
-
return resolve(options.data);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (process.env.PATHWAY_DATA) {
|
|
350
|
-
return resolve(process.env.PATHWAY_DATA);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const homeData = join(homedir(), ".fit", "pathway", "data");
|
|
354
|
-
if (existsSync(homeData)) {
|
|
355
|
-
return homeData;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const cwdDataPathway = join(process.cwd(), "data/pathway");
|
|
359
|
-
if (existsSync(cwdDataPathway)) {
|
|
360
|
-
return cwdDataPathway;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const cwdExamplesPathway = join(process.cwd(), "examples/pathway");
|
|
364
|
-
if (existsSync(cwdExamplesPathway)) {
|
|
365
|
-
return cwdExamplesPathway;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const cwdData = join(process.cwd(), "data");
|
|
369
|
-
if (existsSync(cwdData)) {
|
|
370
|
-
return cwdData;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const cwdExamples = join(process.cwd(), "examples");
|
|
374
|
-
if (existsSync(cwdExamples)) {
|
|
375
|
-
return cwdExamples;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
throw new Error(
|
|
379
|
-
"No data directory found. Create ./data/pathway/ or use --data=<path>",
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
341
|
/**
|
|
384
342
|
* Main CLI entry point
|
|
385
343
|
*/
|
|
@@ -404,7 +362,20 @@ async function main() {
|
|
|
404
362
|
process.exit(0);
|
|
405
363
|
}
|
|
406
364
|
|
|
407
|
-
|
|
365
|
+
let dataDir;
|
|
366
|
+
if (options.data) {
|
|
367
|
+
dataDir = resolve(options.data);
|
|
368
|
+
} else {
|
|
369
|
+
const logger = createLogger("pathway");
|
|
370
|
+
const finder = new Finder(fs, logger, process);
|
|
371
|
+
try {
|
|
372
|
+
dataDir = join(finder.findData("data", homedir()), "pathway");
|
|
373
|
+
} catch {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"No data directory found. Use --data=<path> to specify location.",
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
408
379
|
|
|
409
380
|
if (command === "dev") {
|
|
410
381
|
await runDevCommand({ dataDir, options });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/pathway",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.2",
|
|
4
4
|
"description": "Career progression web app and CLI for exploring roles and generating agent teams",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
"@forwardimpact/libskill": "^4.0.0",
|
|
45
45
|
"@forwardimpact/libtemplate": "^0.2.0",
|
|
46
46
|
"@forwardimpact/libui": "^1.0.0",
|
|
47
|
+
"@forwardimpact/libutil": "^0.1.64",
|
|
47
48
|
"mustache": "^4.2.0",
|
|
48
|
-
"simple-icons": "^16.
|
|
49
|
-
"yaml": "^2.3
|
|
49
|
+
"simple-icons": "^16.13.0",
|
|
50
|
+
"yaml": "^2.8.3"
|
|
50
51
|
},
|
|
51
52
|
"engines": {
|
|
52
53
|
"node": ">=18.0.0"
|
package/src/commands/update.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { cp, mkdir, rm, readFile, writeFile, access } from "fs/promises";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
|
-
import { execFileSync
|
|
12
|
+
import { execFileSync } from "child_process";
|
|
13
13
|
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
14
14
|
|
|
15
15
|
const INSTALL_DIR = join(homedir(), ".fit", "pathway");
|
|
@@ -112,9 +112,13 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
|
|
|
112
112
|
// 6. Update global pathway package if version changed
|
|
113
113
|
if (oldVersion !== newVersion) {
|
|
114
114
|
console.log(` Updating pathway ${oldVersion} → ${newVersion}...`);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
execFileSync(
|
|
116
|
+
"npm",
|
|
117
|
+
["install", "-g", `@forwardimpact/pathway@${newVersion}`],
|
|
118
|
+
{
|
|
119
|
+
stdio: "ignore",
|
|
120
|
+
},
|
|
121
|
+
);
|
|
118
122
|
console.log(" ✓ Global package updated");
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -30,8 +30,12 @@ import { truncate } from "../formatters/shared.js";
|
|
|
30
30
|
function sortByCapabilityThenLevel(skills, capabilityOrder) {
|
|
31
31
|
const orderMap = new Map(capabilityOrder.map((id, i) => [id, i]));
|
|
32
32
|
return [...skills].sort((a, b) => {
|
|
33
|
-
const capA = orderMap.has(a.capability)
|
|
34
|
-
|
|
33
|
+
const capA = orderMap.has(a.capability)
|
|
34
|
+
? orderMap.get(a.capability)
|
|
35
|
+
: capabilityOrder.length;
|
|
36
|
+
const capB = orderMap.has(b.capability)
|
|
37
|
+
? orderMap.get(b.capability)
|
|
38
|
+
: capabilityOrder.length;
|
|
35
39
|
if (capA !== capB) return capA - capB;
|
|
36
40
|
const levelA = SKILL_PROFICIENCY_ORDER.indexOf(a.proficiency);
|
|
37
41
|
const levelB = SKILL_PROFICIENCY_ORDER.indexOf(b.proficiency);
|
package/src/lib/radar.js
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
* Radar chart visualization using SVG
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Escape HTML special characters to prevent XSS
|
|
7
|
+
* @param {string} text
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
function escapeHtml(text) {
|
|
11
|
+
return text
|
|
12
|
+
.replace(/&/g, "&")
|
|
13
|
+
.replace(/</g, "<")
|
|
14
|
+
.replace(/>/g, ">")
|
|
15
|
+
.replace(/"/g, """)
|
|
16
|
+
.replace(/'/g, "'");
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
/**
|
|
6
20
|
* @typedef {Object} RadarDataPoint
|
|
7
21
|
* @property {string} label - Label for this axis
|
|
@@ -393,9 +407,9 @@ export class RadarChart {
|
|
|
393
407
|
const y = event.clientY - rect.top;
|
|
394
408
|
|
|
395
409
|
this.tooltip.innerHTML = `
|
|
396
|
-
<strong>${data.label}</strong><br>
|
|
410
|
+
<strong>${escapeHtml(data.label)}</strong><br>
|
|
397
411
|
Value: ${data.value}/${data.maxValue}
|
|
398
|
-
${data.description ? `<br><small>${data.description}</small>` : ""}
|
|
412
|
+
${data.description ? `<br><small>${escapeHtml(data.description)}</small>` : ""}
|
|
399
413
|
`;
|
|
400
414
|
|
|
401
415
|
this.tooltip.style.left = `${x + 10}px`;
|
|
@@ -815,9 +829,9 @@ export class ComparisonRadarChart {
|
|
|
815
829
|
const typeLabel = type === "current" ? "Current" : "Target";
|
|
816
830
|
|
|
817
831
|
this.tooltip.innerHTML = `
|
|
818
|
-
<strong>${data.label}</strong><br>
|
|
832
|
+
<strong>${escapeHtml(data.label)}</strong><br>
|
|
819
833
|
${typeLabel}: ${data.value}/${data.maxValue}
|
|
820
|
-
${data.description ? `<br><small>${data.description}</small>` : ""}
|
|
834
|
+
${data.description ? `<br><small>${escapeHtml(data.description)}</small>` : ""}
|
|
821
835
|
`;
|
|
822
836
|
|
|
823
837
|
this.tooltip.style.left = `${x + 10}px`;
|
|
@@ -844,7 +858,7 @@ export class ComparisonRadarChart {
|
|
|
844
858
|
: "<span style='color: #94a3b8'>No change</span>";
|
|
845
859
|
|
|
846
860
|
this.tooltip.innerHTML = `
|
|
847
|
-
<strong>${currentData.label}</strong><br>
|
|
861
|
+
<strong>${escapeHtml(currentData.label)}</strong><br>
|
|
848
862
|
Current: ${currentData.value}/${currentData.maxValue}<br>
|
|
849
863
|
Target: ${targetData.value}/${targetData.maxValue}<br>
|
|
850
864
|
${diffText}
|
package/src/slide-main.js
CHANGED
|
@@ -53,6 +53,20 @@ function showLoading() {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Escape HTML special characters to prevent XSS
|
|
58
|
+
* @param {string} text
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function escapeHtml(text) {
|
|
62
|
+
return text
|
|
63
|
+
.replace(/&/g, "&")
|
|
64
|
+
.replace(/</g, "<")
|
|
65
|
+
.replace(/>/g, ">")
|
|
66
|
+
.replace(/"/g, """)
|
|
67
|
+
.replace(/'/g, "'");
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
/**
|
|
57
71
|
* Render error slide
|
|
58
72
|
* @param {string} title
|
|
@@ -62,8 +76,8 @@ function renderError(title, message) {
|
|
|
62
76
|
const container = getSlideContent();
|
|
63
77
|
container.innerHTML = `
|
|
64
78
|
<div class="slide-error">
|
|
65
|
-
<h1>${title}</h1>
|
|
66
|
-
<p>${message}</p>
|
|
79
|
+
<h1>${escapeHtml(title)}</h1>
|
|
80
|
+
<p>${escapeHtml(message)}</p>
|
|
67
81
|
<a href="#/">← Back to Index</a>
|
|
68
82
|
</div>
|
|
69
83
|
`;
|