lula2 0.7.5 → 0.8.4-nightly.0
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 +2 -2
- package/dist/_app/immutable/assets/{0.DqWJrcPI.css → 0.DLu2XH4u.css} +1 -1
- package/dist/_app/immutable/chunks/{DsuS1uUo.js → BNu54jRO.js} +1 -1
- package/dist/_app/immutable/chunks/Bct7KINo.js +1 -0
- package/dist/_app/immutable/chunks/CC6oS456.js +1 -0
- package/dist/_app/immutable/chunks/CfYuj7nz.js +79 -0
- package/dist/_app/immutable/chunks/{BIdjJ0zz.js → D9WVNv7O.js} +1 -1
- package/dist/_app/immutable/chunks/DHuA7MQr.js +1 -0
- package/dist/_app/immutable/chunks/DSxRA67V.js +2 -0
- package/dist/_app/immutable/chunks/Dpd5zUJG.js +1 -0
- package/dist/_app/immutable/chunks/DznG4VMX.js +2 -0
- package/dist/_app/immutable/chunks/Ew6_cz_0.js +1 -0
- package/dist/_app/immutable/chunks/kRA7ZCNG.js +1 -0
- package/dist/_app/immutable/entry/{app.CjycYot0.js → app.DMutRaVE.js} +2 -2
- package/dist/_app/immutable/entry/start.BZfBvC8E.js +1 -0
- package/dist/_app/immutable/nodes/{0.CGKh5y4X.js → 0.jGNluNZR.js} +2 -2
- package/dist/_app/immutable/nodes/1.DO1jbRyK.js +1 -0
- package/dist/_app/immutable/nodes/{2.Hrl6uq-b.js → 2.CeUzIeUq.js} +1 -1
- package/dist/_app/immutable/nodes/3.BcMLTtzX.js +1 -0
- package/dist/_app/immutable/nodes/{4.DAVWsDkK.js → 4.CkamcV91.js} +7 -7
- package/dist/_app/version.json +1 -1
- package/dist/cli/commands/ui.js +98 -8
- package/dist/cli/server/index.js +98 -8
- package/dist/cli/server/server.js +98 -8
- package/dist/cli/server/serverState.js +40 -6
- package/dist/cli/server/websocketServer.js +1146 -1056
- package/dist/index.html +11 -11
- package/dist/index.js +98 -8
- package/package.json +127 -128
- package/src/lib/components/controls/MappingCard.svelte +6 -1
- package/src/lib/components/controls/MappingForm.svelte +36 -3
- package/src/lib/components/controls/renderers/EditableFieldRenderer.svelte +57 -0
- package/src/lib/components/controls/tabs/CustomFieldsTab.svelte +1 -0
- package/src/lib/components/controls/tabs/ImplementationTab.svelte +1 -0
- package/src/lib/components/controls/tabs/MappingsTab.svelte +39 -14
- package/src/lib/components/controls/tabs/OverviewTab.svelte +1 -0
- package/src/lib/components/forms/FormField.svelte +65 -3
- package/src/lib/types.ts +2 -1
- package/src/lib/websocket.ts +7 -0
- package/src/routes/control/[id]/+page.svelte +2 -2
- package/dist/_app/immutable/chunks/BI-GirXZ.js +0 -1
- package/dist/_app/immutable/chunks/BSyVkqhj.js +0 -2
- package/dist/_app/immutable/chunks/Cng7c2CG.js +0 -1
- package/dist/_app/immutable/chunks/CxBMFlfX.js +0 -1
- package/dist/_app/immutable/chunks/DArZRX9-.js +0 -65
- package/dist/_app/immutable/chunks/DH2IP9c7.js +0 -1
- package/dist/_app/immutable/chunks/DXSHWIjJ.js +0 -2
- package/dist/_app/immutable/chunks/urFjAlpd.js +0 -1
- package/dist/_app/immutable/entry/start.Bgy9x4Qb.js +0 -1
- package/dist/_app/immutable/nodes/1.D5L7DxSG.js +0 -1
- package/dist/_app/immutable/nodes/3.BoHxdRm3.js +0 -1
package/dist/cli/commands/ui.js
CHANGED
|
@@ -1668,6 +1668,7 @@ var fileStore_exports = {};
|
|
|
1668
1668
|
__export(fileStore_exports, {
|
|
1669
1669
|
FileStore: () => FileStore
|
|
1670
1670
|
});
|
|
1671
|
+
import { createHash as createHash2 } from "crypto";
|
|
1671
1672
|
import {
|
|
1672
1673
|
existsSync as existsSync2,
|
|
1673
1674
|
promises as fs,
|
|
@@ -1680,7 +1681,6 @@ import {
|
|
|
1680
1681
|
} from "fs";
|
|
1681
1682
|
import * as yaml2 from "js-yaml";
|
|
1682
1683
|
import { join as join2 } from "path";
|
|
1683
|
-
import { createHash as createHash2 } from "crypto";
|
|
1684
1684
|
var FileStore;
|
|
1685
1685
|
var init_fileStore = __esm({
|
|
1686
1686
|
"cli/server/infrastructure/fileStore.ts"() {
|
|
@@ -1700,6 +1700,40 @@ var init_fileStore = __esm({
|
|
|
1700
1700
|
this.refreshControlsCache();
|
|
1701
1701
|
}
|
|
1702
1702
|
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Update a single mapping in place, preserving file order
|
|
1705
|
+
*/
|
|
1706
|
+
async updateMapping(oldCompositeKey, updatedMapping) {
|
|
1707
|
+
const mappingFiles = this.getAllMappingFiles();
|
|
1708
|
+
for (const file of mappingFiles) {
|
|
1709
|
+
try {
|
|
1710
|
+
const content = readFileSync2(file, "utf8");
|
|
1711
|
+
let mappings = yaml2.load(content) || [];
|
|
1712
|
+
let changed = false;
|
|
1713
|
+
mappings = mappings.map((m) => {
|
|
1714
|
+
const hash = createHash2("sha256").update(JSON.stringify(m)).digest("hex");
|
|
1715
|
+
if (`${m.control_id}:${hash}` === oldCompositeKey) {
|
|
1716
|
+
const clean = { ...updatedMapping };
|
|
1717
|
+
delete clean.hash;
|
|
1718
|
+
changed = true;
|
|
1719
|
+
return clean;
|
|
1720
|
+
}
|
|
1721
|
+
return m;
|
|
1722
|
+
});
|
|
1723
|
+
if (changed) {
|
|
1724
|
+
const yamlContent = yaml2.dump(mappings, {
|
|
1725
|
+
indent: 2,
|
|
1726
|
+
lineWidth: -1,
|
|
1727
|
+
noRefs: true
|
|
1728
|
+
});
|
|
1729
|
+
writeFileSync(file, yamlContent, "utf8");
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
console.error(`Error processing mapping file ${file}:`, error);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1703
1737
|
/**
|
|
1704
1738
|
* Get simple filename from control ID
|
|
1705
1739
|
*/
|
|
@@ -1722,8 +1756,8 @@ var init_fileStore = __esm({
|
|
|
1722
1756
|
if (!this.baseDir || this.baseDir === "." || this.baseDir === process.cwd()) {
|
|
1723
1757
|
return;
|
|
1724
1758
|
}
|
|
1725
|
-
const
|
|
1726
|
-
if (!existsSync2(
|
|
1759
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1760
|
+
if (!existsSync2(lulaConfigPath)) {
|
|
1727
1761
|
return;
|
|
1728
1762
|
}
|
|
1729
1763
|
if (!existsSync2(this.controlsDir)) {
|
|
@@ -1882,10 +1916,10 @@ var init_fileStore = __esm({
|
|
|
1882
1916
|
return [];
|
|
1883
1917
|
}
|
|
1884
1918
|
let controlOrder = null;
|
|
1919
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1885
1920
|
try {
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
const content = readFileSync2(lulaConfigPath2, "utf8");
|
|
1921
|
+
if (existsSync2(lulaConfigPath)) {
|
|
1922
|
+
const content = readFileSync2(lulaConfigPath, "utf8");
|
|
1889
1923
|
const metadata = yaml2.load(content);
|
|
1890
1924
|
controlOrder = metadata?.controlOrder || null;
|
|
1891
1925
|
}
|
|
@@ -5139,12 +5173,12 @@ import { fileURLToPath } from "url";
|
|
|
5139
5173
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5140
5174
|
init_debug();
|
|
5141
5175
|
init_controlHelpers();
|
|
5142
|
-
init_serverState();
|
|
5143
5176
|
init_gitHistory();
|
|
5177
|
+
init_serverState();
|
|
5144
5178
|
import * as yaml5 from "js-yaml";
|
|
5179
|
+
import crypto2 from "node:crypto";
|
|
5145
5180
|
import { join as join5 } from "path";
|
|
5146
5181
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5147
|
-
import crypto2 from "node:crypto";
|
|
5148
5182
|
var WebSocketManager = class {
|
|
5149
5183
|
wss = null;
|
|
5150
5184
|
clients = /* @__PURE__ */ new Set();
|
|
@@ -5185,6 +5219,62 @@ var WebSocketManager = class {
|
|
|
5185
5219
|
}
|
|
5186
5220
|
break;
|
|
5187
5221
|
}
|
|
5222
|
+
case "update-mapping": {
|
|
5223
|
+
const state = getServerState();
|
|
5224
|
+
if (payload && payload.old_composite_key && payload.mapping) {
|
|
5225
|
+
const oldCompositeKey = payload.old_composite_key;
|
|
5226
|
+
const existing = state.mappingsCache.get(oldCompositeKey);
|
|
5227
|
+
if (!existing) {
|
|
5228
|
+
console.error("Mapping not found for update:", oldCompositeKey);
|
|
5229
|
+
break;
|
|
5230
|
+
}
|
|
5231
|
+
const incoming = payload.mapping;
|
|
5232
|
+
const updated = {
|
|
5233
|
+
...existing,
|
|
5234
|
+
...incoming,
|
|
5235
|
+
control_id: incoming.control_id || existing.control_id,
|
|
5236
|
+
uuid: incoming.uuid || existing.uuid
|
|
5237
|
+
};
|
|
5238
|
+
if (!updated.hash || updated.hash === "") {
|
|
5239
|
+
updated.hash = crypto2.createHash("sha256").update(JSON.stringify({ ...updated, hash: void 0 })).digest("hex");
|
|
5240
|
+
}
|
|
5241
|
+
const oldHash = existing.hash;
|
|
5242
|
+
const oldControlId = existing.control_id;
|
|
5243
|
+
const oldFamily = oldControlId.split("-")[0];
|
|
5244
|
+
const newHash = updated.hash;
|
|
5245
|
+
const newControlId = updated.control_id;
|
|
5246
|
+
const newFamily = newControlId.split("-")[0];
|
|
5247
|
+
const newCompositeKey = `${newControlId}:${newHash}`;
|
|
5248
|
+
await state.fileStore.updateMapping(oldCompositeKey, updated);
|
|
5249
|
+
const entries = Array.from(state.mappingsCache.entries());
|
|
5250
|
+
const oldIndex = entries.findIndex(([key]) => key === oldCompositeKey);
|
|
5251
|
+
if (oldIndex === -1) {
|
|
5252
|
+
state.mappingsCache.delete(oldCompositeKey);
|
|
5253
|
+
state.mappingsCache.set(newCompositeKey, updated);
|
|
5254
|
+
} else {
|
|
5255
|
+
entries[oldIndex] = [newCompositeKey, updated];
|
|
5256
|
+
state.mappingsCache = new Map(entries);
|
|
5257
|
+
}
|
|
5258
|
+
state.mappingsByFamily.get(oldFamily)?.delete(oldHash);
|
|
5259
|
+
state.mappingsByControl.get(oldControlId)?.delete(oldHash);
|
|
5260
|
+
if (!state.mappingsByFamily.has(newFamily)) {
|
|
5261
|
+
state.mappingsByFamily.set(newFamily, /* @__PURE__ */ new Set());
|
|
5262
|
+
}
|
|
5263
|
+
state.mappingsByFamily.get(newFamily).add(newHash);
|
|
5264
|
+
if (!state.mappingsByControl.has(newControlId)) {
|
|
5265
|
+
state.mappingsByControl.set(newControlId, /* @__PURE__ */ new Set());
|
|
5266
|
+
}
|
|
5267
|
+
state.mappingsByControl.get(newControlId).add(newHash);
|
|
5268
|
+
ws.send(
|
|
5269
|
+
JSON.stringify({
|
|
5270
|
+
type: "mapping-updated",
|
|
5271
|
+
payload: { uuid: updated.uuid, success: true }
|
|
5272
|
+
})
|
|
5273
|
+
);
|
|
5274
|
+
this.broadcastState();
|
|
5275
|
+
}
|
|
5276
|
+
break;
|
|
5277
|
+
}
|
|
5188
5278
|
case "refresh-controls": {
|
|
5189
5279
|
const state = getServerState();
|
|
5190
5280
|
state.controlsCache.clear();
|
package/dist/cli/server/index.js
CHANGED
|
@@ -1650,6 +1650,7 @@ var fileStore_exports = {};
|
|
|
1650
1650
|
__export(fileStore_exports, {
|
|
1651
1651
|
FileStore: () => FileStore
|
|
1652
1652
|
});
|
|
1653
|
+
import { createHash as createHash2 } from "crypto";
|
|
1653
1654
|
import {
|
|
1654
1655
|
existsSync as existsSync2,
|
|
1655
1656
|
promises as fs,
|
|
@@ -1662,7 +1663,6 @@ import {
|
|
|
1662
1663
|
} from "fs";
|
|
1663
1664
|
import * as yaml2 from "js-yaml";
|
|
1664
1665
|
import { join as join2 } from "path";
|
|
1665
|
-
import { createHash as createHash2 } from "crypto";
|
|
1666
1666
|
var FileStore;
|
|
1667
1667
|
var init_fileStore = __esm({
|
|
1668
1668
|
"cli/server/infrastructure/fileStore.ts"() {
|
|
@@ -1682,6 +1682,40 @@ var init_fileStore = __esm({
|
|
|
1682
1682
|
this.refreshControlsCache();
|
|
1683
1683
|
}
|
|
1684
1684
|
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Update a single mapping in place, preserving file order
|
|
1687
|
+
*/
|
|
1688
|
+
async updateMapping(oldCompositeKey, updatedMapping) {
|
|
1689
|
+
const mappingFiles = this.getAllMappingFiles();
|
|
1690
|
+
for (const file of mappingFiles) {
|
|
1691
|
+
try {
|
|
1692
|
+
const content = readFileSync2(file, "utf8");
|
|
1693
|
+
let mappings = yaml2.load(content) || [];
|
|
1694
|
+
let changed = false;
|
|
1695
|
+
mappings = mappings.map((m) => {
|
|
1696
|
+
const hash = createHash2("sha256").update(JSON.stringify(m)).digest("hex");
|
|
1697
|
+
if (`${m.control_id}:${hash}` === oldCompositeKey) {
|
|
1698
|
+
const clean = { ...updatedMapping };
|
|
1699
|
+
delete clean.hash;
|
|
1700
|
+
changed = true;
|
|
1701
|
+
return clean;
|
|
1702
|
+
}
|
|
1703
|
+
return m;
|
|
1704
|
+
});
|
|
1705
|
+
if (changed) {
|
|
1706
|
+
const yamlContent = yaml2.dump(mappings, {
|
|
1707
|
+
indent: 2,
|
|
1708
|
+
lineWidth: -1,
|
|
1709
|
+
noRefs: true
|
|
1710
|
+
});
|
|
1711
|
+
writeFileSync(file, yamlContent, "utf8");
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
console.error(`Error processing mapping file ${file}:`, error);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1685
1719
|
/**
|
|
1686
1720
|
* Get simple filename from control ID
|
|
1687
1721
|
*/
|
|
@@ -1704,8 +1738,8 @@ var init_fileStore = __esm({
|
|
|
1704
1738
|
if (!this.baseDir || this.baseDir === "." || this.baseDir === process.cwd()) {
|
|
1705
1739
|
return;
|
|
1706
1740
|
}
|
|
1707
|
-
const
|
|
1708
|
-
if (!existsSync2(
|
|
1741
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1742
|
+
if (!existsSync2(lulaConfigPath)) {
|
|
1709
1743
|
return;
|
|
1710
1744
|
}
|
|
1711
1745
|
if (!existsSync2(this.controlsDir)) {
|
|
@@ -1864,10 +1898,10 @@ var init_fileStore = __esm({
|
|
|
1864
1898
|
return [];
|
|
1865
1899
|
}
|
|
1866
1900
|
let controlOrder = null;
|
|
1901
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1867
1902
|
try {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const content = readFileSync2(lulaConfigPath2, "utf8");
|
|
1903
|
+
if (existsSync2(lulaConfigPath)) {
|
|
1904
|
+
const content = readFileSync2(lulaConfigPath, "utf8");
|
|
1871
1905
|
const metadata = yaml2.load(content);
|
|
1872
1906
|
controlOrder = metadata?.controlOrder || null;
|
|
1873
1907
|
}
|
|
@@ -5116,12 +5150,12 @@ import { fileURLToPath } from "url";
|
|
|
5116
5150
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5117
5151
|
init_debug();
|
|
5118
5152
|
init_controlHelpers();
|
|
5119
|
-
init_serverState();
|
|
5120
5153
|
init_gitHistory();
|
|
5154
|
+
init_serverState();
|
|
5121
5155
|
import * as yaml5 from "js-yaml";
|
|
5156
|
+
import crypto2 from "node:crypto";
|
|
5122
5157
|
import { join as join5 } from "path";
|
|
5123
5158
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5124
|
-
import crypto2 from "node:crypto";
|
|
5125
5159
|
var WebSocketManager = class {
|
|
5126
5160
|
wss = null;
|
|
5127
5161
|
clients = /* @__PURE__ */ new Set();
|
|
@@ -5162,6 +5196,62 @@ var WebSocketManager = class {
|
|
|
5162
5196
|
}
|
|
5163
5197
|
break;
|
|
5164
5198
|
}
|
|
5199
|
+
case "update-mapping": {
|
|
5200
|
+
const state = getServerState();
|
|
5201
|
+
if (payload && payload.old_composite_key && payload.mapping) {
|
|
5202
|
+
const oldCompositeKey = payload.old_composite_key;
|
|
5203
|
+
const existing = state.mappingsCache.get(oldCompositeKey);
|
|
5204
|
+
if (!existing) {
|
|
5205
|
+
console.error("Mapping not found for update:", oldCompositeKey);
|
|
5206
|
+
break;
|
|
5207
|
+
}
|
|
5208
|
+
const incoming = payload.mapping;
|
|
5209
|
+
const updated = {
|
|
5210
|
+
...existing,
|
|
5211
|
+
...incoming,
|
|
5212
|
+
control_id: incoming.control_id || existing.control_id,
|
|
5213
|
+
uuid: incoming.uuid || existing.uuid
|
|
5214
|
+
};
|
|
5215
|
+
if (!updated.hash || updated.hash === "") {
|
|
5216
|
+
updated.hash = crypto2.createHash("sha256").update(JSON.stringify({ ...updated, hash: void 0 })).digest("hex");
|
|
5217
|
+
}
|
|
5218
|
+
const oldHash = existing.hash;
|
|
5219
|
+
const oldControlId = existing.control_id;
|
|
5220
|
+
const oldFamily = oldControlId.split("-")[0];
|
|
5221
|
+
const newHash = updated.hash;
|
|
5222
|
+
const newControlId = updated.control_id;
|
|
5223
|
+
const newFamily = newControlId.split("-")[0];
|
|
5224
|
+
const newCompositeKey = `${newControlId}:${newHash}`;
|
|
5225
|
+
await state.fileStore.updateMapping(oldCompositeKey, updated);
|
|
5226
|
+
const entries = Array.from(state.mappingsCache.entries());
|
|
5227
|
+
const oldIndex = entries.findIndex(([key]) => key === oldCompositeKey);
|
|
5228
|
+
if (oldIndex === -1) {
|
|
5229
|
+
state.mappingsCache.delete(oldCompositeKey);
|
|
5230
|
+
state.mappingsCache.set(newCompositeKey, updated);
|
|
5231
|
+
} else {
|
|
5232
|
+
entries[oldIndex] = [newCompositeKey, updated];
|
|
5233
|
+
state.mappingsCache = new Map(entries);
|
|
5234
|
+
}
|
|
5235
|
+
state.mappingsByFamily.get(oldFamily)?.delete(oldHash);
|
|
5236
|
+
state.mappingsByControl.get(oldControlId)?.delete(oldHash);
|
|
5237
|
+
if (!state.mappingsByFamily.has(newFamily)) {
|
|
5238
|
+
state.mappingsByFamily.set(newFamily, /* @__PURE__ */ new Set());
|
|
5239
|
+
}
|
|
5240
|
+
state.mappingsByFamily.get(newFamily).add(newHash);
|
|
5241
|
+
if (!state.mappingsByControl.has(newControlId)) {
|
|
5242
|
+
state.mappingsByControl.set(newControlId, /* @__PURE__ */ new Set());
|
|
5243
|
+
}
|
|
5244
|
+
state.mappingsByControl.get(newControlId).add(newHash);
|
|
5245
|
+
ws.send(
|
|
5246
|
+
JSON.stringify({
|
|
5247
|
+
type: "mapping-updated",
|
|
5248
|
+
payload: { uuid: updated.uuid, success: true }
|
|
5249
|
+
})
|
|
5250
|
+
);
|
|
5251
|
+
this.broadcastState();
|
|
5252
|
+
}
|
|
5253
|
+
break;
|
|
5254
|
+
}
|
|
5165
5255
|
case "refresh-controls": {
|
|
5166
5256
|
const state = getServerState();
|
|
5167
5257
|
state.controlsCache.clear();
|
|
@@ -1650,6 +1650,7 @@ var fileStore_exports = {};
|
|
|
1650
1650
|
__export(fileStore_exports, {
|
|
1651
1651
|
FileStore: () => FileStore
|
|
1652
1652
|
});
|
|
1653
|
+
import { createHash as createHash2 } from "crypto";
|
|
1653
1654
|
import {
|
|
1654
1655
|
existsSync as existsSync2,
|
|
1655
1656
|
promises as fs,
|
|
@@ -1662,7 +1663,6 @@ import {
|
|
|
1662
1663
|
} from "fs";
|
|
1663
1664
|
import * as yaml2 from "js-yaml";
|
|
1664
1665
|
import { join as join2 } from "path";
|
|
1665
|
-
import { createHash as createHash2 } from "crypto";
|
|
1666
1666
|
var FileStore;
|
|
1667
1667
|
var init_fileStore = __esm({
|
|
1668
1668
|
"cli/server/infrastructure/fileStore.ts"() {
|
|
@@ -1682,6 +1682,40 @@ var init_fileStore = __esm({
|
|
|
1682
1682
|
this.refreshControlsCache();
|
|
1683
1683
|
}
|
|
1684
1684
|
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Update a single mapping in place, preserving file order
|
|
1687
|
+
*/
|
|
1688
|
+
async updateMapping(oldCompositeKey, updatedMapping) {
|
|
1689
|
+
const mappingFiles = this.getAllMappingFiles();
|
|
1690
|
+
for (const file of mappingFiles) {
|
|
1691
|
+
try {
|
|
1692
|
+
const content = readFileSync2(file, "utf8");
|
|
1693
|
+
let mappings = yaml2.load(content) || [];
|
|
1694
|
+
let changed = false;
|
|
1695
|
+
mappings = mappings.map((m) => {
|
|
1696
|
+
const hash = createHash2("sha256").update(JSON.stringify(m)).digest("hex");
|
|
1697
|
+
if (`${m.control_id}:${hash}` === oldCompositeKey) {
|
|
1698
|
+
const clean = { ...updatedMapping };
|
|
1699
|
+
delete clean.hash;
|
|
1700
|
+
changed = true;
|
|
1701
|
+
return clean;
|
|
1702
|
+
}
|
|
1703
|
+
return m;
|
|
1704
|
+
});
|
|
1705
|
+
if (changed) {
|
|
1706
|
+
const yamlContent = yaml2.dump(mappings, {
|
|
1707
|
+
indent: 2,
|
|
1708
|
+
lineWidth: -1,
|
|
1709
|
+
noRefs: true
|
|
1710
|
+
});
|
|
1711
|
+
writeFileSync(file, yamlContent, "utf8");
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
console.error(`Error processing mapping file ${file}:`, error);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1685
1719
|
/**
|
|
1686
1720
|
* Get simple filename from control ID
|
|
1687
1721
|
*/
|
|
@@ -1704,8 +1738,8 @@ var init_fileStore = __esm({
|
|
|
1704
1738
|
if (!this.baseDir || this.baseDir === "." || this.baseDir === process.cwd()) {
|
|
1705
1739
|
return;
|
|
1706
1740
|
}
|
|
1707
|
-
const
|
|
1708
|
-
if (!existsSync2(
|
|
1741
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1742
|
+
if (!existsSync2(lulaConfigPath)) {
|
|
1709
1743
|
return;
|
|
1710
1744
|
}
|
|
1711
1745
|
if (!existsSync2(this.controlsDir)) {
|
|
@@ -1864,10 +1898,10 @@ var init_fileStore = __esm({
|
|
|
1864
1898
|
return [];
|
|
1865
1899
|
}
|
|
1866
1900
|
let controlOrder = null;
|
|
1901
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
1867
1902
|
try {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const content = readFileSync2(lulaConfigPath2, "utf8");
|
|
1903
|
+
if (existsSync2(lulaConfigPath)) {
|
|
1904
|
+
const content = readFileSync2(lulaConfigPath, "utf8");
|
|
1871
1905
|
const metadata = yaml2.load(content);
|
|
1872
1906
|
controlOrder = metadata?.controlOrder || null;
|
|
1873
1907
|
}
|
|
@@ -5116,12 +5150,12 @@ import { fileURLToPath } from "url";
|
|
|
5116
5150
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5117
5151
|
init_debug();
|
|
5118
5152
|
init_controlHelpers();
|
|
5119
|
-
init_serverState();
|
|
5120
5153
|
init_gitHistory();
|
|
5154
|
+
init_serverState();
|
|
5121
5155
|
import * as yaml5 from "js-yaml";
|
|
5156
|
+
import crypto2 from "node:crypto";
|
|
5122
5157
|
import { join as join5 } from "path";
|
|
5123
5158
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5124
|
-
import crypto2 from "node:crypto";
|
|
5125
5159
|
var WebSocketManager = class {
|
|
5126
5160
|
wss = null;
|
|
5127
5161
|
clients = /* @__PURE__ */ new Set();
|
|
@@ -5162,6 +5196,62 @@ var WebSocketManager = class {
|
|
|
5162
5196
|
}
|
|
5163
5197
|
break;
|
|
5164
5198
|
}
|
|
5199
|
+
case "update-mapping": {
|
|
5200
|
+
const state = getServerState();
|
|
5201
|
+
if (payload && payload.old_composite_key && payload.mapping) {
|
|
5202
|
+
const oldCompositeKey = payload.old_composite_key;
|
|
5203
|
+
const existing = state.mappingsCache.get(oldCompositeKey);
|
|
5204
|
+
if (!existing) {
|
|
5205
|
+
console.error("Mapping not found for update:", oldCompositeKey);
|
|
5206
|
+
break;
|
|
5207
|
+
}
|
|
5208
|
+
const incoming = payload.mapping;
|
|
5209
|
+
const updated = {
|
|
5210
|
+
...existing,
|
|
5211
|
+
...incoming,
|
|
5212
|
+
control_id: incoming.control_id || existing.control_id,
|
|
5213
|
+
uuid: incoming.uuid || existing.uuid
|
|
5214
|
+
};
|
|
5215
|
+
if (!updated.hash || updated.hash === "") {
|
|
5216
|
+
updated.hash = crypto2.createHash("sha256").update(JSON.stringify({ ...updated, hash: void 0 })).digest("hex");
|
|
5217
|
+
}
|
|
5218
|
+
const oldHash = existing.hash;
|
|
5219
|
+
const oldControlId = existing.control_id;
|
|
5220
|
+
const oldFamily = oldControlId.split("-")[0];
|
|
5221
|
+
const newHash = updated.hash;
|
|
5222
|
+
const newControlId = updated.control_id;
|
|
5223
|
+
const newFamily = newControlId.split("-")[0];
|
|
5224
|
+
const newCompositeKey = `${newControlId}:${newHash}`;
|
|
5225
|
+
await state.fileStore.updateMapping(oldCompositeKey, updated);
|
|
5226
|
+
const entries = Array.from(state.mappingsCache.entries());
|
|
5227
|
+
const oldIndex = entries.findIndex(([key]) => key === oldCompositeKey);
|
|
5228
|
+
if (oldIndex === -1) {
|
|
5229
|
+
state.mappingsCache.delete(oldCompositeKey);
|
|
5230
|
+
state.mappingsCache.set(newCompositeKey, updated);
|
|
5231
|
+
} else {
|
|
5232
|
+
entries[oldIndex] = [newCompositeKey, updated];
|
|
5233
|
+
state.mappingsCache = new Map(entries);
|
|
5234
|
+
}
|
|
5235
|
+
state.mappingsByFamily.get(oldFamily)?.delete(oldHash);
|
|
5236
|
+
state.mappingsByControl.get(oldControlId)?.delete(oldHash);
|
|
5237
|
+
if (!state.mappingsByFamily.has(newFamily)) {
|
|
5238
|
+
state.mappingsByFamily.set(newFamily, /* @__PURE__ */ new Set());
|
|
5239
|
+
}
|
|
5240
|
+
state.mappingsByFamily.get(newFamily).add(newHash);
|
|
5241
|
+
if (!state.mappingsByControl.has(newControlId)) {
|
|
5242
|
+
state.mappingsByControl.set(newControlId, /* @__PURE__ */ new Set());
|
|
5243
|
+
}
|
|
5244
|
+
state.mappingsByControl.get(newControlId).add(newHash);
|
|
5245
|
+
ws.send(
|
|
5246
|
+
JSON.stringify({
|
|
5247
|
+
type: "mapping-updated",
|
|
5248
|
+
payload: { uuid: updated.uuid, success: true }
|
|
5249
|
+
})
|
|
5250
|
+
);
|
|
5251
|
+
this.broadcastState();
|
|
5252
|
+
}
|
|
5253
|
+
break;
|
|
5254
|
+
}
|
|
5165
5255
|
case "refresh-controls": {
|
|
5166
5256
|
const state = getServerState();
|
|
5167
5257
|
state.controlsCache.clear();
|
|
@@ -11,6 +11,7 @@ function debug(...args) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// cli/server/infrastructure/fileStore.ts
|
|
14
|
+
import { createHash } from "crypto";
|
|
14
15
|
import {
|
|
15
16
|
existsSync as existsSync2,
|
|
16
17
|
promises as fs,
|
|
@@ -89,7 +90,6 @@ function getControlId(control, baseDir) {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
// cli/server/infrastructure/fileStore.ts
|
|
92
|
-
import { createHash } from "crypto";
|
|
93
93
|
var FileStore = class {
|
|
94
94
|
baseDir;
|
|
95
95
|
controlsDir;
|
|
@@ -104,6 +104,40 @@ var FileStore = class {
|
|
|
104
104
|
this.refreshControlsCache();
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Update a single mapping in place, preserving file order
|
|
109
|
+
*/
|
|
110
|
+
async updateMapping(oldCompositeKey, updatedMapping) {
|
|
111
|
+
const mappingFiles = this.getAllMappingFiles();
|
|
112
|
+
for (const file of mappingFiles) {
|
|
113
|
+
try {
|
|
114
|
+
const content = readFileSync2(file, "utf8");
|
|
115
|
+
let mappings = yaml2.load(content) || [];
|
|
116
|
+
let changed = false;
|
|
117
|
+
mappings = mappings.map((m) => {
|
|
118
|
+
const hash = createHash("sha256").update(JSON.stringify(m)).digest("hex");
|
|
119
|
+
if (`${m.control_id}:${hash}` === oldCompositeKey) {
|
|
120
|
+
const clean = { ...updatedMapping };
|
|
121
|
+
delete clean.hash;
|
|
122
|
+
changed = true;
|
|
123
|
+
return clean;
|
|
124
|
+
}
|
|
125
|
+
return m;
|
|
126
|
+
});
|
|
127
|
+
if (changed) {
|
|
128
|
+
const yamlContent = yaml2.dump(mappings, {
|
|
129
|
+
indent: 2,
|
|
130
|
+
lineWidth: -1,
|
|
131
|
+
noRefs: true
|
|
132
|
+
});
|
|
133
|
+
writeFileSync(file, yamlContent, "utf8");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`Error processing mapping file ${file}:`, error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
107
141
|
/**
|
|
108
142
|
* Get simple filename from control ID
|
|
109
143
|
*/
|
|
@@ -126,8 +160,8 @@ var FileStore = class {
|
|
|
126
160
|
if (!this.baseDir || this.baseDir === "." || this.baseDir === process.cwd()) {
|
|
127
161
|
return;
|
|
128
162
|
}
|
|
129
|
-
const
|
|
130
|
-
if (!existsSync2(
|
|
163
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
164
|
+
if (!existsSync2(lulaConfigPath)) {
|
|
131
165
|
return;
|
|
132
166
|
}
|
|
133
167
|
if (!existsSync2(this.controlsDir)) {
|
|
@@ -286,10 +320,10 @@ var FileStore = class {
|
|
|
286
320
|
return [];
|
|
287
321
|
}
|
|
288
322
|
let controlOrder = null;
|
|
323
|
+
const lulaConfigPath = join2(this.baseDir, "lula.yaml");
|
|
289
324
|
try {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const content = readFileSync2(lulaConfigPath2, "utf8");
|
|
325
|
+
if (existsSync2(lulaConfigPath)) {
|
|
326
|
+
const content = readFileSync2(lulaConfigPath, "utf8");
|
|
293
327
|
const metadata = yaml2.load(content);
|
|
294
328
|
controlOrder = metadata?.controlOrder || null;
|
|
295
329
|
}
|