filegrc 0.3.2 → 0.3.4
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/model/v1.json +8 -5
- package/package.json +1 -1
- package/src/build.js +3 -1
- package/src/cli.js +19 -7
- package/src/favicon.js +3 -108
- package/src/favicon.png +0 -0
- package/src/git-name.js +16 -0
- package/src/git.js +499 -1
- package/src/index.js +4 -1
- package/src/logo-mark-white.png +0 -0
- package/src/mutation.js +4 -1
- package/src/server.js +89 -20
- package/src/startup.js +5 -0
- package/src/state.js +7 -2
- package/src/validate.js +2 -0
- package/src/web.js +85 -17
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ npx filegrc prepare-audit audit-id
|
|
|
34
34
|
npx filegrc evidence-packet --start 2026-01-01 --end 2026-06-30 --audit audit-id
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`filegrc serve --help` prints bind, port, environment, and safety options without starting the server. The editable server defaults to `127.0.0.1:8787`; set `FILEGRC_HOST`, `FILEGRC_PORT`, or the matching flags when needed.
|
|
37
|
+
`filegrc serve --help` prints bind, port, environment, and safety options without starting the server. The editable server defaults to `127.0.0.1:8787`; set `FILEGRC_HOST`, `FILEGRC_PORT`, or the matching flags when needed. In trunk mode, browser saves synchronize, commit, and push from the authoritative branch. Use `--allow-non-authoritative-writes` for local development in a task checkout; the override never commits or pushes.
|
|
38
38
|
|
|
39
39
|
`filegrc setup` provides the headless equivalent of browser onboarding. Run it without arguments for guided terminal setup, or pass all initial service-boundary fields and a management program goal as flags or a JSON payload. Add `--preview` to validate and inspect the planned service and workspace writes without saving. Add `--summary --json` for compact agent output. Selecting Type 1 or Type 2 updates the workspace goal and selected systems. Setup does not select framework records, link controls, create evidence, or create an audit record.
|
|
40
40
|
|
|
@@ -54,7 +54,7 @@ Use `filegrc attach <evidence-id> <source-file>` to copy a fixed evidence file u
|
|
|
54
54
|
|
|
55
55
|
Use `filegrc detach <evidence-id> <attachment-name> --yes` for explicit removal. Evidence records with linked local attachments cannot be deleted.
|
|
56
56
|
|
|
57
|
-
The package requires Node.js 20 or newer. It uses Git for authors, commit timestamps, messages, diffs, and revisions.
|
|
57
|
+
The package requires Node.js 20 or newer. It uses Git for authors, commit timestamps, messages, diffs, and revisions. New workspaces use trunk mode, which fetches and fast-forwards before each browser mutation, validates and commits the saved change, then pushes. Existing settings without `repositoryMode` keep manual browser Git behavior. Agents and terminal users use Git directly; the filegrc CLI does not wrap pull, commit, or push.
|
|
58
58
|
|
|
59
59
|
The editable server has no authentication and binds to loopback by default. Put it behind trusted authentication before exposing it on a network, or publish the read-only static build.
|
|
60
60
|
|
package/model/v1.json
CHANGED
|
@@ -104,17 +104,20 @@
|
|
|
104
104
|
"pluralTitle": "Renderer settings",
|
|
105
105
|
"group": "repository",
|
|
106
106
|
"singleton": "renderer.json",
|
|
107
|
-
"description": "Optional local interface settings, including onboarding visibility
|
|
107
|
+
"description": "Optional local interface and browser repository settings, including onboarding visibility, manually completed Step pages, and authoritative-branch synchronization. Renderer settings are not required for SOC 2 and do not change compliance records.",
|
|
108
108
|
"guidance": {
|
|
109
|
-
"policyBasis": "Renderer settings are a filegrc convenience, not a SOC 2 requirement, control, audit record, or substitute for evidence.",
|
|
110
|
-
"cadence": "Change it when the team wants to rerun or suppress an optional renderer workflow
|
|
109
|
+
"policyBasis": "Renderer settings are a filegrc convenience, not a SOC 2 requirement, control, audit record, or substitute for evidence. Record lifecycle status represents approval; Git branches do not.",
|
|
110
|
+
"cadence": "Change it when the team wants to rerun or suppress an optional renderer workflow or change repository synchronization. Existing workspaces without repositoryMode keep manual browser Git behavior."
|
|
111
111
|
},
|
|
112
112
|
"required": ["showOnboarding"],
|
|
113
113
|
"fields": {
|
|
114
114
|
"showOnboarding": {"type":"boolean","label":"Show onboarding"},
|
|
115
|
-
"completedStagePageIds": {"type":"array","items":"string","label":"Manually completed program pages"}
|
|
115
|
+
"completedStagePageIds": {"type":"array","items":"string","label":"Manually completed program pages"},
|
|
116
|
+
"repositoryMode": {"type":"enum","values":["trunk","manual"],"label":"Repository mode"},
|
|
117
|
+
"authoritativeBranch": {"type":"string","format":"git-name","label":"Authoritative branch (defaults to main)"},
|
|
118
|
+
"repositoryRemote": {"type":"string","format":"git-name","label":"Repository remote (defaults to origin)"}
|
|
116
119
|
},
|
|
117
|
-
"listFields": ["title","showOnboarding","completedStagePageIds"]
|
|
120
|
+
"listFields": ["title","showOnboarding","repositoryMode","authoritativeBranch","repositoryRemote","completedStagePageIds"]
|
|
118
121
|
},
|
|
119
122
|
"person": {
|
|
120
123
|
"title": "Person",
|
package/package.json
CHANGED
package/src/build.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { FAVICON_PNG } from "./favicon.js";
|
|
3
|
+
import { FAVICON_PNG, LOGO_MARK_PNG } from "./favicon.js";
|
|
4
4
|
import { resolveWorkspacePath, resolveWorkspaceRoot } from "./paths.js";
|
|
5
5
|
import { createAppState } from "./state.js";
|
|
6
6
|
import { APP_SCRIPT, APP_STYLES, renderIndex } from "./web.js";
|
|
@@ -12,6 +12,7 @@ export async function buildWorkspace(input = process.cwd(), options = {}) {
|
|
|
12
12
|
const paths = {
|
|
13
13
|
html: resolveWorkspacePath(root, join(outputOption, "index.html")),
|
|
14
14
|
favicon: resolveWorkspacePath(root, join(outputOption, "favicon.png")),
|
|
15
|
+
logoMark: resolveWorkspacePath(root, join(outputOption, "logo-mark-white.png")),
|
|
15
16
|
script: resolveWorkspacePath(root, join(outputOption, "filegrc-app.js")),
|
|
16
17
|
styles: resolveWorkspacePath(root, join(outputOption, "filegrc.css"))
|
|
17
18
|
};
|
|
@@ -20,6 +21,7 @@ export async function buildWorkspace(input = process.cwd(), options = {}) {
|
|
|
20
21
|
await Promise.all([
|
|
21
22
|
writeFile(paths.html, renderIndex(state), "utf8"),
|
|
22
23
|
writeFile(paths.favicon, FAVICON_PNG),
|
|
24
|
+
writeFile(paths.logoMark, LOGO_MARK_PNG),
|
|
23
25
|
writeFile(paths.script, APP_SCRIPT, "utf8"),
|
|
24
26
|
writeFile(paths.styles, APP_STYLES, "utf8")
|
|
25
27
|
]);
|
package/src/cli.js
CHANGED
|
@@ -29,12 +29,14 @@ import { markdownEntries } from "./resource-markdown.js";
|
|
|
29
29
|
import { searchResources } from "./search.js";
|
|
30
30
|
import { serveWorkspace } from "./server.js";
|
|
31
31
|
import { planWorkspaceSetup, setupWorkspace, summarizeSetupResult } from "./setup.js";
|
|
32
|
+
import { printGithubStarMessage } from "./startup.js";
|
|
32
33
|
import { createAppState } from "./state.js";
|
|
33
34
|
import { currentCalendarDate } from "./time.js";
|
|
34
35
|
import { validateWorkspace } from "./validate.js";
|
|
35
36
|
import { loadWorkspace } from "./workspace.js";
|
|
36
37
|
|
|
37
38
|
const BOOLEAN_FLAGS = new Set([
|
|
39
|
+
"allow-non-authoritative-writes",
|
|
38
40
|
"check-docs",
|
|
39
41
|
"complete",
|
|
40
42
|
"current",
|
|
@@ -62,15 +64,22 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
62
64
|
if (command === "serve") {
|
|
63
65
|
const result = await serveWorkspace(positionals[0] ?? root, {
|
|
64
66
|
host: flags.host ?? process.env.FILEGRC_HOST,
|
|
65
|
-
port: flags.port ?? process.env.FILEGRC_PORT
|
|
67
|
+
port: flags.port ?? process.env.FILEGRC_PORT,
|
|
68
|
+
allowNonAuthoritativeWrites: flags["allow-non-authoritative-writes"] === true
|
|
66
69
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const stopped = new Promise((resolvePromise) => {
|
|
71
|
+
const stop = () => {
|
|
72
|
+
process.removeListener("SIGINT", stop);
|
|
73
|
+
process.removeListener("SIGTERM", stop);
|
|
74
|
+
result.server.close(resolvePromise);
|
|
75
|
+
};
|
|
71
76
|
process.once("SIGINT", stop);
|
|
72
77
|
process.once("SIGTERM", stop);
|
|
73
78
|
});
|
|
79
|
+
console.log(`filegrc workspace: ${result.url}`);
|
|
80
|
+
console.log(`Data: ${result.root}/data`);
|
|
81
|
+
printGithubStarMessage();
|
|
82
|
+
return await stopped;
|
|
74
83
|
}
|
|
75
84
|
if (command === "setup") {
|
|
76
85
|
const payload = positionals[0] ? await readSetupPayload(positionals[0]) : {};
|
|
@@ -676,7 +685,7 @@ function printHelp() {
|
|
|
676
685
|
console.log(`filegrc - Git-native GRC workspace
|
|
677
686
|
|
|
678
687
|
Usage:
|
|
679
|
-
filegrc serve [root] [--host 127.0.0.1] [--port 8787]
|
|
688
|
+
filegrc serve [root] [--host 127.0.0.1] [--port 8787] [--allow-non-authoritative-writes]
|
|
680
689
|
filegrc setup [setup.json|-] [setup options] [--draft] [--preview] [--summary] [--json]
|
|
681
690
|
filegrc build [root] [--output .filegrc/site]
|
|
682
691
|
filegrc validate [root] [--json]
|
|
@@ -713,12 +722,15 @@ All commands accept --root <workspace>. Writes never create Git commits.`);
|
|
|
713
722
|
function printCommandHelp(command) {
|
|
714
723
|
if (command === "serve") {
|
|
715
724
|
console.log(`Usage:
|
|
716
|
-
filegrc serve [root] [--host address] [--port number]
|
|
725
|
+
filegrc serve [root] [--host address] [--port number] [--allow-non-authoritative-writes]
|
|
717
726
|
|
|
718
727
|
Options:
|
|
719
728
|
--host <address> Bind address. Defaults to FILEGRC_HOST or 127.0.0.1.
|
|
720
729
|
--port <number> Port. Defaults to FILEGRC_PORT or 8787. Use 0 for an available port.
|
|
721
730
|
--root <path> Workspace path when no positional root is given.
|
|
731
|
+
--allow-non-authoritative-writes
|
|
732
|
+
Allow local browser writes from a task checkout. This explicit
|
|
733
|
+
development override never commits or pushes.
|
|
722
734
|
--help Show this help without starting the server.
|
|
723
735
|
|
|
724
736
|
Safety:
|
package/src/favicon.js
CHANGED
|
@@ -1,109 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
export const FAVICON_PNG = createFavicon();
|
|
7
|
-
|
|
8
|
-
function createFavicon() {
|
|
9
|
-
const pixels = Buffer.alloc(SIZE * SIZE * 4);
|
|
10
|
-
|
|
11
|
-
for (let y = 0; y < SIZE; y += 1) {
|
|
12
|
-
for (let x = 0; x < SIZE; x += 1) {
|
|
13
|
-
if (!insideRoundedSquare(x, y, 11)) continue;
|
|
14
|
-
setPixel(pixels, x, y, backgroundColor(x, y));
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
drawStroke(pixels, [
|
|
19
|
-
[18, 8],
|
|
20
|
-
[39, 8],
|
|
21
|
-
[51, 20],
|
|
22
|
-
[51, 50],
|
|
23
|
-
[50, 53],
|
|
24
|
-
[47, 55],
|
|
25
|
-
[17, 55],
|
|
26
|
-
[14, 54],
|
|
27
|
-
[12, 51],
|
|
28
|
-
[12, 13],
|
|
29
|
-
[14, 10],
|
|
30
|
-
[18, 8]
|
|
31
|
-
], 2.3, WHITE);
|
|
32
|
-
drawStroke(pixels, [[39, 8], [39, 20], [51, 20]], 2.3, WHITE);
|
|
33
|
-
|
|
34
|
-
const rows = Buffer.alloc((SIZE * 4 + 1) * SIZE);
|
|
35
|
-
for (let y = 0; y < SIZE; y += 1) {
|
|
36
|
-
const rowOffset = y * (SIZE * 4 + 1);
|
|
37
|
-
rows[rowOffset] = 0;
|
|
38
|
-
pixels.copy(rows, rowOffset + 1, y * SIZE * 4, (y + 1) * SIZE * 4);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const header = Buffer.alloc(13);
|
|
42
|
-
header.writeUInt32BE(SIZE, 0);
|
|
43
|
-
header.writeUInt32BE(SIZE, 4);
|
|
44
|
-
header.set([8, 6, 0, 0, 0], 8);
|
|
45
|
-
|
|
46
|
-
return Buffer.concat([
|
|
47
|
-
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
48
|
-
pngChunk("IHDR", header),
|
|
49
|
-
pngChunk("IDAT", deflateSync(rows)),
|
|
50
|
-
pngChunk("IEND", Buffer.alloc(0))
|
|
51
|
-
]);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function insideRoundedSquare(x, y, radius) {
|
|
55
|
-
const cornerX = x < radius ? radius - 1 : x >= SIZE - radius ? SIZE - radius : x;
|
|
56
|
-
const cornerY = y < radius ? radius - 1 : y >= SIZE - radius ? SIZE - radius : y;
|
|
57
|
-
return Math.hypot(x - cornerX, y - cornerY) <= radius;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function backgroundColor(x, y) {
|
|
61
|
-
const progress = Math.min(1, (x + y) / ((SIZE - 1) * 1.2));
|
|
62
|
-
return [0, 0, Math.round(112 + (53 - 112) * progress), 255];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function drawStroke(pixels, points, radius, color) {
|
|
66
|
-
for (let y = 0; y < SIZE; y += 1) {
|
|
67
|
-
for (let x = 0; x < SIZE; x += 1) {
|
|
68
|
-
const onStroke = points.slice(1).some((point, index) => (
|
|
69
|
-
distanceToSegment(x, y, points[index], point) <= radius
|
|
70
|
-
));
|
|
71
|
-
if (onStroke) setPixel(pixels, x, y, color);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function distanceToSegment(x, y, start, end) {
|
|
77
|
-
const dx = end[0] - start[0];
|
|
78
|
-
const dy = end[1] - start[1];
|
|
79
|
-
const lengthSquared = dx * dx + dy * dy;
|
|
80
|
-
const progress = lengthSquared
|
|
81
|
-
? Math.max(0, Math.min(1, ((x - start[0]) * dx + (y - start[1]) * dy) / lengthSquared))
|
|
82
|
-
: 0;
|
|
83
|
-
return Math.hypot(x - (start[0] + progress * dx), y - (start[1] + progress * dy));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function setPixel(pixels, x, y, color) {
|
|
87
|
-
pixels.set(color, (y * SIZE + x) * 4);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function pngChunk(type, data) {
|
|
91
|
-
const typeBuffer = Buffer.from(type, "ascii");
|
|
92
|
-
const chunk = Buffer.alloc(data.length + 12);
|
|
93
|
-
chunk.writeUInt32BE(data.length, 0);
|
|
94
|
-
typeBuffer.copy(chunk, 4);
|
|
95
|
-
data.copy(chunk, 8);
|
|
96
|
-
chunk.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), data.length + 8);
|
|
97
|
-
return chunk;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function crc32(buffer) {
|
|
101
|
-
let value = 0xffffffff;
|
|
102
|
-
for (const byte of buffer) {
|
|
103
|
-
value ^= byte;
|
|
104
|
-
for (let bit = 0; bit < 8; bit += 1) {
|
|
105
|
-
value = (value >>> 1) ^ (value & 1 ? 0xedb88320 : 0);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return (value ^ 0xffffffff) >>> 0;
|
|
109
|
-
}
|
|
3
|
+
export const FAVICON_PNG = readFileSync(new URL("./favicon.png", import.meta.url));
|
|
4
|
+
export const LOGO_MARK_PNG = readFileSync(new URL("./logo-mark-white.png", import.meta.url));
|
package/src/favicon.png
ADDED
|
Binary file
|
package/src/git-name.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function isSafeGitName(value) {
|
|
2
|
+
const segments = value.split("/");
|
|
3
|
+
return Boolean(value)
|
|
4
|
+
&& value !== "@"
|
|
5
|
+
&& value !== "HEAD"
|
|
6
|
+
&& !value.startsWith("-")
|
|
7
|
+
&& !value.includes("..")
|
|
8
|
+
&& !value.includes("@{")
|
|
9
|
+
&& !/[\s~^:?*[\]\\\u0000-\u001f\u007f]/.test(value)
|
|
10
|
+
&& segments.every((segment) => (
|
|
11
|
+
segment
|
|
12
|
+
&& !segment.startsWith(".")
|
|
13
|
+
&& !segment.endsWith(".")
|
|
14
|
+
&& !segment.endsWith(".lock")
|
|
15
|
+
));
|
|
16
|
+
}
|