filegrc 0.6.0 → 0.6.2

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 CHANGED
@@ -60,7 +60,7 @@ npx filegrc migrate --to-model 2 --preview --json
60
60
 
61
61
  Follow the [model v2 upgrade guide](https://github.com/Alignbase/filegrc/blob/main/docs/upgrading-to-model-v2.md), then run the model v3 and v4 previews in order.
62
62
 
63
- `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.
63
+ `filegrc serve --help` prints bind, port, environment, and safety options without starting the server. The editable server prefers `127.0.0.1:8787` and chooses another available port when that port is occupied. 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.
64
64
 
65
65
  `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 System and Program writes without saving. Add `--summary --json` for compact agent output. Selecting Type 1 or Type 2 updates the Program goal and selected Systems. Setup does not select Framework records, link Controls, create Evidence Artifacts, or create an Audit record.
66
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "filegrc",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Zero-dependency Git-native GRC engine",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -96,6 +96,7 @@ export async function runCli(argv = process.argv.slice(2)) {
96
96
  const result = await serveWorkspace(positionals[0] ?? root, {
97
97
  host: flags.host ?? process.env.FILEGRC_HOST,
98
98
  port: flags.port ?? process.env.FILEGRC_PORT,
99
+ fallbackToAvailablePort: true,
99
100
  allowNonAuthoritativeWrites: flags["allow-non-authoritative-writes"] === true
100
101
  });
101
102
  const stopped = new Promise((resolvePromise) => {
@@ -107,6 +108,9 @@ export async function runCli(argv = process.argv.slice(2)) {
107
108
  process.once("SIGINT", stop);
108
109
  process.once("SIGTERM", stop);
109
110
  });
111
+ if (result.usedFallbackPort) {
112
+ console.log(`Port ${result.requestedPort} is already in use. Using ${result.address.port} instead.`);
113
+ }
110
114
  console.log(`filegrc workspace: ${result.url}`);
111
115
  console.log(`Data: ${result.root}/data`);
112
116
  printGithubStarMessage();
@@ -1048,7 +1052,8 @@ function printCommandHelp(command) {
1048
1052
 
1049
1053
  Options:
1050
1054
  --host <address> Bind address. Defaults to FILEGRC_HOST or 127.0.0.1.
1051
- --port <number> Port. Defaults to FILEGRC_PORT or 8787. Use 0 for an available port.
1055
+ --port <number> Preferred port. Defaults to FILEGRC_PORT or 8787. If occupied,
1056
+ the server uses an available port. Use 0 to choose one directly.
1052
1057
  --root <path> Workspace path when no positional root is given.
1053
1058
  --allow-non-authoritative-writes
1054
1059
  Allow local browser writes from a task checkout. This explicit
package/src/web.js CHANGED
@@ -482,7 +482,7 @@ function openCollectionReviewDialog(type) {
482
482
  const dialog = document.createElement("dialog");
483
483
  dialog.className = "commit-dialog event-dialog collection-review-dialog";
484
484
  dialog.setAttribute("aria-labelledby", "collection-review-dialog-title");
485
- dialog.innerHTML = '<form><div class="dialog-head"><div><p class="kicker">Scope confirmation</p><h2 id="collection-review-dialog-title">Confirm ' + esc(configuration.title.toLowerCase()) + '</h2></div><button type="button" class="icon-button" aria-label="Close">×</button></div><p>' + esc(configuration.description) + '</p><section class="event-dialog-steps collection-review-checks"><strong>Before confirming</strong><ul>' + configuration.reviewPoints.map((point) => '<li>' + esc(point) + '</li>').join("") + '</ul></section><div class="form-grid"><label><span>Conclusion</span><select name="decision" required>' + decisions + '</select></label><label><span>Reviewer</span><select name="reviewerId" required><option value="">Select</option>' + people.map(({ record }) => '<option value="' + esc(record.id) + '" ' + ((assessment.review?.reviewedByIds || []).includes(record.id) ? "selected" : "") + '>' + esc(record.title) + '</option>').join("") + '</select></label><label><span>Reviewed on</span><input name="reviewedOn" type="date" required value="' + esc(assessment.review?.reviewedOn || currentDate()) + '"></label><label data-authoritative-system><span>Authoritative ' + (v4 ? "Component" : "System") + '</span><select name="authoritativeSourceId"><option value="">Select</option>' + systems.map(({ record }) => '<option value="' + esc(record.id) + '" ' + ((assessment.review?.authoritativeComponentId || assessment.review?.authoritativeSystemId) === record.id ? "selected" : "") + '>' + esc(record.title) + '</option>').join("") + '</select></label><label class="full"><span>Review notes</span><textarea name="rationale" rows="3" required placeholder="Note what you confirmed and any scope decision that needs context.">' + esc(assessment.review?.rationale || "") + '</textarea></label></div><div class="workflow-preview" role="status"></div><div class="dialog-error" role="alert"></div><div class="dialog-actions"><span class="save-status review-save-status" role="status" aria-live="polite"></span><button type="button" class="button" data-event="cancel">Cancel</button><button type="submit" class="button primary" data-preview-collection-review>Preview confirmation</button></div></form>';
485
+ dialog.innerHTML = '<form><div class="dialog-head"><div><p class="kicker">Scope confirmation</p><h2 id="collection-review-dialog-title">Confirm ' + esc(configuration.title.toLowerCase()) + '</h2></div><button type="button" class="icon-button" aria-label="Close">×</button></div><p>' + esc(configuration.description) + '</p><section class="event-dialog-steps collection-review-checks"><strong>Before confirming</strong><ul>' + configuration.reviewPoints.map((point) => '<li>' + esc(point) + '</li>').join("") + '</ul></section><div class="form-grid"><label><span>Conclusion</span><select name="decision" required>' + decisions + '</select></label><label><span>Reviewer</span><select name="reviewerId" required><option value="">Select</option>' + people.map(({ record }) => '<option value="' + esc(record.id) + '" ' + ((assessment.review?.reviewedByIds || []).includes(record.id) ? "selected" : "") + '>' + esc(record.title) + '</option>').join("") + '</select></label><label><span>Reviewed on</span><input name="reviewedOn" type="date" required value="' + esc(assessment.review?.reviewedOn || currentDate()) + '"></label><label data-authoritative-system><span>Authoritative ' + (v4 ? "Component" : "System") + '</span><select name="authoritativeSourceId"><option value="">Select</option>' + systems.map(({ record }) => '<option value="' + esc(record.id) + '" ' + ((assessment.review?.authoritativeComponentId || assessment.review?.authoritativeSystemId) === record.id ? "selected" : "") + '>' + esc(record.title) + '</option>').join("") + '</select></label><label class="full"><span>Review notes</span><textarea name="rationale" rows="3" required placeholder="Note what you confirmed and any scope decision that needs context.">' + esc(assessment.review?.rationale || "") + '</textarea></label></div><div class="workflow-preview"><strong>What this saves</strong><p>Confirm ' + assessment.recordCount + ' current ' + esc(pluralize("record", assessment.recordCount)) + '. If the collection or material scope changes, FileGRC will ask for another review.</p></div><div class="dialog-error" role="alert"></div><div class="dialog-actions"><span class="save-status review-save-status" role="status" aria-live="polite"></span><button type="button" class="button" data-event="cancel">Cancel</button><button type="submit" class="button primary">Confirm and save</button></div></form>';
486
486
  document.body.append(dialog);
487
487
  dialog.showModal();
488
488
  const form = dialog.querySelector("form");
@@ -498,12 +498,8 @@ function openCollectionReviewDialog(type) {
498
498
  dialog.querySelector(".icon-button").addEventListener("click", () => dialog.close());
499
499
  dialog.querySelector('[data-event="cancel"]').addEventListener("click", () => dialog.close());
500
500
  dialog.addEventListener("close", () => dialog.remove());
501
- let previewedPayload = null;
502
501
  form.addEventListener("input", () => {
503
- previewedPayload = null;
504
502
  syncDecision();
505
- dialog.querySelector(".workflow-preview").innerHTML = "";
506
- dialog.querySelector("[data-preview-collection-review]").textContent = "Preview confirmation";
507
503
  });
508
504
  form.addEventListener("submit", async (event) => {
509
505
  event.preventDefault();
@@ -521,32 +517,20 @@ function openCollectionReviewDialog(type) {
521
517
  };
522
518
  form.querySelectorAll("button,input,select,textarea").forEach((control) => { control.disabled = true; });
523
519
  try {
524
- if (!previewedPayload) {
525
- const response = await localFetch("/api/collection-review/preview", {
526
- method: "POST",
527
- headers: { "content-type": "application/json" },
528
- body: JSON.stringify(payload)
529
- });
530
- if (!response.ok) throw new Error(await responseMessage(response));
531
- const preview = await response.json();
532
- previewedPayload = payload;
533
- dialog.querySelector(".workflow-preview").innerHTML = '<strong>Review before saving</strong><p>Save this confirmation for ' + preview.assessment.recordCount + ' current ' + esc(pluralize("record", preview.assessment.recordCount)) + '. If the collection or material scope changes, FileGRC will ask for another review.</p>';
534
- dialog.querySelector("[data-preview-collection-review]").textContent = "Confirm and save";
535
- } else {
536
- saveStatus.textContent = "Validating and saving…";
537
- const prefetch = await repositoryPrefetch;
538
- const response = await localFetch("/api/collection-review", {
539
- method: "POST",
540
- headers: { "content-type": "application/json", prefer: "respond-async" },
541
- body: JSON.stringify({ ...previewedPayload, confirmed: true, prefetchToken: prefetch?.token })
542
- });
543
- if (!response.ok) throw new Error(await responseMessage(response));
544
- saveStatus.textContent = "Saved locally. Refreshing page…";
545
- applyMutationState(await response.json());
546
- dialog.close();
547
- render();
548
- }
520
+ saveStatus.textContent = "Validating and saving…";
521
+ const prefetch = await repositoryPrefetch;
522
+ const response = await localFetch("/api/collection-review", {
523
+ method: "POST",
524
+ headers: { "content-type": "application/json", prefer: "respond-async" },
525
+ body: JSON.stringify({ ...payload, confirmed: true, prefetchToken: prefetch?.token })
526
+ });
527
+ if (!response.ok) throw new Error(await responseMessage(response));
528
+ saveStatus.textContent = "Saved locally. Refreshing page…";
529
+ applyMutationState(await response.json());
530
+ dialog.close();
531
+ render();
549
532
  } catch (requestError) {
533
+ saveStatus.textContent = "Not saved";
550
534
  error.textContent = requestError.message;
551
535
  } finally {
552
536
  form.querySelectorAll("button,input,select,textarea").forEach((control) => { control.disabled = false; });