lula2 0.6.2 → 0.6.3-nightly.1
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/README.md +34 -0
- package/dist/_app/immutable/chunks/B9HtV8_1.js +3 -0
- package/dist/_app/immutable/chunks/{CDqxzo-U.js → CxWn_Y-j.js} +1 -1
- package/dist/_app/immutable/chunks/{CrzX_dHQ.js → Dx7QZ9kE.js} +1 -1
- package/dist/_app/immutable/entry/{app.BU_7sxPU.js → app.DI-AYM-e.js} +2 -2
- package/dist/_app/immutable/entry/start.Qohy1rto.js +1 -0
- package/dist/_app/immutable/nodes/{0.XCpjvc2-.js → 0.c6B21yPf.js} +1 -1
- package/dist/_app/immutable/nodes/{1.C1uVrnSL.js → 1.BfZiPaZF.js} +1 -1
- package/dist/_app/immutable/nodes/{2.DsoxAUFC.js → 2.CSWUUbIh.js} +1 -1
- package/dist/_app/immutable/nodes/{3.C5xOlBQo.js → 3.hCMigK9L.js} +1 -1
- package/dist/_app/immutable/nodes/4.DQ3ddGz3.js +11 -0
- package/dist/_app/version.json +1 -1
- package/dist/cli/commands/crawl.js +151 -90
- package/dist/cli/commands/ui.js +92 -11
- package/dist/cli/server/index.js +92 -11
- package/dist/cli/server/server.js +92 -11
- package/dist/cli/server/serverState.js +35 -2
- package/dist/cli/server/spreadsheetRoutes.js +56 -8
- package/dist/cli/server/websocketServer.js +92 -11
- package/dist/index.html +6 -6
- package/dist/index.js +237 -102
- package/package.json +21 -22
- package/src/lib/components/setup/SpreadsheetImport.svelte +40 -15
- package/dist/_app/immutable/chunks/DtmRvgOL.js +0 -3
- package/dist/_app/immutable/entry/start.DtOV9FzL.js +0 -1
- package/dist/_app/immutable/nodes/4.CHRd9Q51.js +0 -11
package/dist/index.js
CHANGED
|
@@ -1881,6 +1881,17 @@ var init_fileStore = __esm({
|
|
|
1881
1881
|
if (!existsSync2(this.controlsDir)) {
|
|
1882
1882
|
return [];
|
|
1883
1883
|
}
|
|
1884
|
+
let controlOrder = null;
|
|
1885
|
+
try {
|
|
1886
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1887
|
+
if (existsSync2(lulaConfigPath)) {
|
|
1888
|
+
const content = readFileSync2(lulaConfigPath, "utf8");
|
|
1889
|
+
const metadata = yaml2.load(content);
|
|
1890
|
+
controlOrder = metadata?.controlOrder || null;
|
|
1891
|
+
}
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
console.error("Failed to load lula.yaml for controlOrder:", error);
|
|
1894
|
+
}
|
|
1884
1895
|
const entries = readdirSync(this.controlsDir);
|
|
1885
1896
|
const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
|
|
1886
1897
|
if (yamlFiles.length > 0) {
|
|
@@ -1899,7 +1910,11 @@ var init_fileStore = __esm({
|
|
|
1899
1910
|
}
|
|
1900
1911
|
});
|
|
1901
1912
|
const results2 = await Promise.all(promises);
|
|
1902
|
-
|
|
1913
|
+
const controls2 = results2.filter((c) => c !== null);
|
|
1914
|
+
if (controlOrder && controlOrder.length > 0) {
|
|
1915
|
+
return this.sortControlsByOrder(controls2, controlOrder);
|
|
1916
|
+
}
|
|
1917
|
+
return controls2;
|
|
1903
1918
|
}
|
|
1904
1919
|
const families = entries.filter((name) => {
|
|
1905
1920
|
const familyPath = join2(this.controlsDir, name);
|
|
@@ -1922,7 +1937,25 @@ var init_fileStore = __esm({
|
|
|
1922
1937
|
allPromises.push(...familyPromises);
|
|
1923
1938
|
}
|
|
1924
1939
|
const results = await Promise.all(allPromises);
|
|
1925
|
-
|
|
1940
|
+
const controls = results.filter((c) => c !== null);
|
|
1941
|
+
if (controlOrder && controlOrder.length > 0) {
|
|
1942
|
+
return this.sortControlsByOrder(controls, controlOrder);
|
|
1943
|
+
}
|
|
1944
|
+
return controls;
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Sort controls based on the provided order array
|
|
1948
|
+
*/
|
|
1949
|
+
sortControlsByOrder(controls, controlOrder) {
|
|
1950
|
+
const orderMap = /* @__PURE__ */ new Map();
|
|
1951
|
+
controlOrder.forEach((controlId, index) => {
|
|
1952
|
+
orderMap.set(controlId, index);
|
|
1953
|
+
});
|
|
1954
|
+
return controls.sort((a, b) => {
|
|
1955
|
+
const aIndex = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
1956
|
+
const bIndex = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
1957
|
+
return aIndex - bIndex;
|
|
1958
|
+
});
|
|
1926
1959
|
}
|
|
1927
1960
|
/**
|
|
1928
1961
|
* Load mappings from mappings directory
|
|
@@ -2949,7 +2982,7 @@ function processImportParameters(reqBody) {
|
|
|
2949
2982
|
frontendFieldSchema
|
|
2950
2983
|
};
|
|
2951
2984
|
}
|
|
2952
|
-
async function parseUploadedFile(file) {
|
|
2985
|
+
async function parseUploadedFile(file, sheetName) {
|
|
2953
2986
|
const fileName = file.originalname || "";
|
|
2954
2987
|
const isCSV = fileName.toLowerCase().endsWith(".csv");
|
|
2955
2988
|
let rawData = [];
|
|
@@ -2958,10 +2991,13 @@ async function parseUploadedFile(file) {
|
|
|
2958
2991
|
rawData = parseCSV(csvContent);
|
|
2959
2992
|
} else {
|
|
2960
2993
|
const workbook = XLSX.read(file.buffer, { type: "buffer" });
|
|
2961
|
-
const worksheetName = workbook.SheetNames[0];
|
|
2994
|
+
const worksheetName = sheetName || workbook.SheetNames[0];
|
|
2962
2995
|
if (!worksheetName) {
|
|
2963
2996
|
throw new Error("No worksheet found in file");
|
|
2964
2997
|
}
|
|
2998
|
+
if (sheetName && !workbook.SheetNames.includes(sheetName)) {
|
|
2999
|
+
throw new Error(`Sheet "${sheetName}" not found in workbook`);
|
|
3000
|
+
}
|
|
2965
3001
|
const worksheet = workbook.Sheets[worksheetName];
|
|
2966
3002
|
rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
|
|
2967
3003
|
}
|
|
@@ -3039,6 +3075,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
|
|
|
3039
3075
|
continue;
|
|
3040
3076
|
}
|
|
3041
3077
|
const family = extractFamilyFromControlId(controlId);
|
|
3078
|
+
control._originalRowIndex = i;
|
|
3042
3079
|
control.family = family;
|
|
3043
3080
|
controls.push(control);
|
|
3044
3081
|
if (!families.has(family)) {
|
|
@@ -3161,6 +3198,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
|
|
|
3161
3198
|
params.controlIdField,
|
|
3162
3199
|
params.namingConvention
|
|
3163
3200
|
);
|
|
3201
|
+
const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
|
|
3164
3202
|
const controlSetData = {
|
|
3165
3203
|
name: params.controlSetName,
|
|
3166
3204
|
description: params.controlSetDescription,
|
|
@@ -3168,12 +3206,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
|
|
|
3168
3206
|
control_id_field: controlIdFieldNameClean,
|
|
3169
3207
|
controlCount: controls.length,
|
|
3170
3208
|
families: uniqueFamilies,
|
|
3209
|
+
controlOrder,
|
|
3171
3210
|
fieldSchema
|
|
3172
3211
|
};
|
|
3173
3212
|
writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
|
|
3174
3213
|
const controlsDir = join4(baseDir, "controls");
|
|
3175
3214
|
const mappingsDir = join4(baseDir, "mappings");
|
|
3176
|
-
families.
|
|
3215
|
+
const sortedFamilies = Array.from(families.entries()).sort(
|
|
3216
|
+
(a, b) => a[0].localeCompare(b[0])
|
|
3217
|
+
);
|
|
3218
|
+
sortedFamilies.forEach(([family, familyControls]) => {
|
|
3177
3219
|
const familyDir = join4(controlsDir, family);
|
|
3178
3220
|
const familyMappingsDir = join4(mappingsDir, family);
|
|
3179
3221
|
if (!existsSync3(familyDir)) {
|
|
@@ -3182,7 +3224,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
|
|
|
3182
3224
|
if (!existsSync3(familyMappingsDir)) {
|
|
3183
3225
|
mkdirSync2(familyMappingsDir, { recursive: true });
|
|
3184
3226
|
}
|
|
3185
|
-
familyControls.
|
|
3227
|
+
const sortedFamilyControls = familyControls.sort(
|
|
3228
|
+
(a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
|
|
3229
|
+
);
|
|
3230
|
+
sortedFamilyControls.forEach((control) => {
|
|
3186
3231
|
const controlId = control[controlIdFieldNameClean];
|
|
3187
3232
|
if (!controlId) {
|
|
3188
3233
|
console.error("Missing control ID for control:", control);
|
|
@@ -3204,7 +3249,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
|
|
|
3204
3249
|
filteredControl.family = control.family;
|
|
3205
3250
|
}
|
|
3206
3251
|
Object.keys(control).forEach((fieldName) => {
|
|
3207
|
-
if (fieldName === "family") return;
|
|
3252
|
+
if (fieldName === "family" || fieldName === "_originalRowIndex") return;
|
|
3208
3253
|
if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
|
|
3209
3254
|
justificationContents.push(control[fieldName]);
|
|
3210
3255
|
}
|
|
@@ -3568,12 +3613,14 @@ function exportAsJSON(controls, metadata, res) {
|
|
|
3568
3613
|
res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
|
|
3569
3614
|
res.json(exportData);
|
|
3570
3615
|
}
|
|
3571
|
-
var router, upload, spreadsheetRoutes_default;
|
|
3616
|
+
var MAX_HEADER_CANDIDATES, PREVIEW_COLUMNS, router, upload, spreadsheetRoutes_default;
|
|
3572
3617
|
var init_spreadsheetRoutes = __esm({
|
|
3573
3618
|
"cli/server/spreadsheetRoutes.ts"() {
|
|
3574
3619
|
"use strict";
|
|
3575
3620
|
init_debug();
|
|
3576
3621
|
init_serverState();
|
|
3622
|
+
MAX_HEADER_CANDIDATES = 5;
|
|
3623
|
+
PREVIEW_COLUMNS = 4;
|
|
3577
3624
|
router = express.Router();
|
|
3578
3625
|
upload = multer({
|
|
3579
3626
|
storage: multer.memoryStorage(),
|
|
@@ -3586,7 +3633,8 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3586
3633
|
return res.status(400).json({ error: "No file uploaded" });
|
|
3587
3634
|
}
|
|
3588
3635
|
const params = processImportParameters(req.body);
|
|
3589
|
-
const
|
|
3636
|
+
const sheetName = req.body.sheetName;
|
|
3637
|
+
const rawData = await parseUploadedFile(req.file, sheetName);
|
|
3590
3638
|
const startRowIndex = parseInt(params.startRow) - 1;
|
|
3591
3639
|
if (rawData.length <= startRowIndex) {
|
|
3592
3640
|
return res.status(400).json({ error: "Start row exceeds sheet data" });
|
|
@@ -3798,9 +3846,9 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3798
3846
|
const worksheet = workbook.Sheets[worksheetName];
|
|
3799
3847
|
rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
|
|
3800
3848
|
}
|
|
3801
|
-
const headerCandidates = rows.slice(0,
|
|
3849
|
+
const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
|
|
3802
3850
|
row: index + 1,
|
|
3803
|
-
preview: row.slice(0,
|
|
3851
|
+
preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
|
|
3804
3852
|
}));
|
|
3805
3853
|
res.json({
|
|
3806
3854
|
sheets,
|
|
@@ -3857,6 +3905,39 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3857
3905
|
res.status(500).json({ error: "Failed to parse Excel sheet" });
|
|
3858
3906
|
}
|
|
3859
3907
|
});
|
|
3908
|
+
router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, res) => {
|
|
3909
|
+
try {
|
|
3910
|
+
const { sheetName } = req.body;
|
|
3911
|
+
if (!req.file) {
|
|
3912
|
+
return res.status(400).json({ error: "No file uploaded" });
|
|
3913
|
+
}
|
|
3914
|
+
const fileName = req.file.originalname || "";
|
|
3915
|
+
const isCSV = fileName.toLowerCase().endsWith(".csv");
|
|
3916
|
+
let rows = [];
|
|
3917
|
+
if (isCSV) {
|
|
3918
|
+
const csvContent = req.file.buffer.toString("utf-8");
|
|
3919
|
+
rows = parseCSV(csvContent);
|
|
3920
|
+
} else {
|
|
3921
|
+
const workbook = XLSX.read(req.file.buffer, { type: "buffer" });
|
|
3922
|
+
if (!workbook.SheetNames.includes(sheetName)) {
|
|
3923
|
+
return res.status(400).json({ error: `Sheet "${sheetName}" not found` });
|
|
3924
|
+
}
|
|
3925
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
3926
|
+
rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
|
|
3927
|
+
}
|
|
3928
|
+
const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
|
|
3929
|
+
row: index + 1,
|
|
3930
|
+
preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
|
|
3931
|
+
}));
|
|
3932
|
+
res.json({
|
|
3933
|
+
rowPreviews: headerCandidates,
|
|
3934
|
+
totalRows: rows.length
|
|
3935
|
+
});
|
|
3936
|
+
} catch (error) {
|
|
3937
|
+
console.error("Error getting sheet previews:", error);
|
|
3938
|
+
res.status(500).json({ error: "Failed to get sheet previews" });
|
|
3939
|
+
}
|
|
3940
|
+
});
|
|
3860
3941
|
spreadsheetRoutes_default = router;
|
|
3861
3942
|
}
|
|
3862
3943
|
});
|
|
@@ -5599,155 +5680,209 @@ function containsLulaAnnotations(text) {
|
|
|
5599
5680
|
const lines = text.split("\n");
|
|
5600
5681
|
return lines.some((line) => line.includes("@lulaStart") || line.includes("@lulaEnd"));
|
|
5601
5682
|
}
|
|
5602
|
-
function
|
|
5603
|
-
return
|
|
5604
|
-
new Option("--post-mode <mode>", "How to post findings").choices(["review", "comment"]).default("review")
|
|
5605
|
-
).action(async (opts) => {
|
|
5606
|
-
let leavePost = false;
|
|
5607
|
-
const { owner, repo, pull_number } = getPRContext();
|
|
5608
|
-
console.log(`Analyzing PR #${pull_number} in ${owner}/${repo} for compliance changes...`);
|
|
5609
|
-
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
5610
|
-
const pr = await octokit.pulls.get({ owner, repo, pull_number });
|
|
5611
|
-
const prBranch = pr.data.head.ref;
|
|
5612
|
-
const { data: files } = await octokit.pulls.listFiles({ owner, repo, pull_number });
|
|
5613
|
-
let commentBody = `${LULA_SIGNATURE}
|
|
5683
|
+
function createInitialCommentBody(filesCount) {
|
|
5684
|
+
return `${LULA_SIGNATURE}
|
|
5614
5685
|
## Lula Compliance Overview
|
|
5615
5686
|
|
|
5616
5687
|
Please review the changes to ensure they meet compliance standards.
|
|
5617
5688
|
|
|
5618
5689
|
### Reviewed Changes
|
|
5619
5690
|
|
|
5620
|
-
Lula reviewed ${
|
|
5691
|
+
Lula reviewed ${filesCount} files changed that affect compliance.
|
|
5621
5692
|
|
|
5622
5693
|
`;
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5694
|
+
}
|
|
5695
|
+
async function analyzeDeletedFiles(context) {
|
|
5696
|
+
const { octokit, owner, repo, files } = context;
|
|
5697
|
+
const deletedFilesWithAnnotations = [];
|
|
5698
|
+
for (const file of files) {
|
|
5699
|
+
if (file.status === "removed") {
|
|
5700
|
+
try {
|
|
5701
|
+
const oldText = await fetchRawFileViaAPI({
|
|
5702
|
+
octokit,
|
|
5703
|
+
owner,
|
|
5704
|
+
repo,
|
|
5705
|
+
path: file.filename,
|
|
5706
|
+
ref: "main"
|
|
5707
|
+
});
|
|
5708
|
+
if (containsLulaAnnotations(oldText)) {
|
|
5709
|
+
deletedFilesWithAnnotations.push(file.filename);
|
|
5639
5710
|
}
|
|
5711
|
+
} catch (err) {
|
|
5712
|
+
console.error(`Error checking deleted file ${file.filename}: ${err}`);
|
|
5640
5713
|
}
|
|
5641
5714
|
}
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5715
|
+
}
|
|
5716
|
+
if (deletedFilesWithAnnotations.length === 0) {
|
|
5717
|
+
return { hasFindings: false, warningContent: "" };
|
|
5718
|
+
}
|
|
5719
|
+
let warningContent = `
|
|
5645
5720
|
|
|
5646
5721
|
**Compliance Warning: Files with Lula annotations were deleted**
|
|
5647
5722
|
|
|
5648
5723
|
`;
|
|
5649
|
-
|
|
5724
|
+
warningContent += `The following files contained compliance annotations (\`@lulaStart\`/\`@lulaEnd\`) and were deleted in this PR. This may affect compliance coverage:
|
|
5650
5725
|
|
|
5651
5726
|
`;
|
|
5652
|
-
|
|
5653
|
-
|
|
5727
|
+
for (const filename of deletedFilesWithAnnotations) {
|
|
5728
|
+
warningContent += `- \`${filename}\`
|
|
5654
5729
|
`;
|
|
5655
|
-
|
|
5656
|
-
|
|
5730
|
+
}
|
|
5731
|
+
warningContent += `
|
|
5657
5732
|
Please review whether:
|
|
5658
5733
|
`;
|
|
5659
|
-
|
|
5734
|
+
warningContent += `- The compliance coverage provided by these files is still needed
|
|
5660
5735
|
`;
|
|
5661
|
-
|
|
5736
|
+
warningContent += `- Alternative compliance measures have been implemented
|
|
5662
5737
|
`;
|
|
5663
|
-
|
|
5738
|
+
warningContent += `- The deletion is intentional and compliance-approved
|
|
5664
5739
|
|
|
5665
5740
|
`;
|
|
5666
|
-
|
|
5741
|
+
warningContent += `---
|
|
5667
5742
|
|
|
5668
5743
|
`;
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
]);
|
|
5677
|
-
const changedBlocks = getChangedBlocks(oldText, newText);
|
|
5678
|
-
const removedBlocks = getRemovedBlocks(oldText, newText);
|
|
5679
|
-
for (const block of changedBlocks) {
|
|
5680
|
-
console.log(`Commenting regarding \`${file.filename}\`.`);
|
|
5681
|
-
leavePost = true;
|
|
5682
|
-
commentBody += `
|
|
5744
|
+
return { hasFindings: true, warningContent };
|
|
5745
|
+
}
|
|
5746
|
+
function generateChangedBlocksContent(filename, changedBlocks, newText) {
|
|
5747
|
+
let content = "";
|
|
5748
|
+
for (const block of changedBlocks) {
|
|
5749
|
+
console.log(`Commenting regarding \`${filename}\`.`);
|
|
5750
|
+
content += `
|
|
5683
5751
|
|
|
5684
5752
|
---
|
|
5685
5753
|
| File | Lines Changed |
|
|
5686
5754
|
| ---- | ------------- |
|
|
5687
5755
|
`;
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5756
|
+
const newBlockText = newText.split("\n").slice(block.startLine, block.endLine).join("\n");
|
|
5757
|
+
const blockSha256 = createHash2("sha256").update(newBlockText).digest("hex");
|
|
5758
|
+
content += `| \`${filename}\` | \`${block.startLine + 1}\u2013${block.endLine}\` |
|
|
5691
5759
|
> **uuid**-\`${block.uuid}\`
|
|
5692
5760
|
**sha256** \`${blockSha256}\`
|
|
5693
5761
|
|
|
5694
5762
|
`;
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5763
|
+
}
|
|
5764
|
+
return content;
|
|
5765
|
+
}
|
|
5766
|
+
function generateRemovedBlocksContent(filename, removedBlocks, oldText) {
|
|
5767
|
+
if (removedBlocks.length === 0) {
|
|
5768
|
+
return "";
|
|
5769
|
+
}
|
|
5770
|
+
console.log(`Found removed annotations in \`${filename}\`.`);
|
|
5771
|
+
let content = `
|
|
5700
5772
|
|
|
5701
|
-
**Compliance Warning: Lula annotations were removed from \`${
|
|
5773
|
+
**Compliance Warning: Lula annotations were removed from \`${filename}\`**
|
|
5702
5774
|
|
|
5703
5775
|
`;
|
|
5704
|
-
|
|
5776
|
+
content += `The following compliance annotation blocks were present in the original file but are missing in the updated version:
|
|
5705
5777
|
|
|
5706
5778
|
`;
|
|
5707
|
-
|
|
5779
|
+
content += `| File | Original Lines | UUID |
|
|
5708
5780
|
`;
|
|
5709
|
-
|
|
5781
|
+
content += `| ---- | -------------- | ---- |
|
|
5710
5782
|
`;
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5783
|
+
for (const block of removedBlocks) {
|
|
5784
|
+
const oldBlockText = oldText.split("\n").slice(block.startLine, block.endLine).join("\n");
|
|
5785
|
+
const blockSha256 = createHash2("sha256").update(oldBlockText).digest("hex");
|
|
5786
|
+
content += `| \`${filename}\` | \`${block.startLine + 1}\u2013${block.endLine}\` | \`${block.uuid}\` |
|
|
5715
5787
|
`;
|
|
5716
|
-
|
|
5788
|
+
content += `> **sha256** \`${blockSha256}\`
|
|
5717
5789
|
|
|
5718
5790
|
`;
|
|
5719
|
-
|
|
5720
|
-
|
|
5791
|
+
}
|
|
5792
|
+
content += `Please review whether:
|
|
5721
5793
|
`;
|
|
5722
|
-
|
|
5794
|
+
content += `- The removal of these compliance annotations is intentional
|
|
5723
5795
|
`;
|
|
5724
|
-
|
|
5796
|
+
content += `- Alternative compliance measures have been implemented
|
|
5725
5797
|
`;
|
|
5726
|
-
|
|
5798
|
+
content += `- The compliance coverage is still adequate
|
|
5727
5799
|
|
|
5728
5800
|
`;
|
|
5729
|
-
|
|
5801
|
+
content += `---
|
|
5730
5802
|
|
|
5731
5803
|
`;
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5804
|
+
return content;
|
|
5805
|
+
}
|
|
5806
|
+
async function analyzeModifiedFiles(context) {
|
|
5807
|
+
const { octokit, owner, repo, prBranch, files } = context;
|
|
5808
|
+
let changesContent = "";
|
|
5809
|
+
let hasFindings = false;
|
|
5810
|
+
for (const file of files) {
|
|
5811
|
+
if (file.status === "added" || file.status === "removed") continue;
|
|
5812
|
+
try {
|
|
5813
|
+
const [oldText, newText] = await Promise.all([
|
|
5814
|
+
fetchRawFileViaAPI({ octokit, owner, repo, path: file.filename, ref: "main" }),
|
|
5815
|
+
fetchRawFileViaAPI({ octokit, owner, repo, path: file.filename, ref: prBranch })
|
|
5816
|
+
]);
|
|
5817
|
+
const changedBlocks = getChangedBlocks(oldText, newText);
|
|
5818
|
+
const removedBlocks = getRemovedBlocks(oldText, newText);
|
|
5819
|
+
if (changedBlocks.length > 0) {
|
|
5820
|
+
hasFindings = true;
|
|
5821
|
+
changesContent += generateChangedBlocksContent(file.filename, changedBlocks, newText);
|
|
5822
|
+
}
|
|
5823
|
+
if (removedBlocks.length > 0) {
|
|
5824
|
+
hasFindings = true;
|
|
5825
|
+
changesContent += generateRemovedBlocksContent(file.filename, removedBlocks, oldText);
|
|
5826
|
+
}
|
|
5827
|
+
} catch (err) {
|
|
5828
|
+
console.error(`Error processing ${file.filename}: ${err}`);
|
|
5742
5829
|
}
|
|
5743
|
-
|
|
5830
|
+
}
|
|
5831
|
+
return { hasFindings, changesContent };
|
|
5832
|
+
}
|
|
5833
|
+
async function performComplianceAnalysis(context) {
|
|
5834
|
+
let commentBody = createInitialCommentBody(context.files.length);
|
|
5835
|
+
let hasFindings = false;
|
|
5836
|
+
const deletedAnalysis = await analyzeDeletedFiles(context);
|
|
5837
|
+
if (deletedAnalysis.hasFindings) {
|
|
5838
|
+
hasFindings = true;
|
|
5839
|
+
commentBody += deletedAnalysis.warningContent;
|
|
5840
|
+
}
|
|
5841
|
+
const modifiedAnalysis = await analyzeModifiedFiles(context);
|
|
5842
|
+
if (modifiedAnalysis.hasFindings) {
|
|
5843
|
+
hasFindings = true;
|
|
5844
|
+
commentBody += modifiedAnalysis.changesContent;
|
|
5845
|
+
}
|
|
5846
|
+
return { hasFindings, commentBody };
|
|
5847
|
+
}
|
|
5848
|
+
async function cleanupOldPosts(context, postMode) {
|
|
5849
|
+
const { octokit, owner, repo, pull_number } = context;
|
|
5850
|
+
if (postMode === "comment") {
|
|
5851
|
+
await deleteOldIssueComments({ octokit, owner, repo, pull_number });
|
|
5852
|
+
} else {
|
|
5853
|
+
await dismissOldReviews({ octokit, owner, repo, pull_number });
|
|
5854
|
+
await deleteOldReviewComments({ octokit, owner, repo, pull_number });
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
function crawlCommand() {
|
|
5858
|
+
return new Command().command("crawl").description("Detect compliance-related changes between @lulaStart and @lulaEnd in PR files").addOption(
|
|
5859
|
+
new Option("--post-mode <mode>", "How to post findings").choices(["review", "comment"]).default("review")
|
|
5860
|
+
).action(async (opts) => {
|
|
5861
|
+
const { owner, repo, pull_number } = getPRContext();
|
|
5862
|
+
console.log(`Analyzing PR #${pull_number} in ${owner}/${repo} for compliance changes...`);
|
|
5863
|
+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
5864
|
+
const pr = await octokit.pulls.get({ owner, repo, pull_number });
|
|
5865
|
+
const prBranch = pr.data.head.ref;
|
|
5866
|
+
const { data: files } = await octokit.pulls.listFiles({ owner, repo, pull_number });
|
|
5867
|
+
const context = {
|
|
5868
|
+
octokit,
|
|
5869
|
+
owner,
|
|
5870
|
+
repo,
|
|
5871
|
+
pull_number,
|
|
5872
|
+
prBranch,
|
|
5873
|
+
files
|
|
5874
|
+
};
|
|
5875
|
+
const analysisResult = await performComplianceAnalysis(context);
|
|
5876
|
+
await cleanupOldPosts(context, opts.postMode);
|
|
5877
|
+
if (analysisResult.hasFindings) {
|
|
5878
|
+
const finalBody = analysisResult.commentBody + closingBody;
|
|
5744
5879
|
await postFinding({
|
|
5745
5880
|
octokit,
|
|
5746
5881
|
postMode: opts.postMode,
|
|
5747
5882
|
owner,
|
|
5748
5883
|
repo,
|
|
5749
5884
|
pull_number,
|
|
5750
|
-
body:
|
|
5885
|
+
body: finalBody
|
|
5751
5886
|
});
|
|
5752
5887
|
const header = `Posted (${opts.postMode})`;
|
|
5753
5888
|
const underline = "-".repeat(header.length);
|
|
@@ -5755,7 +5890,7 @@ Please review whether:
|
|
|
5755
5890
|
${header}
|
|
5756
5891
|
${underline}
|
|
5757
5892
|
|
|
5758
|
-
${
|
|
5893
|
+
${finalBody}
|
|
5759
5894
|
|
|
5760
5895
|
`);
|
|
5761
5896
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lula2",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3-nightly.1",
|
|
4
4
|
"description": "A tool for managing compliance as code in your GitHub repositories.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lula2": "./dist/lula2"
|
|
@@ -33,25 +33,6 @@
|
|
|
33
33
|
"!dist/**/*.test.js*",
|
|
34
34
|
"!dist/**/*.test.d.ts*"
|
|
35
35
|
],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "vite dev --port 5173",
|
|
38
|
-
"dev:api": "tsx --watch index.ts --debug ui --port 3000 --no-open-browser",
|
|
39
|
-
"dev:full": "concurrently \"npm run dev:api\" \"npm run dev\"",
|
|
40
|
-
"build": "npm run build:svelte && npm run build:cli && npm run postbuild:cli",
|
|
41
|
-
"build:svelte": "vite build",
|
|
42
|
-
"build:cli": "esbuild index.ts cli/**/*.ts --bundle --platform=node --target=node22 --format=esm --outdir=dist --external:express --external:commander --external:js-yaml --external:yaml --external:isomorphic-git --external:glob --external:open --external:ws --external:cors --external:multer --external:@octokit/rest --external:undici --external:xlsx-republish --external:csv-parse",
|
|
43
|
-
"postbuild:cli": "cp cli-wrapper.mjs dist/lula2 && chmod +x dist/lula2",
|
|
44
|
-
"preview": "vite preview",
|
|
45
|
-
"prepare": "svelte-kit sync || echo ''",
|
|
46
|
-
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && tsc --noEmit",
|
|
47
|
-
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
48
|
-
"format": "prettier --write 'src/**/*.{ts,js,svelte}' 'cli/**/*.ts' 'index.ts' 'tests/**/*.ts'",
|
|
49
|
-
"format:check": "prettier --check 'src/**/*.{ts,js,svelte}' 'cli/**/*.ts' 'index.ts' 'tests/**/*.ts'",
|
|
50
|
-
"lint": "prettier --check 'src/**/*.{ts,js,svelte}' 'cli/**/*.ts' 'index.ts' 'tests/**/*.ts' && eslint src cli",
|
|
51
|
-
"test": "npm run test:unit -- --run --coverage",
|
|
52
|
-
"test:integration": "vitest --config integration/vitest.config.integration.ts",
|
|
53
|
-
"test:unit": "vitest"
|
|
54
|
-
},
|
|
55
36
|
"dependencies": {
|
|
56
37
|
"@octokit/rest": "^22.0.0",
|
|
57
38
|
"@types/ws": "^8.18.1",
|
|
@@ -98,7 +79,7 @@
|
|
|
98
79
|
"esbuild": "^0.25.9",
|
|
99
80
|
"eslint": "^9.35.0",
|
|
100
81
|
"eslint-config-prettier": "^10.1.8",
|
|
101
|
-
"eslint-plugin-jsdoc": "^
|
|
82
|
+
"eslint-plugin-jsdoc": "^61.0.0",
|
|
102
83
|
"eslint-plugin-svelte": "^3.12.2",
|
|
103
84
|
"globals": "^16.3.0",
|
|
104
85
|
"husky": "^9.1.7",
|
|
@@ -124,5 +105,23 @@
|
|
|
124
105
|
"main",
|
|
125
106
|
"next"
|
|
126
107
|
]
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"dev": "vite dev --port 5173",
|
|
111
|
+
"dev:api": "tsx --watch index.ts --debug ui --port 3000 --no-open-browser",
|
|
112
|
+
"dev:full": "concurrently \"npm run dev:api\" \"npm run dev\"",
|
|
113
|
+
"build": "npm run build:svelte && npm run build:cli && npm run postbuild:cli",
|
|
114
|
+
"build:svelte": "vite build",
|
|
115
|
+
"build:cli": "esbuild index.ts cli/**/*.ts --bundle --platform=node --target=node22 --format=esm --outdir=dist --external:express --external:commander --external:js-yaml --external:yaml --external:isomorphic-git --external:glob --external:open --external:ws --external:cors --external:multer --external:@octokit/rest --external:undici --external:xlsx-republish --external:csv-parse",
|
|
116
|
+
"postbuild:cli": "cp cli-wrapper.mjs dist/lula2 && chmod +x dist/lula2",
|
|
117
|
+
"preview": "vite preview",
|
|
118
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && tsc --noEmit",
|
|
119
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
120
|
+
"format": "prettier --write 'src/**/*.{ts,js,svelte}' 'cli/**/*.ts' 'index.ts' 'tests/**/*.ts'",
|
|
121
|
+
"format:check": "prettier --check 'src/**/*.{ts,js,svelte}' 'cli/**/*.ts' 'index.ts' 'tests/**/*.ts'",
|
|
122
|
+
"lint": "prettier --check 'src/**/*.{ts,js,svelte}' 'cli/**/*.ts' 'index.ts' 'tests/**/*.ts' && eslint src cli",
|
|
123
|
+
"test": "npm run test:unit -- --run --coverage",
|
|
124
|
+
"test:integration": "vitest --config integration/vitest.config.integration.ts",
|
|
125
|
+
"test:unit": "vitest"
|
|
127
126
|
}
|
|
128
|
-
}
|
|
127
|
+
}
|