redlinegate 0.0.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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/commands/redline-init.md +54 -0
  4. package/commands/redline-verify.md +69 -0
  5. package/dist/bin/redline.js +605 -0
  6. package/dist/bin/redline.js.map +1 -0
  7. package/dist/commands/exempt.js +45 -0
  8. package/dist/commands/exempt.js.map +1 -0
  9. package/dist/commands/init.js +615 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/policy.js +25 -0
  12. package/dist/commands/policy.js.map +1 -0
  13. package/dist/commands/remove.js +427 -0
  14. package/dist/commands/remove.js.map +1 -0
  15. package/dist/commands/review.js +72 -0
  16. package/dist/commands/review.js.map +1 -0
  17. package/dist/commands/sync.js +42 -0
  18. package/dist/commands/sync.js.map +1 -0
  19. package/dist/commands/verify.js +478 -0
  20. package/dist/commands/verify.js.map +1 -0
  21. package/dist/config/redline-json.js +187 -0
  22. package/dist/config/redline-json.js.map +1 -0
  23. package/dist/core/errors.js +25 -0
  24. package/dist/core/errors.js.map +1 -0
  25. package/dist/core/git.js +152 -0
  26. package/dist/core/git.js.map +1 -0
  27. package/dist/core/log.js +22 -0
  28. package/dist/core/log.js.map +1 -0
  29. package/dist/core/severity.js +16 -0
  30. package/dist/core/severity.js.map +1 -0
  31. package/dist/core/version.js +15 -0
  32. package/dist/core/version.js.map +1 -0
  33. package/dist/detect/scan.js +79 -0
  34. package/dist/detect/scan.js.map +1 -0
  35. package/dist/detect/stack.js +71 -0
  36. package/dist/detect/stack.js.map +1 -0
  37. package/dist/enforce/ladder.js +148 -0
  38. package/dist/enforce/ladder.js.map +1 -0
  39. package/dist/exempt/parse.js +86 -0
  40. package/dist/exempt/parse.js.map +1 -0
  41. package/dist/metrics/options.js +257 -0
  42. package/dist/metrics/options.js.map +1 -0
  43. package/dist/metrics/run.js +72 -0
  44. package/dist/metrics/run.js.map +1 -0
  45. package/dist/platforms/azure/client.js +53 -0
  46. package/dist/platforms/azure/client.js.map +1 -0
  47. package/dist/platforms/azure/index.js +69 -0
  48. package/dist/platforms/azure/index.js.map +1 -0
  49. package/dist/platforms/azure/install.js +917 -0
  50. package/dist/platforms/azure/install.js.map +1 -0
  51. package/dist/platforms/azure/policy-types.js +70 -0
  52. package/dist/platforms/azure/policy-types.js.map +1 -0
  53. package/dist/platforms/azure/verify.js +309 -0
  54. package/dist/platforms/azure/verify.js.map +1 -0
  55. package/dist/platforms/detect.js +40 -0
  56. package/dist/platforms/detect.js.map +1 -0
  57. package/dist/platforms/github/client.js +49 -0
  58. package/dist/platforms/github/client.js.map +1 -0
  59. package/dist/platforms/github/index.js +54 -0
  60. package/dist/platforms/github/index.js.map +1 -0
  61. package/dist/platforms/github/install.js +558 -0
  62. package/dist/platforms/github/install.js.map +1 -0
  63. package/dist/platforms/github/push.js +102 -0
  64. package/dist/platforms/github/push.js.map +1 -0
  65. package/dist/platforms/github/remote.js +36 -0
  66. package/dist/platforms/github/remote.js.map +1 -0
  67. package/dist/platforms/github/verify.js +366 -0
  68. package/dist/platforms/github/verify.js.map +1 -0
  69. package/dist/platforms/http.js +58 -0
  70. package/dist/platforms/http.js.map +1 -0
  71. package/dist/platforms/pull-request-templates.js +157 -0
  72. package/dist/platforms/pull-request-templates.js.map +1 -0
  73. package/dist/platforms/remote.js +21 -0
  74. package/dist/platforms/remote.js.map +1 -0
  75. package/dist/platforms/resolve.js +69 -0
  76. package/dist/platforms/resolve.js.map +1 -0
  77. package/dist/platforms/shape.js +19 -0
  78. package/dist/platforms/shape.js.map +1 -0
  79. package/dist/platforms/types.js +24 -0
  80. package/dist/platforms/types.js.map +1 -0
  81. package/dist/policy/checks.js +91 -0
  82. package/dist/policy/checks.js.map +1 -0
  83. package/dist/policy/diff.js +50 -0
  84. package/dist/policy/diff.js.map +1 -0
  85. package/dist/registry/discover.js +74 -0
  86. package/dist/registry/discover.js.map +1 -0
  87. package/dist/registry/serialize.js +41 -0
  88. package/dist/registry/serialize.js.map +1 -0
  89. package/dist/registry/types.js +2 -0
  90. package/dist/registry/types.js.map +1 -0
  91. package/dist/remove/host.js +294 -0
  92. package/dist/remove/host.js.map +1 -0
  93. package/dist/render/commands.js +139 -0
  94. package/dist/render/commands.js.map +1 -0
  95. package/dist/render/manifest.js +82 -0
  96. package/dist/render/manifest.js.map +1 -0
  97. package/dist/render/markers.js +166 -0
  98. package/dist/render/markers.js.map +1 -0
  99. package/dist/render/profile.js +23 -0
  100. package/dist/render/profile.js.map +1 -0
  101. package/dist/render/standards.js +169 -0
  102. package/dist/render/standards.js.map +1 -0
  103. package/dist/render/vendors.js +199 -0
  104. package/dist/render/vendors.js.map +1 -0
  105. package/dist/review/engines/api.js +95 -0
  106. package/dist/review/engines/api.js.map +1 -0
  107. package/dist/review/engines/embedded.js +20 -0
  108. package/dist/review/engines/embedded.js.map +1 -0
  109. package/dist/review/engines/types.js +2 -0
  110. package/dist/review/engines/types.js.map +1 -0
  111. package/dist/review/glob.js +29 -0
  112. package/dist/review/glob.js.map +1 -0
  113. package/dist/review/prompt.js +45 -0
  114. package/dist/review/prompt.js.map +1 -0
  115. package/dist/review/rules.js +25 -0
  116. package/dist/review/rules.js.map +1 -0
  117. package/dist/review/schema.js +84 -0
  118. package/dist/review/schema.js.map +1 -0
  119. package/dist/review/scope.js +27 -0
  120. package/dist/review/scope.js.map +1 -0
  121. package/dist/sarif/map.js +58 -0
  122. package/dist/sarif/map.js.map +1 -0
  123. package/dist/sarif/parse.js +88 -0
  124. package/dist/sarif/parse.js.map +1 -0
  125. package/dist/sarif/types.js +2 -0
  126. package/dist/sarif/types.js.map +1 -0
  127. package/dist/sync/host.js +21 -0
  128. package/dist/sync/host.js.map +1 -0
  129. package/dist/sync/plan.js +47 -0
  130. package/dist/sync/plan.js.map +1 -0
  131. package/dist/sync/render.js +57 -0
  132. package/dist/sync/render.js.map +1 -0
  133. package/dist/sync/run.js +118 -0
  134. package/dist/sync/run.js.map +1 -0
  135. package/dist/verify/host.js +52 -0
  136. package/dist/verify/host.js.map +1 -0
  137. package/dist/verify/remote.js +146 -0
  138. package/dist/verify/remote.js.map +1 -0
  139. package/package.json +52 -0
  140. package/platforms/azure/gate-template.yml +105 -0
  141. package/rulesets/redline-org-ruleset.json +46 -0
  142. package/rulesets/redline-ruleset.json +49 -0
  143. package/scripts/assign-rule-ids.mjs +129 -0
  144. package/scripts/build-baseline.mjs +124 -0
  145. package/scripts/build-correlation.mjs +74 -0
  146. package/scripts/build-dashboard.mjs +584 -0
  147. package/scripts/build-digest.mjs +164 -0
  148. package/scripts/build-inbox.mjs +133 -0
  149. package/scripts/build-registry.mjs +42 -0
  150. package/scripts/build-roi.mjs +181 -0
  151. package/scripts/check-pins.mjs +88 -0
  152. package/scripts/collect-telemetry.mjs +300 -0
  153. package/scripts/lib/__tests__/baseline.test.mjs +132 -0
  154. package/scripts/lib/__tests__/correlate.test.mjs +131 -0
  155. package/scripts/lib/__tests__/dora.test.mjs +110 -0
  156. package/scripts/lib/__tests__/exemptions.test.mjs +85 -0
  157. package/scripts/lib/__tests__/metrics-scanner.test.mjs +65 -0
  158. package/scripts/lib/__tests__/sarif.test.mjs +125 -0
  159. package/scripts/lib/__tests__/spend.test.mjs +64 -0
  160. package/scripts/lib/baseline.mjs +156 -0
  161. package/scripts/lib/correlate.mjs +125 -0
  162. package/scripts/lib/dora.mjs +133 -0
  163. package/scripts/lib/exemptions.mjs +79 -0
  164. package/scripts/lib/metrics.mjs +172 -0
  165. package/scripts/lib/rules.mjs +107 -0
  166. package/scripts/lib/sarif.mjs +108 -0
  167. package/scripts/lib/spend.mjs +69 -0
  168. package/scripts/measure-context.mjs +101 -0
  169. package/scripts/render-self.mjs +18 -0
  170. package/scripts/score-seeds.mjs +294 -0
  171. package/scripts/validate.mjs +394 -0
  172. package/standards/core.md +124 -0
  173. package/standards/manifest.json +214 -0
  174. package/standards/stacks/csharp.md +29 -0
  175. package/standards/stacks/go.md +30 -0
  176. package/standards/stacks/java.md +30 -0
  177. package/standards/stacks/javascript.md +42 -0
  178. package/standards/stacks/kotlin.md +27 -0
  179. package/standards/stacks/microservices.md +37 -0
  180. package/standards/stacks/nodejs.md +34 -0
  181. package/standards/stacks/python.md +29 -0
  182. package/standards/stacks/react-native.md +29 -0
  183. package/standards/stacks/react.md +46 -0
  184. package/standards/stacks/swift.md +26 -0
  185. package/standards/stacks/terraform.md +27 -0
  186. package/templates/CODEOWNERS +29 -0
  187. package/templates/azure/pull_request_template.md +72 -0
  188. package/templates/github/pull_request_template.md +72 -0
  189. package/templates/redline.yml +35 -0
  190. package/templates/repo-context.md +64 -0
  191. package/workflows/dashboard.yml +119 -0
  192. package/workflows/inbox.yml +97 -0
  193. package/workflows/redline-collect.yml +61 -0
  194. package/workflows/redline-gate.yml +301 -0
  195. package/workflows/redline-sync.yml +79 -0
  196. package/workflows/seed-canary.yml +231 -0
  197. package/workflows/verify-onboarding.yml +115 -0
  198. package/workflows/weekly-digest.yml +78 -0
@@ -0,0 +1,157 @@
1
+ import { readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { isRedlineError } from "../core/errors.js";
4
+ import { findBlock } from "../render/markers.js";
5
+ // What `redline verify` needs to see about a pull request template, and the
6
+ // only place outside the install adapters that resolves one.
7
+ //
8
+ // CONTRACT with cli/platforms/github/install.ts and
9
+ // cli/platforms/azure/install.ts: the candidate folders, the served filenames
10
+ // and the branch-template layout below must stay the same as the ones those
11
+ // two resolve when they write. A verify that looked at a different file than
12
+ // init writes to would report on a template the host never serves. The two
13
+ // adapters keep their own private copies for the write side; when one changes,
14
+ // change this with it.
15
+ const TEMPLATE_NAMES = {
16
+ // GitHub documents only `pull_request_template.md`.
17
+ github: ['pull_request_template.md'],
18
+ // Azure DevOps documents both extensions, case-insensitively.
19
+ azure: ['pull_request_template.md', 'pull_request_template.txt'],
20
+ };
21
+ // Exported for cli/platforms/github/install.ts and cli/platforms/azure/
22
+ // install.ts: the folder order is the one thing that must never disagree
23
+ // between what `redline init` writes and what `redline verify` reads back —
24
+ // three private copies of the same order used to exist here, and a copy left
25
+ // behind by an edit to the other two would report a template `init` does not
26
+ // write. The per-host discovery and merge logic stays private to each
27
+ // adapter; only this order is shared.
28
+ export const TEMPLATE_DIRS = {
29
+ github: ['.github', '', 'docs'],
30
+ azure: ['.azuredevops', '.vsts', 'docs', ''],
31
+ };
32
+ // Azure serves a branch-specific template in preference to the default, so one
33
+ // added after onboarding replaces everything `redline init` merged. GitHub's
34
+ // `PULL_REQUEST_TEMPLATE/` directory is the opt-in kind, reachable only through
35
+ // a `?template=` link, and is deliberately not observed here.
36
+ const BRANCH_TEMPLATES = { github: false, azure: true };
37
+ const BRANCH_TEMPLATE_EXTENSIONS = ['.md', '.txt'];
38
+ const MAX_BRANCH_DEPTH = 10;
39
+ // CONTRACT with workflows/redline-gate.yml: the `checklist` job fails a pull
40
+ // request whose body has no `## Launch readiness` heading (matched by prefix,
41
+ // so `## Launch readiness checklist` satisfies it), and the `adr` job fails a
42
+ // large diff whose body carries no `docs/adr/` link. A template that answers
43
+ // both is one no pull request can be failed by, whoever wrote it.
44
+ const GATED_SECTIONS = [
45
+ { heading: '## Launch readiness', satisfied: (body) => /^##[ \t]+Launch readiness/m.test(body) },
46
+ { heading: '## Architecture decision', satisfied: (body) => body.includes('docs/adr/') },
47
+ ];
48
+ // Listed rather than probed with existsSync: both hosts treat these folder
49
+ // names as case-insensitive, and existsSync is case-insensitive on macOS but
50
+ // not on Linux. isDirectory() is also what stops a plain file named `docs`
51
+ // from throwing ENOTDIR.
52
+ function candidateDirs(cwd, dirs) {
53
+ const entries = readdirSync(cwd, { withFileTypes: true });
54
+ const found = [];
55
+ for (const wanted of dirs) {
56
+ if (wanted === '') {
57
+ found.push({ abs: cwd, rel: '' });
58
+ continue;
59
+ }
60
+ const dir = entries.find((entry) => entry.isDirectory() && entry.name.toLowerCase() === wanted);
61
+ if (dir)
62
+ found.push({ abs: join(cwd, dir.name), rel: dir.name });
63
+ }
64
+ return found;
65
+ }
66
+ const under = (candidate, name) => candidate.rel === '' ? name : `${candidate.rel}/${name}`;
67
+ function findDefaultTemplate(cwd, host) {
68
+ const names = TEMPLATE_NAMES[host];
69
+ for (const candidate of candidateDirs(cwd, TEMPLATE_DIRS[host])) {
70
+ const hits = readdirSync(candidate.abs, { withFileTypes: true })
71
+ .filter((entry) => entry.isFile() && names.includes(entry.name.toLowerCase()))
72
+ .map((entry) => entry.name);
73
+ hits.sort((a, b) => {
74
+ const byName = names.indexOf(a.toLowerCase()) - names.indexOf(b.toLowerCase());
75
+ if (byName !== 0)
76
+ return byName;
77
+ if (a === b.toLowerCase())
78
+ return -1;
79
+ if (b === a.toLowerCase())
80
+ return 1;
81
+ return a < b ? -1 : 1;
82
+ });
83
+ const hit = hits[0];
84
+ if (hit !== undefined)
85
+ return under(candidate, hit);
86
+ }
87
+ return null;
88
+ }
89
+ function childDir(abs, name) {
90
+ const entry = readdirSync(abs, { withFileTypes: true }).find((candidate) => candidate.isDirectory() && candidate.name.toLowerCase() === name);
91
+ return entry ? join(abs, entry.name) : null;
92
+ }
93
+ function collectBranchTemplates(abs, rel, depth, into) {
94
+ if (depth > MAX_BRANCH_DEPTH)
95
+ return;
96
+ for (const entry of readdirSync(abs, { withFileTypes: true })) {
97
+ const childRel = `${rel}/${entry.name}`;
98
+ if (entry.isDirectory())
99
+ collectBranchTemplates(join(abs, entry.name), childRel, depth + 1, into);
100
+ else if (entry.isFile() &&
101
+ BRANCH_TEMPLATE_EXTENSIONS.some((ext) => entry.name.toLowerCase().endsWith(ext))) {
102
+ into.push(childRel);
103
+ }
104
+ }
105
+ }
106
+ function findBranchTemplates(cwd, host) {
107
+ if (!BRANCH_TEMPLATES[host])
108
+ return [];
109
+ const found = [];
110
+ for (const candidate of candidateDirs(cwd, TEMPLATE_DIRS[host])) {
111
+ const folder = childDir(candidate.abs, 'pull_request_template');
112
+ const branches = folder === null ? null : childDir(folder, 'branches');
113
+ if (branches === null)
114
+ continue;
115
+ collectBranchTemplates(branches, under(candidate, 'pull_request_template/branches'), 1, found);
116
+ }
117
+ return found.sort();
118
+ }
119
+ function classify(cwd, relPath, branch) {
120
+ const body = readFileSync(join(cwd, relPath), 'utf8');
121
+ try {
122
+ if (findBlock(body, relPath) !== null)
123
+ return { path: relPath, branch, state: 'managed', missing: [] };
124
+ }
125
+ catch (error) {
126
+ // findBlock refuses a half-edited pair with RedlineError('failed'). That
127
+ // is a finding about this repository, not a reason for `verify` to abort
128
+ // before it has reported anything else.
129
+ if (!isRedlineError(error))
130
+ throw error;
131
+ return { path: relPath, branch, state: 'mangled', missing: [] };
132
+ }
133
+ const missing = GATED_SECTIONS.filter((section) => !section.satisfied(body)).map((s) => s.heading);
134
+ return {
135
+ path: relPath,
136
+ branch,
137
+ state: missing.length === 0 ? 'satisfied' : 'incomplete',
138
+ missing,
139
+ };
140
+ }
141
+ /**
142
+ * Every pull request template this host would actually serve, in the order it
143
+ * resolves them: the default first, then Azure's branch-specific ones. Empty
144
+ * when the host would serve none — for an onboarded repository that means the
145
+ * template `redline init` wrote is gone.
146
+ */
147
+ export function observePullRequestTemplates(host, cwd) {
148
+ const observed = [];
149
+ const defaultPath = findDefaultTemplate(cwd, host);
150
+ if (defaultPath !== null)
151
+ observed.push(classify(cwd, defaultPath, false));
152
+ for (const branchPath of findBranchTemplates(cwd, host)) {
153
+ observed.push(classify(cwd, branchPath, true));
154
+ }
155
+ return observed;
156
+ }
157
+ //# sourceMappingURL=pull-request-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull-request-templates.js","sourceRoot":"","sources":["../../cli/platforms/pull-request-templates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { RedlineError } from "../core/errors.js";
2
+ import { parseConfig } from "../config/redline-json.js";
3
+ // Shared by both hosts: the file is external input from a repository this
4
+ // process does not control, so a malformed one is a reported failure naming the
5
+ // repository, never a crash that costs a whole estate walk.
6
+ export function parseRemoteConfig(raw, where) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(raw);
10
+ }
11
+ catch (error) {
12
+ throw new RedlineError('failed', `${where}: .redline.json is not valid JSON (${error instanceof Error ? error.message : String(error)})`);
13
+ }
14
+ try {
15
+ return parseConfig(parsed);
16
+ }
17
+ catch (error) {
18
+ throw new RedlineError('failed', `${where}: .redline.json is invalid — ${error instanceof Error ? error.message : String(error)}`);
19
+ }
20
+ }
21
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../cli/platforms/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAsB,MAAM,2BAA2B,CAAC;AAuB5E,0EAA0E;AAC1E,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,KAAa;IAC1D,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CACpB,QAAQ,EACR,GAAG,KAAK,sCAAsC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CACxG,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CACpB,QAAQ,EACR,GAAG,KAAK,gCAAgC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjG,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,69 @@
1
+ import { createGit } from "../core/git.js";
2
+ import { RedlineError } from "../core/errors.js";
3
+ import { parseRemote } from "./detect.js";
4
+ import { createGitHubClient } from "./github/client.js";
5
+ import { createGitHubPlatform } from "./github/index.js";
6
+ import { createAzureClient } from "./azure/client.js";
7
+ import { createAzurePlatform } from "./azure/index.js";
8
+ // One construction per process, success or failure. Memoising the failure
9
+ // matters as much as memoising the success: `createGitHubClient()` shells out
10
+ // to `gh auth token`, so a client that failed to build would otherwise
11
+ // re-spawn it on every single request.
12
+ function once(make) {
13
+ let result = null;
14
+ return () => {
15
+ if (result === null) {
16
+ try {
17
+ result = { ok: true, value: make() };
18
+ }
19
+ catch (error) {
20
+ result = { ok: false, error };
21
+ }
22
+ }
23
+ if (!result.ok)
24
+ throw result.error;
25
+ return result.value;
26
+ };
27
+ }
28
+ // Deliberately written out per client rather than proxied: `rest`/`request` are
29
+ // generic, and a generic proxy cannot forward a type parameter without an
30
+ // escape hatch.
31
+ function lazyGitHubClient(make) {
32
+ const client = once(make);
33
+ return {
34
+ rest(method, path, body) {
35
+ return client().rest(method, path, body);
36
+ },
37
+ graphql(query, variables) {
38
+ return client().graphql(query, variables);
39
+ },
40
+ };
41
+ }
42
+ function lazyAzureClient(make) {
43
+ const client = once(make);
44
+ return {
45
+ request(method, path, body, opts) {
46
+ return client().request(method, path, body, opts);
47
+ },
48
+ };
49
+ }
50
+ // The one place that turns a working tree into a Platform. Every command
51
+ // resolves a platform through this function — no command constructs a
52
+ // GitHubClient/AzureClient or a *Platform directly.
53
+ export async function resolvePlatform(cwd, deps = {}) {
54
+ const gitFor = deps.gitFor ?? ((dir) => createGit(dir));
55
+ const git = gitFor(cwd);
56
+ if (!git.isRepo()) {
57
+ throw new RedlineError('usage', `${cwd} is not a git repository`, 'run redline from inside your repo');
58
+ }
59
+ const identity = parseRemote(git.remoteUrl());
60
+ const lazy = deps.lazyCredentials === true;
61
+ if (identity.host === 'github') {
62
+ const make = deps.makeGitHubClient ?? (() => createGitHubClient());
63
+ return createGitHubPlatform({ client: lazy ? lazyGitHubClient(make) : make(), gitFor });
64
+ }
65
+ const makeAzure = deps.makeAzureClient ?? ((org) => createAzureClient(org));
66
+ const make = () => makeAzure(identity.org);
67
+ return createAzurePlatform({ client: lazy ? lazyAzureClient(make) : make(), gitFor });
68
+ }
69
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../cli/platforms/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { RedlineError } from "../core/errors.js";
2
+ // Host response bodies are untrusted external input: the transport
3
+ // (http.ts) only guarantees valid JSON, never a particular shape. Every
4
+ // body is typed `unknown` and narrowed by an explicit parse function, in
5
+ // the house style of cli/render/manifest.ts — never a bare `as` onto a
6
+ // concrete host type. A host error body is a truthy object (e.g.
7
+ // `{ message: "Forbidden" }`), not an array, so `isNonNullObject` rejects
8
+ // arrays too — a host-supplied array must never be mistaken for the keyed
9
+ // object callers go on to index.
10
+ export function isNonNullObject(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ export function isSuccess(status) {
14
+ return status >= 200 && status < 300;
15
+ }
16
+ export function createHostShapeError(host) {
17
+ return (what) => new RedlineError('host', `${host} returned an unexpected shape for ${what}`);
18
+ }
19
+ //# sourceMappingURL=shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape.js","sourceRoot":"","sources":["../../cli/platforms/shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,mEAAmE;AACnE,wEAAwE;AACxE,yEAAyE;AACzE,uEAAuE;AACvE,iEAAiE;AACjE,0EAA0E;AAC1E,0EAA0E;AAC1E,iCAAiC;AACjC,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,MAAM,EAAE,GAAG,IAAI,qCAAqC,IAAI,EAAE,CAAC,CAAC;AACxG,CAAC"}
@@ -0,0 +1,24 @@
1
+ export const HOSTS = ['github', 'azure'];
2
+ export const ADMIN_CAPABILITIES = [
3
+ 'secret-scanning',
4
+ 'push-protection',
5
+ 'dependency-alerts',
6
+ 'merge-policy',
7
+ 'repo-property',
8
+ 'labels',
9
+ 'review-ownership',
10
+ // The machinery that runs the merge gate on the host. GitHub needs none (a
11
+ // workflow file's pr trigger fires natively); Azure Repos ignores YAML
12
+ // `pr:` triggers, so there it is a registered pipeline definition plus a
13
+ // Build Validation branch policy.
14
+ 'gate',
15
+ ];
16
+ // Exactly `denied`. `unknown` must behave like `unsupported` here: neither
17
+ // ever reaches pendingAdmin, because an indeterminate read has told nobody
18
+ // anything they can act on. Nothing clears an entry already recorded there
19
+ // either — no read revises pendingAdmin in either direction, so only
20
+ // `redline init --repair` retries the write that recorded it.
21
+ export function isPending(outcome) {
22
+ return outcome.status === 'denied';
23
+ }
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../cli/platforms/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAU,CAAC;AAGlD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;IACnB,cAAc;IACd,eAAe;IACf,QAAQ;IACR,kBAAkB;IAClB,2EAA2E;IAC3E,uEAAuE;IACvE,yEAAyE;IACzE,kCAAkC;IAClC,MAAM;CACE,CAAC;AA4BX,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,qEAAqE;AACrE,8DAA8D;AAC9D,MAAM,UAAU,SAAS,CAAC,OAA0B;IAClD,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;AACrC,CAAC"}
@@ -0,0 +1,91 @@
1
+ // A ticket reference: ABC-123, #123, or a URL to an issue tracker. Deliberately
2
+ // broad — the rule asks for a reference, not for a particular tracker, and a
3
+ // narrow pattern would fail a team whose tickets do not look like ours.
4
+ const TICKET = /([A-Z][A-Z0-9]+-\d+|#\d+|https?:\/\/\S*(issue|ticket|jira|linear|browse)\S*)/i;
5
+ const JS_LIKE = /\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)$/;
6
+ const finding = (line, ruleId, severity, problem) => ({ ruleId, severity, file: line.file, line: line.line, problem });
7
+ // `core/untracked-todo` — a TODO or FIXME with no ticket reference.
8
+ //
9
+ // Decidable in full: the rule asks whether a reference is present on the line,
10
+ // which is a fact about the text. The model is not asked about it at all.
11
+ const untrackedTodo = ({ added }) => added
12
+ .filter((l) => /\b(TODO|FIXME|HACK|XXX)\b/.test(l.text) && !TICKET.test(l.text))
13
+ .map((l) => finding(l, 'core/untracked-todo', 'HIGH', 'this TODO carries no ticket reference, so nothing will bring anyone back to it. ' +
14
+ 'Add the ticket id on the same line, or do the work now.'));
15
+ // `core/type-checker-suppression` — a suppression with no comment AND no ticket.
16
+ //
17
+ // The rule requires both. A suppression with an explanation but no ticket is
18
+ // still a violation, and this decides that without a model: the presence of a
19
+ // ticket reference on the line is a fact.
20
+ const SUPPRESSIONS = [
21
+ '@ts-ignore',
22
+ '@ts-expect-error',
23
+ '# type: ignore',
24
+ '@SuppressWarnings',
25
+ 'eslint-disable',
26
+ '// nolint',
27
+ '# noqa',
28
+ ];
29
+ const typeCheckerSuppression = ({ added }) => added
30
+ .filter((l) => SUPPRESSIONS.some((s) => l.text.includes(s)) && !TICKET.test(l.text))
31
+ .map((l) => finding(l, 'core/type-checker-suppression', 'BLOCKER', 'a type-checker suppression needs an inline explanation AND a ticket reference on the ' +
32
+ 'same line. Without one, nobody knows what it hides or when it can go.'));
33
+ // `javascript/var-in-new-code` — `var` on a line this change added.
34
+ //
35
+ // Added lines only. Flagging `var` in a legacy file the author merely moved is
36
+ // exactly the noise the standard's "what NOT to flag" section forbids.
37
+ // A line whose content begins a comment. Not a parser — a checker that reported
38
+ // "use const" on the sentence "avoid var declarations here" is worse than one
39
+ // that misses a `var` on the same line as a trailing comment, and this is the
40
+ // cheap way to avoid the embarrassing half.
41
+ const COMMENT_LINE = /^\s*(\/\/|\/\*|\*|#)/;
42
+ const varInNewCode = ({ added }) => added
43
+ .filter((l) => JS_LIKE.test(l.file) &&
44
+ !COMMENT_LINE.test(l.text) &&
45
+ /(^|[^.\w])var\s+[A-Za-z_$]/.test(l.text))
46
+ .map((l) => finding(l, 'javascript/var-in-new-code', 'HIGH', '`var` is function-scoped and hoisted. Use `const`, or `let` where the binding is reassigned.'));
47
+ // `javascript/unsafe-numeric-coercion` — `parseInt` with no radix.
48
+ //
49
+ // Only the radix half. The rest of the rule ("Number() coercion of user input
50
+ // without Number.isFinite") needs to know what is user input, which is judgement,
51
+ // and the model still sees the whole rule.
52
+ // Nested parentheses in the argument — `parseInt(String(x), 10)` — hid the comma
53
+ // from a `[^,)]*` scan, so a correctly-written call was reported as missing its
54
+ // radix. This checker flagged its own source, which is the clearest possible
55
+ // signal that the pattern was wrong.
56
+ const PARSE_INT_NO_RADIX = /\bparseInt\s*\((?:[^(),]|\([^()]*\))*\)/;
57
+ const parseIntWithoutRadix = ({ added }) => added
58
+ .filter((l) => JS_LIKE.test(l.file) && PARSE_INT_NO_RADIX.test(l.text))
59
+ .map((l) => finding(l, 'javascript/unsafe-numeric-coercion', 'HIGH', '`parseInt` without a radix. Pass 10 explicitly: an input like "08" or "0x10" is otherwise ' +
60
+ 'parsed by a rule most readers do not have in mind.'));
61
+ // `core/hardcoded-secrets` — deliberately NOT here. A regex over added lines is
62
+ // how a secret scanner earns a reputation for false positives, and the gate
63
+ // already runs a real one against verified secrets only. The model keeps the rule
64
+ // for the cases a scanner misses, like a credential in a comment.
65
+ export const CHECKS = {
66
+ 'core/untracked-todo': untrackedTodo,
67
+ 'core/type-checker-suppression': typeCheckerSuppression,
68
+ 'javascript/var-in-new-code': varInNewCode,
69
+ 'javascript/unsafe-numeric-coercion': parseIntWithoutRadix,
70
+ };
71
+ export function runChecks(ctx, only) {
72
+ const ids = only ?? Object.keys(CHECKS);
73
+ const findings = [];
74
+ const evaluated = [];
75
+ for (const id of ids) {
76
+ const check = CHECKS[id];
77
+ if (!check)
78
+ continue;
79
+ evaluated.push(id);
80
+ findings.push(...check(ctx));
81
+ }
82
+ // Stable order: a gate whose output reorders between runs on the same diff
83
+ // makes every re-run look like a change.
84
+ findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.ruleId.localeCompare(b.ruleId));
85
+ return { findings, evaluated };
86
+ }
87
+ /** The output contract, rendered by code — never free-typed. */
88
+ export function formatFinding(finding) {
89
+ return `Redline/${finding.severity} [${finding.ruleId}]: ${finding.problem}`;
90
+ }
91
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../cli/policy/checks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ // A unified diff, reduced to the only thing a deterministic check may look at:
2
+ // the lines this change ADDED, with their file and line number.
3
+ //
4
+ // Added lines only, and that is a rule not an optimisation. `core/unrelated-change`
5
+ // and the "what NOT to flag" section both say the same thing — existing patterns a
6
+ // pull request merely touches are not this pull request's problem. A checker that
7
+ // read whole files would flag every `var` in a legacy file the author renamed, and
8
+ // the author would be right to ignore it, and then to ignore the next one too.
9
+ // `b/` OR `/dev/null`: the prefixed form is a real destination path, the second
10
+ // is a deletion. Matching only the prefixed form left `+++ /dev/null` falling
11
+ // through to the added-line branch below, where it became a phantom line of
12
+ // content attributed to the PREVIOUS file — so a deleted file could produce a
13
+ // finding on a file the change never touched.
14
+ const FILE_HEADER = /^\+\+\+ (?:b\/(.+)|(\/dev\/null))$/;
15
+ const HUNK = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
16
+ export function parseDiff(diff) {
17
+ const added = [];
18
+ let file = null;
19
+ let line = 0;
20
+ for (const raw of diff.split('\n')) {
21
+ const header = FILE_HEADER.exec(raw);
22
+ if (header) {
23
+ // A deletion has no destination path, so nothing after it is an addition
24
+ // until the next file header.
25
+ file = header[2] ? null : (header[1] ?? null);
26
+ continue;
27
+ }
28
+ const hunk = HUNK.exec(raw);
29
+ if (hunk) {
30
+ line = Number.parseInt(hunk[1] ?? '1', 10);
31
+ continue;
32
+ }
33
+ if (file === null)
34
+ continue;
35
+ if (raw.startsWith('+')) {
36
+ added.push({ file, line, text: raw.slice(1) });
37
+ line += 1;
38
+ }
39
+ else if (raw.startsWith('-')) {
40
+ // A removed line does not advance the new-file counter.
41
+ continue;
42
+ }
43
+ else if (raw.startsWith(' ') || raw === '') {
44
+ line += 1;
45
+ }
46
+ // Everything else — `diff --git`, `index`, `\ No newline` — is metadata.
47
+ }
48
+ return added;
49
+ }
50
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../cli/policy/diff.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,gEAAgE;AAChE,EAAE;AACF,oFAAoF;AACpF,mFAAmF;AACnF,kFAAkF;AAClF,mFAAmF;AACnF,+EAA+E;AAU/E,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,8CAA8C;AAC9C,MAAM,WAAW,GAAG,oCAAoC,CAAC;AACzD,MAAM,IAAI,GAAG,uCAAuC,CAAC;AAErD,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,MAAM,EAAE,CAAC;YACX,yEAAyE;YACzE,8BAA8B;YAC9B,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YAC9C,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,IAAI;YAAE,SAAS;QAE5B,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/C,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,wDAAwD;YACxD,SAAS;QACX,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;QACD,yEAAyE;IAC3E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { parseConfig } from "../config/redline-json.js";
2
+ export const DISCOVERY_QUERY = `
3
+ query($org: String!, $cursor: String) {
4
+ repositoryOwner(login: $org) {
5
+ repositories(first: 100, after: $cursor, isArchived: false, ownerAffiliations: OWNER) {
6
+ nodes {
7
+ name
8
+ defaultBranchRef { name }
9
+ object(expression: "HEAD:.redline.json") { ... on Blob { text } }
10
+ }
11
+ pageInfo { hasNextPage endCursor }
12
+ }
13
+ }
14
+ }`;
15
+ // A host that reports hasNextPage: true forever must not hang a nightly job.
16
+ // 200 pages of 100 is 20,000 repositories — far past any real estate.
17
+ const MAX_PAGES = 200;
18
+ export async function discoverGitHub(client, org) {
19
+ const entries = [];
20
+ const problems = [];
21
+ let cursor = null;
22
+ let pages = 0;
23
+ do {
24
+ const data = await client.graphql(DISCOVERY_QUERY, { org, cursor });
25
+ const repos = data.repositoryOwner?.repositories;
26
+ if (!repos) {
27
+ problems.push(`${org}: owner not readable with this token`);
28
+ break;
29
+ }
30
+ for (const node of repos.nodes) {
31
+ // No .redline.json is the discovery signal for "not onboarded", not an
32
+ // error: it is why nothing has to write a register on the way in.
33
+ const text = node.object?.text;
34
+ if (!text)
35
+ continue;
36
+ if (!node.defaultBranchRef) {
37
+ problems.push(`${org}/${node.name}: has .redline.json but no default branch`);
38
+ continue;
39
+ }
40
+ // The estate's own files are external input to this walk, so this is a
41
+ // real boundary and the guard belongs here. Without it one hand-edited
42
+ // config would abort the walk and publish a register missing every
43
+ // repository after it.
44
+ let config;
45
+ try {
46
+ config = parseConfig(JSON.parse(text));
47
+ }
48
+ catch (error) {
49
+ const detail = error instanceof Error ? error.message : String(error);
50
+ problems.push(`${org}/${node.name}: ${detail}`);
51
+ continue;
52
+ }
53
+ entries.push({
54
+ host: 'github',
55
+ org,
56
+ repo: node.name,
57
+ defaultBranch: node.defaultBranchRef.name,
58
+ profile: config.profile,
59
+ standardsVersion: config.standardsVersion,
60
+ cliVersion: config.cliVersion,
61
+ onboardedAt: config.onboardedAt,
62
+ rung: config.rung,
63
+ });
64
+ }
65
+ cursor = repos.pageInfo.hasNextPage ? repos.pageInfo.endCursor : null;
66
+ pages += 1;
67
+ if (cursor !== null && pages >= MAX_PAGES) {
68
+ problems.push(`${org}: stopped after ${MAX_PAGES} pages — the register may be incomplete`);
69
+ break;
70
+ }
71
+ } while (cursor !== null);
72
+ return { entries, problems };
73
+ }
74
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../cli/registry/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA8BxD,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;EAY7B,CAAC;AAEH,6EAA6E;AAC7E,sEAAsE;AACtE,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAqC,EACrC,GAAW;IAEX,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,GAAG,CAAC;QACF,MAAM,IAAI,GAAY,MAAM,MAAM,CAAC,OAAO,CAAU,eAAe,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QACtF,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,sCAAsC,CAAC,CAAC;YAC5D,MAAM;QACR,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/B,uEAAuE;YACvE,kEAAkE;YAClE,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;YAC/B,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,2CAA2C,CAAC,CAAC;gBAC9E,SAAS;YACX,CAAC;YACD,uEAAuE;YACvE,uEAAuE;YACvE,mEAAmE;YACnE,uBAAuB;YACvB,IAAI,MAAM,CAAC;YACX,IAAI,CAAC;gBACH,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtE,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;gBAChD,SAAS;YACX,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,QAAQ;gBACd,GAAG;gBACH,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI;gBACzC,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QACtE,KAAK,IAAI,CAAC,CAAC;QACX,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,mBAAmB,SAAS,yCAAyC,CAAC,CAAC;YAC3F,MAAM;QACR,CAAC;IACH,CAAC,QAAQ,MAAM,KAAK,IAAI,EAAE;IAE1B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { RedlineError } from "../core/errors.js";
2
+ const byOrgThenRepo = (a, b) => a.org.localeCompare(b.org) || a.repo.localeCompare(b.repo);
3
+ // The register is committed on a schedule. Unordered entries would rewrite the
4
+ // file on every run and make each diff unreadable, so ordering is part of the
5
+ // format, not a presentation choice.
6
+ export function serializeRegistry(registry) {
7
+ const ordered = {
8
+ generatedAt: registry.generatedAt,
9
+ source: registry.source,
10
+ entries: [...registry.entries].sort(byOrgThenRepo),
11
+ };
12
+ return `${JSON.stringify(ordered, null, 2)}\n`;
13
+ }
14
+ export function parseRegistry(raw) {
15
+ const bad = (what) => {
16
+ throw new RedlineError('failed', `registry.json is invalid: ${what}`);
17
+ };
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(raw);
21
+ }
22
+ catch {
23
+ return bad('not valid JSON');
24
+ }
25
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
26
+ return bad('expected an object');
27
+ }
28
+ const o = parsed;
29
+ if (typeof o['generatedAt'] !== 'string')
30
+ return bad('generatedAt must be a string');
31
+ if (typeof o['source'] !== 'string')
32
+ return bad('source must be a string');
33
+ if (!Array.isArray(o['entries']))
34
+ return bad('entries must be an array');
35
+ return {
36
+ generatedAt: o['generatedAt'],
37
+ source: o['source'],
38
+ entries: o['entries'],
39
+ };
40
+ }
41
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../cli/registry/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,MAAM,aAAa,GAAG,CAAC,CAAgB,EAAE,CAAgB,EAAU,EAAE,CACnE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAE7D,+EAA+E;AAC/E,8EAA8E;AAC9E,qCAAqC;AACrC,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IAClD,MAAM,OAAO,GAAa;QACxB,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC;KACnD,CAAC;IACF,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAS,EAAE;QAClC,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,OAAO,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACnC,CAAC;IACD,MAAM,CAAC,GAAG,MAAiC,CAAC;IAC5C,IAAI,OAAO,CAAC,CAAC,aAAa,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,8BAA8B,CAAC,CAAC;IACrF,IAAI,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAC3E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAEzE,OAAO;QACL,WAAW,EAAE,CAAC,CAAC,aAAa,CAAC;QAC7B,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC;QACnB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAoB;KACzC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../cli/registry/types.ts"],"names":[],"mappings":""}