lula2 0.4.0 → 0.5.0-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.
Files changed (34) hide show
  1. package/dist/_app/immutable/assets/0.Dfpe5goI.css +1 -0
  2. package/dist/_app/immutable/chunks/{d2wclEM1.js → B3DV5AB9.js} +1 -1
  3. package/dist/_app/immutable/chunks/{DTNJlR4l.js → BBW9xKix.js} +19 -19
  4. package/dist/_app/immutable/chunks/{B8sFn9qB.js → BIY1u1I9.js} +1 -1
  5. package/dist/_app/immutable/chunks/{DPYPcVpy.js → BN4ish10.js} +1 -1
  6. package/dist/_app/immutable/chunks/{D6AXzSy_.js → BtwnwKFn.js} +1 -1
  7. package/dist/_app/immutable/chunks/{CgOk0Ct0.js → C2de20AA.js} +3 -3
  8. package/dist/_app/immutable/chunks/Cyp5c8fY.js +1 -0
  9. package/dist/_app/immutable/chunks/{DL7cUWpq.js → D6NghQtU.js} +1 -1
  10. package/dist/_app/immutable/chunks/{AMsv4DTs.js → DBN1r830.js} +1 -1
  11. package/dist/_app/immutable/entry/{app.DCeR7c2L.js → app.ChNLcnhL.js} +2 -2
  12. package/dist/_app/immutable/entry/start.CuWGVoVi.js +1 -0
  13. package/dist/_app/immutable/nodes/0.DnmE0r6A.js +2 -0
  14. package/dist/_app/immutable/nodes/{1.DZuIkAUv.js → 1.CPF_x4ZW.js} +1 -1
  15. package/dist/_app/immutable/nodes/{2.CaMZrOwr.js → 2.dJ7_0KZr.js} +1 -1
  16. package/dist/_app/immutable/nodes/{3.BCB0DxLi.js → 3.BhR2ddch.js} +1 -1
  17. package/dist/_app/immutable/nodes/{4.BlOrY_m9.js → 4.Djg06sYy.js} +1 -1
  18. package/dist/_app/version.json +1 -1
  19. package/dist/cli/commands/ui.js +265 -49
  20. package/dist/cli/server/index.js +265 -49
  21. package/dist/cli/server/server.js +265 -49
  22. package/dist/cli/server/spreadsheetRoutes.js +282 -59
  23. package/dist/cli/server/websocketServer.js +265 -49
  24. package/dist/index.html +10 -10
  25. package/dist/index.js +265 -49
  26. package/package.json +22 -23
  27. package/src/lib/components/dialogs/ExportColumnDialog.svelte +163 -0
  28. package/src/lib/components/dialogs/index.ts +4 -0
  29. package/src/routes/+layout.svelte +67 -5
  30. package/src/routes/control/[id]/+page.svelte +0 -1
  31. package/dist/_app/immutable/assets/0.D6CB7gA7.css +0 -1
  32. package/dist/_app/immutable/chunks/CBRQrpza.js +0 -1
  33. package/dist/_app/immutable/entry/start.Ca2qbK08.js +0 -1
  34. package/dist/_app/immutable/nodes/0.B2WQMmWy.js +0 -1
@@ -2967,7 +2967,10 @@ function extractFamilyFromControlId(controlId) {
2967
2967
  }
2968
2968
  return controlId.substring(0, 2).toUpperCase();
2969
2969
  }
2970
- function exportAsCSV(controls, metadata, res) {
2970
+ function exportAsCSV(controls, metadata, mappingsColumn, res) {
2971
+ return exportAsCSVWithMapping(controls, metadata, { mappings: mappingsColumn }, res);
2972
+ }
2973
+ function exportAsCSVWithMapping(controls, metadata, columnMappings, res) {
2971
2974
  const fieldSchema = metadata?.fieldSchema?.fields || {};
2972
2975
  const controlIdField = metadata?.control_id_field || "id";
2973
2976
  const allFields = /* @__PURE__ */ new Set();
@@ -2975,49 +2978,91 @@ function exportAsCSV(controls, metadata, res) {
2975
2978
  Object.keys(control).forEach((key) => allFields.add(key));
2976
2979
  });
2977
2980
  const fieldMapping = [];
2981
+ const usedDisplayNames = /* @__PURE__ */ new Set();
2978
2982
  if (allFields.has(controlIdField)) {
2979
2983
  const idSchema = fieldSchema[controlIdField];
2980
- fieldMapping.push({
2981
- fieldName: controlIdField,
2982
- displayName: idSchema?.original_name || "Control ID"
2983
- });
2984
+ const displayName = idSchema?.original_name || "Control ID";
2985
+ let isMappingColumn = false;
2986
+ if (columnMappings["mappings"]) {
2987
+ const targetDisplayName = columnMappings["mappings"];
2988
+ if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
2989
+ isMappingColumn = true;
2990
+ }
2991
+ }
2992
+ fieldMapping.push({ fieldName: controlIdField, displayName, isMappingColumn });
2993
+ usedDisplayNames.add(displayName.toLowerCase());
2984
2994
  allFields.delete(controlIdField);
2985
2995
  } else if (allFields.has("id")) {
2986
- fieldMapping.push({
2987
- fieldName: "id",
2988
- displayName: "Control ID"
2989
- });
2996
+ let isMappingColumn = false;
2997
+ const displayName = "Control ID";
2998
+ if (columnMappings["mappings"]) {
2999
+ const targetDisplayName = columnMappings["mappings"];
3000
+ if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
3001
+ isMappingColumn = true;
3002
+ }
3003
+ }
3004
+ fieldMapping.push({ fieldName: "id", displayName, isMappingColumn });
3005
+ usedDisplayNames.add("control id");
2990
3006
  allFields.delete("id");
2991
3007
  }
2992
3008
  if (allFields.has("family")) {
2993
3009
  const familySchema = fieldSchema["family"];
2994
- fieldMapping.push({
2995
- fieldName: "family",
2996
- displayName: familySchema?.original_name || "Family"
2997
- });
3010
+ const displayName = familySchema?.original_name || "Family";
3011
+ fieldMapping.push({ fieldName: "family", displayName });
3012
+ usedDisplayNames.add(displayName.toLowerCase());
2998
3013
  allFields.delete("family");
2999
3014
  }
3000
3015
  Array.from(allFields).filter((field) => field !== "mappings" && field !== "mappings_count").sort().forEach((field) => {
3001
3016
  const schema = fieldSchema[field];
3002
- const displayName = schema?.original_name || field.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
3003
- fieldMapping.push({ fieldName: field, displayName });
3017
+ const defaultDisplayName = schema?.original_name || field.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
3018
+ let isMappingColumn = false;
3019
+ let finalDisplayName = defaultDisplayName;
3020
+ if (columnMappings["mappings"]) {
3021
+ const targetDisplayName = columnMappings["mappings"];
3022
+ if (defaultDisplayName.toLowerCase() === targetDisplayName.toLowerCase()) {
3023
+ isMappingColumn = true;
3024
+ finalDisplayName = targetDisplayName;
3025
+ }
3026
+ }
3027
+ if (!usedDisplayNames.has(finalDisplayName.toLowerCase())) {
3028
+ fieldMapping.push({
3029
+ fieldName: field,
3030
+ displayName: finalDisplayName,
3031
+ isMappingColumn
3032
+ });
3033
+ usedDisplayNames.add(finalDisplayName.toLowerCase());
3034
+ }
3004
3035
  });
3005
- if (allFields.has("mappings_count")) {
3006
- fieldMapping.push({ fieldName: "mappings_count", displayName: "Mappings Count" });
3007
- }
3008
- if (allFields.has("mappings")) {
3036
+ if (allFields.has("mappings") && (!columnMappings["mappings"] || columnMappings["mappings"] === "Mappings")) {
3009
3037
  fieldMapping.push({ fieldName: "mappings", displayName: "Mappings" });
3010
3038
  }
3011
3039
  const csvRows = [];
3012
3040
  csvRows.push(fieldMapping.map((field) => `"${field.displayName}"`).join(","));
3013
3041
  controls.forEach((control) => {
3014
- const row = fieldMapping.map(({ fieldName }) => {
3015
- const value = control[fieldName];
3042
+ const row = fieldMapping.map(({ fieldName, isMappingColumn }) => {
3043
+ let value;
3044
+ if (isMappingColumn) {
3045
+ const mappingsValue = control["mappings"];
3046
+ if (Array.isArray(mappingsValue) && mappingsValue.length > 0) {
3047
+ const mappingsStr = mappingsValue.map((m) => m.description || m.justification || "").filter((desc) => desc && desc.trim() !== "").join("\n");
3048
+ if (mappingsStr.trim() !== "") {
3049
+ value = mappingsStr;
3050
+ } else {
3051
+ value = control[fieldName];
3052
+ }
3053
+ } else {
3054
+ value = control[fieldName];
3055
+ }
3056
+ } else {
3057
+ value = control[fieldName];
3058
+ }
3016
3059
  if (value === void 0 || value === null) return '""';
3017
3060
  if (fieldName === "mappings" && Array.isArray(value)) {
3018
- const mappingsStr = value.map(
3019
- (m) => `${m.status}: ${m.description.substring(0, 50)}${m.description.length > 50 ? "..." : ""}`
3020
- ).join("; ");
3061
+ const mappingsStr = value.map((m) => {
3062
+ const justification = m.description || m.justification || "";
3063
+ const status = m.status || "Unknown";
3064
+ return justification.trim() !== "" ? justification : `[${status}]`;
3065
+ }).join("\n");
3021
3066
  return `"${mappingsStr.replace(/"/g, '""')}"`;
3022
3067
  }
3023
3068
  if (Array.isArray(value)) return `"${value.join("; ").replace(/"/g, '""')}"`;
@@ -3032,32 +3077,101 @@ function exportAsCSV(controls, metadata, res) {
3032
3077
  res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
3033
3078
  res.send(csvContent);
3034
3079
  }
3035
- async function exportAsExcel(controls, metadata, res) {
3080
+ async function exportAsExcel(controls, metadata, mappingsColumn, res) {
3081
+ return await exportAsExcelWithMapping(controls, metadata, { mappings: mappingsColumn }, res);
3082
+ }
3083
+ async function exportAsExcelWithMapping(controls, metadata, columnMappings, res) {
3036
3084
  const fieldSchema = metadata?.fieldSchema?.fields || {};
3037
3085
  const controlIdField = metadata?.control_id_field || "id";
3038
- const worksheetData = controls.map((control) => {
3039
- const exportControl = {};
3040
- if (control[controlIdField]) {
3041
- const idSchema = fieldSchema[controlIdField];
3042
- const idDisplayName = idSchema?.original_name || "Control ID";
3043
- exportControl[idDisplayName] = control[controlIdField];
3044
- } else if (control.id) {
3045
- exportControl["Control ID"] = control.id;
3086
+ const allFields = /* @__PURE__ */ new Set();
3087
+ controls.forEach((control) => {
3088
+ Object.keys(control).forEach((key) => allFields.add(key));
3089
+ });
3090
+ const fieldMapping = [];
3091
+ const usedDisplayNames = /* @__PURE__ */ new Set();
3092
+ if (allFields.has(controlIdField)) {
3093
+ const idSchema = fieldSchema[controlIdField];
3094
+ const displayName = idSchema?.original_name || "Control ID";
3095
+ let isMappingColumn = false;
3096
+ if (columnMappings["mappings"]) {
3097
+ const targetDisplayName = columnMappings["mappings"];
3098
+ if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
3099
+ isMappingColumn = true;
3100
+ }
3046
3101
  }
3047
- if (control.family) {
3048
- const familySchema = fieldSchema["family"];
3049
- const familyDisplayName = familySchema?.original_name || "Family";
3050
- exportControl[familyDisplayName] = control.family;
3102
+ fieldMapping.push({ fieldName: controlIdField, displayName, isMappingColumn });
3103
+ usedDisplayNames.add(displayName.toLowerCase());
3104
+ allFields.delete(controlIdField);
3105
+ } else if (allFields.has("id")) {
3106
+ let isMappingColumn = false;
3107
+ const displayName = "Control ID";
3108
+ if (columnMappings["mappings"]) {
3109
+ const targetDisplayName = columnMappings["mappings"];
3110
+ if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
3111
+ isMappingColumn = true;
3112
+ }
3113
+ }
3114
+ fieldMapping.push({ fieldName: "id", displayName, isMappingColumn });
3115
+ usedDisplayNames.add("control id");
3116
+ allFields.delete("id");
3117
+ }
3118
+ if (allFields.has("family")) {
3119
+ const familySchema = fieldSchema["family"];
3120
+ const displayName = familySchema?.original_name || "Family";
3121
+ fieldMapping.push({ fieldName: "family", displayName });
3122
+ usedDisplayNames.add(displayName.toLowerCase());
3123
+ allFields.delete("family");
3124
+ }
3125
+ Array.from(allFields).filter((field) => field !== "mappings" && field !== "mappings_count").sort().forEach((field) => {
3126
+ const schema = fieldSchema[field];
3127
+ const defaultDisplayName = schema?.original_name || field.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
3128
+ let isMappingColumn = false;
3129
+ let finalDisplayName = defaultDisplayName;
3130
+ if (columnMappings["mappings"]) {
3131
+ const targetDisplayName = columnMappings["mappings"];
3132
+ if (defaultDisplayName.toLowerCase() === targetDisplayName.toLowerCase()) {
3133
+ isMappingColumn = true;
3134
+ finalDisplayName = targetDisplayName;
3135
+ }
3136
+ }
3137
+ if (!usedDisplayNames.has(finalDisplayName.toLowerCase())) {
3138
+ fieldMapping.push({
3139
+ fieldName: field,
3140
+ displayName: finalDisplayName,
3141
+ isMappingColumn
3142
+ });
3143
+ usedDisplayNames.add(finalDisplayName.toLowerCase());
3051
3144
  }
3052
- Object.keys(control).forEach((key) => {
3053
- if (key === controlIdField || key === "id" || key === "family") return;
3054
- const schema = fieldSchema[key];
3055
- const displayName = schema?.original_name || (key === "mappings_count" ? "Mappings Count" : key === "mappings" ? "Mappings" : key.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()));
3056
- const value = control[key];
3057
- if (key === "mappings" && Array.isArray(value)) {
3058
- exportControl[displayName] = value.map(
3059
- (m) => `${m.status}: ${m.description.substring(0, 100)}${m.description.length > 100 ? "..." : ""}`
3060
- ).join("\n");
3145
+ });
3146
+ if (allFields.has("mappings") && (!columnMappings["mappings"] || columnMappings["mappings"] === "Mappings")) {
3147
+ fieldMapping.push({ fieldName: "mappings", displayName: "Mappings" });
3148
+ }
3149
+ const worksheetData = controls.map((control) => {
3150
+ const exportControl = {};
3151
+ fieldMapping.forEach(({ fieldName, displayName, isMappingColumn }) => {
3152
+ let value;
3153
+ if (isMappingColumn) {
3154
+ const mappingsValue = control["mappings"];
3155
+ if (Array.isArray(mappingsValue) && mappingsValue.length > 0) {
3156
+ const mappingsStr = mappingsValue.map((m) => m.description || m.justification || "").filter((desc) => desc && desc.trim() !== "").join("\n");
3157
+ if (mappingsStr.trim() !== "") {
3158
+ value = mappingsStr;
3159
+ } else {
3160
+ value = control[fieldName];
3161
+ }
3162
+ } else {
3163
+ value = control[fieldName];
3164
+ }
3165
+ } else {
3166
+ value = control[fieldName];
3167
+ }
3168
+ if (fieldName === "mappings" && Array.isArray(value)) {
3169
+ const mappingsStr = value.map((m) => {
3170
+ const justification = m.description || m.justification || "";
3171
+ const status = m.status || "Unknown";
3172
+ return justification.trim() !== "" ? justification : `[${status}]`;
3173
+ }).join("\n");
3174
+ exportControl[displayName] = mappingsStr;
3061
3175
  } else if (Array.isArray(value)) {
3062
3176
  exportControl[displayName] = value.join("; ");
3063
3177
  } else if (typeof value === "object" && value !== null) {
@@ -3471,6 +3585,7 @@ var init_spreadsheetRoutes = __esm({
3471
3585
  router.get("/export-controls", async (req, res) => {
3472
3586
  try {
3473
3587
  const format = req.query.format || "csv";
3588
+ const mappingsColumn = req.query.mappingsColumn || "Mappings";
3474
3589
  const state = getServerState();
3475
3590
  const fileStore = state.fileStore;
3476
3591
  if (!fileStore) {
@@ -3480,7 +3595,8 @@ var init_spreadsheetRoutes = __esm({
3480
3595
  const mappings = await fileStore.loadMappings();
3481
3596
  let metadata = {};
3482
3597
  try {
3483
- const metadataPath2 = join4(state.CONTROL_SET_DIR, "lula.yaml");
3598
+ const controlSetPath = getCurrentControlSetPath();
3599
+ const metadataPath2 = join4(controlSetPath, "lula.yaml");
3484
3600
  if (existsSync3(metadataPath2)) {
3485
3601
  const metadataContent = readFileSync3(metadataPath2, "utf8");
3486
3602
  metadata = yaml4.load(metadataContent);
@@ -3508,10 +3624,10 @@ var init_spreadsheetRoutes = __esm({
3508
3624
  debug(`Exporting ${controlsWithMappings.length} controls as ${format}`);
3509
3625
  switch (format.toLowerCase()) {
3510
3626
  case "csv":
3511
- return exportAsCSV(controlsWithMappings, metadata, res);
3627
+ return exportAsCSV(controlsWithMappings, metadata, mappingsColumn, res);
3512
3628
  case "excel":
3513
3629
  case "xlsx":
3514
- return await exportAsExcel(controlsWithMappings, metadata, res);
3630
+ return await exportAsExcel(controlsWithMappings, metadata, mappingsColumn, res);
3515
3631
  case "json":
3516
3632
  return exportAsJSON(controlsWithMappings, metadata, res);
3517
3633
  default:
@@ -3522,6 +3638,106 @@ var init_spreadsheetRoutes = __esm({
3522
3638
  res.status(500).json({ error: error.message });
3523
3639
  }
3524
3640
  });
3641
+ router.get("/export-column-headers", async (req, res) => {
3642
+ try {
3643
+ let metadata = {};
3644
+ try {
3645
+ const controlSetPath = getCurrentControlSetPath();
3646
+ const metadataPath2 = join4(controlSetPath, "lula.yaml");
3647
+ if (existsSync3(metadataPath2)) {
3648
+ const metadataContent = readFileSync3(metadataPath2, "utf8");
3649
+ metadata = yaml4.load(metadataContent);
3650
+ } else {
3651
+ return res.status(404).json({ error: `No lula.yaml file found in control set path: ${controlSetPath}` });
3652
+ }
3653
+ } catch {
3654
+ return res.status(500).json({ error: "Failed to read lula.yaml file" });
3655
+ }
3656
+ const fieldSchema = metadata?.fieldSchema?.fields || {};
3657
+ const controlIdField = metadata?.control_id_field || "id";
3658
+ const columnHeaders = [];
3659
+ if (fieldSchema[controlIdField]) {
3660
+ const idSchema = fieldSchema[controlIdField];
3661
+ const displayName = idSchema?.original_name || "Control ID";
3662
+ columnHeaders.push({
3663
+ value: displayName,
3664
+ label: displayName
3665
+ });
3666
+ } else {
3667
+ columnHeaders.push({ value: "Control ID", label: "Control ID" });
3668
+ }
3669
+ if (fieldSchema["family"]) {
3670
+ const familySchema = fieldSchema["family"];
3671
+ const displayName = familySchema?.original_name || "Family";
3672
+ columnHeaders.push({
3673
+ value: displayName,
3674
+ label: displayName
3675
+ });
3676
+ }
3677
+ Object.entries(fieldSchema).forEach(([fieldName, schema]) => {
3678
+ if (fieldName === controlIdField || fieldName === "family" || fieldName === "mappings" || fieldName === "mappings_count") {
3679
+ return;
3680
+ }
3681
+ const displayName = schema?.original_name || fieldName.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
3682
+ columnHeaders.push({
3683
+ value: displayName,
3684
+ label: displayName
3685
+ });
3686
+ });
3687
+ columnHeaders.push({ value: "Mappings", label: "Mappings (Default)" });
3688
+ res.json({
3689
+ columnHeaders,
3690
+ defaultColumn: "Mappings"
3691
+ });
3692
+ } catch (error) {
3693
+ res.status(500).json({ error: error.message });
3694
+ }
3695
+ });
3696
+ router.post("/export-csv", async (req, res) => {
3697
+ try {
3698
+ const { format = "csv", _columns = [], columnMappings = {} } = req.body;
3699
+ const state = getServerState();
3700
+ const fileStore = state.fileStore;
3701
+ if (!fileStore) {
3702
+ return res.status(500).json({ error: "No control set loaded" });
3703
+ }
3704
+ const controls = await fileStore.loadAllControls();
3705
+ const mappings = await fileStore.loadMappings();
3706
+ let metadata = {};
3707
+ try {
3708
+ const controlSetPath = getCurrentControlSetPath();
3709
+ const metadataPath2 = join4(controlSetPath, "lula.yaml");
3710
+ if (existsSync3(metadataPath2)) {
3711
+ const metadataContent = readFileSync3(metadataPath2, "utf8");
3712
+ metadata = yaml4.load(metadataContent);
3713
+ }
3714
+ } catch (err) {
3715
+ debug("Could not load metadata:", err);
3716
+ }
3717
+ if (!controls || controls.length === 0) {
3718
+ return res.status(404).json({ error: "No controls found" });
3719
+ }
3720
+ const controlIdField = metadata?.control_id_field || "id";
3721
+ const controlsWithMappings = controls.map((control) => {
3722
+ const controlId = control[controlIdField] || control.id;
3723
+ const controlMappings = mappings.filter((m) => m.control_id === controlId);
3724
+ return {
3725
+ ...control,
3726
+ mappings_count: controlMappings.length,
3727
+ mappings: controlMappings.map((m) => ({
3728
+ uuid: m.uuid,
3729
+ status: m.status,
3730
+ description: m.justification || ""
3731
+ }))
3732
+ };
3733
+ });
3734
+ debug(`Exporting ${controlsWithMappings.length} controls as ${format} with column mappings`);
3735
+ return exportAsCSVWithMapping(controlsWithMappings, metadata, columnMappings, res);
3736
+ } catch (error) {
3737
+ console.error("Export error:", error);
3738
+ res.status(500).json({ error: error.message });
3739
+ }
3740
+ });
3525
3741
  router.post("/parse-excel", upload.single("file"), async (req, res) => {
3526
3742
  try {
3527
3743
  if (!req.file) {