lula2 0.6.3-nightly.0 → 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/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
- return results2.filter((c) => c !== null);
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
- return results.filter((c) => c !== null);
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
@@ -3042,6 +3075,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
3042
3075
  continue;
3043
3076
  }
3044
3077
  const family = extractFamilyFromControlId(controlId);
3078
+ control._originalRowIndex = i;
3045
3079
  control.family = family;
3046
3080
  controls.push(control);
3047
3081
  if (!families.has(family)) {
@@ -3164,6 +3198,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3164
3198
  params.controlIdField,
3165
3199
  params.namingConvention
3166
3200
  );
3201
+ const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
3167
3202
  const controlSetData = {
3168
3203
  name: params.controlSetName,
3169
3204
  description: params.controlSetDescription,
@@ -3171,12 +3206,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3171
3206
  control_id_field: controlIdFieldNameClean,
3172
3207
  controlCount: controls.length,
3173
3208
  families: uniqueFamilies,
3209
+ controlOrder,
3174
3210
  fieldSchema
3175
3211
  };
3176
3212
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3177
3213
  const controlsDir = join4(baseDir, "controls");
3178
3214
  const mappingsDir = join4(baseDir, "mappings");
3179
- families.forEach((familyControls, family) => {
3215
+ const sortedFamilies = Array.from(families.entries()).sort(
3216
+ (a, b) => a[0].localeCompare(b[0])
3217
+ );
3218
+ sortedFamilies.forEach(([family, familyControls]) => {
3180
3219
  const familyDir = join4(controlsDir, family);
3181
3220
  const familyMappingsDir = join4(mappingsDir, family);
3182
3221
  if (!existsSync3(familyDir)) {
@@ -3185,7 +3224,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3185
3224
  if (!existsSync3(familyMappingsDir)) {
3186
3225
  mkdirSync2(familyMappingsDir, { recursive: true });
3187
3226
  }
3188
- familyControls.forEach((control) => {
3227
+ const sortedFamilyControls = familyControls.sort(
3228
+ (a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
3229
+ );
3230
+ sortedFamilyControls.forEach((control) => {
3189
3231
  const controlId = control[controlIdFieldNameClean];
3190
3232
  if (!controlId) {
3191
3233
  console.error("Missing control ID for control:", control);
@@ -3207,7 +3249,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3207
3249
  filteredControl.family = control.family;
3208
3250
  }
3209
3251
  Object.keys(control).forEach((fieldName) => {
3210
- if (fieldName === "family") return;
3252
+ if (fieldName === "family" || fieldName === "_originalRowIndex") return;
3211
3253
  if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3212
3254
  justificationContents.push(control[fieldName]);
3213
3255
  }
@@ -5638,155 +5680,209 @@ function containsLulaAnnotations(text) {
5638
5680
  const lines = text.split("\n");
5639
5681
  return lines.some((line) => line.includes("@lulaStart") || line.includes("@lulaEnd"));
5640
5682
  }
5641
- function crawlCommand() {
5642
- return new Command().command("crawl").description("Detect compliance-related changes between @lulaStart and @lulaEnd in PR files").addOption(
5643
- new Option("--post-mode <mode>", "How to post findings").choices(["review", "comment"]).default("review")
5644
- ).action(async (opts) => {
5645
- let leavePost = false;
5646
- const { owner, repo, pull_number } = getPRContext();
5647
- console.log(`Analyzing PR #${pull_number} in ${owner}/${repo} for compliance changes...`);
5648
- const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
5649
- const pr = await octokit.pulls.get({ owner, repo, pull_number });
5650
- const prBranch = pr.data.head.ref;
5651
- const { data: files } = await octokit.pulls.listFiles({ owner, repo, pull_number });
5652
- let commentBody = `${LULA_SIGNATURE}
5683
+ function createInitialCommentBody(filesCount) {
5684
+ return `${LULA_SIGNATURE}
5653
5685
  ## Lula Compliance Overview
5654
5686
 
5655
5687
  Please review the changes to ensure they meet compliance standards.
5656
5688
 
5657
5689
  ### Reviewed Changes
5658
5690
 
5659
- Lula reviewed ${files.length} files changed that affect compliance.
5691
+ Lula reviewed ${filesCount} files changed that affect compliance.
5660
5692
 
5661
5693
  `;
5662
- const deletedFilesWithAnnotations = [];
5663
- for (const file of files) {
5664
- if (file.status === "removed") {
5665
- try {
5666
- const oldText = await fetchRawFileViaAPI({
5667
- octokit,
5668
- owner,
5669
- repo,
5670
- path: file.filename,
5671
- ref: "main"
5672
- });
5673
- if (containsLulaAnnotations(oldText)) {
5674
- deletedFilesWithAnnotations.push(file.filename);
5675
- }
5676
- } catch (err) {
5677
- console.error(`Error checking deleted file ${file.filename}: ${err}`);
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);
5678
5710
  }
5711
+ } catch (err) {
5712
+ console.error(`Error checking deleted file ${file.filename}: ${err}`);
5679
5713
  }
5680
5714
  }
5681
- if (deletedFilesWithAnnotations.length > 0) {
5682
- leavePost = true;
5683
- commentBody += `
5715
+ }
5716
+ if (deletedFilesWithAnnotations.length === 0) {
5717
+ return { hasFindings: false, warningContent: "" };
5718
+ }
5719
+ let warningContent = `
5684
5720
 
5685
5721
  **Compliance Warning: Files with Lula annotations were deleted**
5686
5722
 
5687
5723
  `;
5688
- commentBody += `The following files contained compliance annotations (\`@lulaStart\`/\`@lulaEnd\`) and were deleted in this PR. This may affect compliance coverage:
5724
+ warningContent += `The following files contained compliance annotations (\`@lulaStart\`/\`@lulaEnd\`) and were deleted in this PR. This may affect compliance coverage:
5689
5725
 
5690
5726
  `;
5691
- for (const filename of deletedFilesWithAnnotations) {
5692
- commentBody += `- \`${filename}\`
5727
+ for (const filename of deletedFilesWithAnnotations) {
5728
+ warningContent += `- \`${filename}\`
5693
5729
  `;
5694
- }
5695
- commentBody += `
5730
+ }
5731
+ warningContent += `
5696
5732
  Please review whether:
5697
5733
  `;
5698
- commentBody += `- The compliance coverage provided by these files is still needed
5734
+ warningContent += `- The compliance coverage provided by these files is still needed
5699
5735
  `;
5700
- commentBody += `- Alternative compliance measures have been implemented
5736
+ warningContent += `- Alternative compliance measures have been implemented
5701
5737
  `;
5702
- commentBody += `- The deletion is intentional and compliance-approved
5738
+ warningContent += `- The deletion is intentional and compliance-approved
5703
5739
 
5704
5740
  `;
5705
- commentBody += `---
5741
+ warningContent += `---
5706
5742
 
5707
5743
  `;
5708
- }
5709
- for (const file of files) {
5710
- if (file.status === "added" || file.status === "removed") continue;
5711
- try {
5712
- const [oldText, newText] = await Promise.all([
5713
- fetchRawFileViaAPI({ octokit, owner, repo, path: file.filename, ref: "main" }),
5714
- fetchRawFileViaAPI({ octokit, owner, repo, path: file.filename, ref: prBranch })
5715
- ]);
5716
- const changedBlocks = getChangedBlocks(oldText, newText);
5717
- const removedBlocks = getRemovedBlocks(oldText, newText);
5718
- for (const block of changedBlocks) {
5719
- console.log(`Commenting regarding \`${file.filename}\`.`);
5720
- leavePost = true;
5721
- 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 += `
5722
5751
 
5723
5752
  ---
5724
5753
  | File | Lines Changed |
5725
5754
  | ---- | ------------- |
5726
5755
  `;
5727
- const newBlockText = newText.split("\n").slice(block.startLine, block.endLine).join("\n");
5728
- const blockSha256 = createHash2("sha256").update(newBlockText).digest("hex");
5729
- commentBody += `| \`${file.filename}\` | \`${block.startLine + 1}\u2013${block.endLine}\` |
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}\` |
5730
5759
  > **uuid**-\`${block.uuid}\`
5731
5760
  **sha256** \`${blockSha256}\`
5732
5761
 
5733
5762
  `;
5734
- }
5735
- if (removedBlocks.length > 0) {
5736
- leavePost = true;
5737
- console.log(`Found removed annotations in \`${file.filename}\`.`);
5738
- commentBody += `
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 = `
5739
5772
 
5740
- **Compliance Warning: Lula annotations were removed from \`${file.filename}\`**
5773
+ **Compliance Warning: Lula annotations were removed from \`${filename}\`**
5741
5774
 
5742
5775
  `;
5743
- commentBody += `The following compliance annotation blocks were present in the original file but are missing in the updated version:
5776
+ content += `The following compliance annotation blocks were present in the original file but are missing in the updated version:
5744
5777
 
5745
5778
  `;
5746
- commentBody += `| File | Original Lines | UUID |
5779
+ content += `| File | Original Lines | UUID |
5747
5780
  `;
5748
- commentBody += `| ---- | -------------- | ---- |
5781
+ content += `| ---- | -------------- | ---- |
5749
5782
  `;
5750
- for (const block of removedBlocks) {
5751
- const oldBlockText = oldText.split("\n").slice(block.startLine, block.endLine).join("\n");
5752
- const blockSha256 = createHash2("sha256").update(oldBlockText).digest("hex");
5753
- commentBody += `| \`${file.filename}\` | \`${block.startLine + 1}\u2013${block.endLine}\` | \`${block.uuid}\` |
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}\` |
5754
5787
  `;
5755
- commentBody += `> **sha256** \`${blockSha256}\`
5788
+ content += `> **sha256** \`${blockSha256}\`
5756
5789
 
5757
5790
  `;
5758
- }
5759
- commentBody += `Please review whether:
5791
+ }
5792
+ content += `Please review whether:
5760
5793
  `;
5761
- commentBody += `- The removal of these compliance annotations is intentional
5794
+ content += `- The removal of these compliance annotations is intentional
5762
5795
  `;
5763
- commentBody += `- Alternative compliance measures have been implemented
5796
+ content += `- Alternative compliance measures have been implemented
5764
5797
  `;
5765
- commentBody += `- The compliance coverage is still adequate
5798
+ content += `- The compliance coverage is still adequate
5766
5799
 
5767
5800
  `;
5768
- commentBody += `---
5801
+ content += `---
5769
5802
 
5770
5803
  `;
5771
- }
5772
- } catch (err) {
5773
- console.error(`Error processing ${file.filename}: ${err}`);
5774
- }
5775
- }
5776
- if (opts.postMode === "comment") {
5777
- await deleteOldIssueComments({ octokit, owner, repo, pull_number });
5778
- } else {
5779
- await dismissOldReviews({ octokit, owner, repo, pull_number });
5780
- await deleteOldReviewComments({ octokit, owner, repo, pull_number });
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}`);
5781
5829
  }
5782
- if (leavePost) {
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;
5783
5879
  await postFinding({
5784
5880
  octokit,
5785
5881
  postMode: opts.postMode,
5786
5882
  owner,
5787
5883
  repo,
5788
5884
  pull_number,
5789
- body: commentBody + closingBody
5885
+ body: finalBody
5790
5886
  });
5791
5887
  const header = `Posted (${opts.postMode})`;
5792
5888
  const underline = "-".repeat(header.length);
@@ -5794,7 +5890,7 @@ Please review whether:
5794
5890
  ${header}
5795
5891
  ${underline}
5796
5892
 
5797
- ${commentBody + closingBody}
5893
+ ${finalBody}
5798
5894
 
5799
5895
  `);
5800
5896
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lula2",
3
- "version": "0.6.3-nightly.0",
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"
@@ -1 +0,0 @@
1
- import{l as o,a as r}from"../chunks/rc9CiIba.js";export{o as load_css,r as start};