create-backbone-template 0.1.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.
Files changed (182) hide show
  1. package/README.md +33 -0
  2. package/bin/create-backbone-template.js +5 -0
  3. package/package.json +30 -0
  4. package/src/create-backbone-template.js +204 -0
  5. package/template/.agents/skills/agent-browser/SKILL.md +55 -0
  6. package/template/.agents/skills/create-plan/SKILL.md +52 -0
  7. package/template/.agents/skills/create-plan/agents/openai.yaml +4 -0
  8. package/template/.agents/skills/create-pr-presentation/SKILL.md +86 -0
  9. package/template/.agents/skills/create-pr-presentation/agents/openai.yaml +4 -0
  10. package/template/.agents/skills/implement-plan/SKILL.md +26 -0
  11. package/template/.agents/skills/implement-plan/agents/openai.yaml +4 -0
  12. package/template/.agents/skills/review-plan/SKILL.md +38 -0
  13. package/template/.agents/skills/review-plan/agents/openai.yaml +4 -0
  14. package/template/.env.schema +30 -0
  15. package/template/.env.test +6 -0
  16. package/template/.oxlintrc.json +67 -0
  17. package/template/.vscode/extensions.json +3 -0
  18. package/template/.vscode/settings.json +23 -0
  19. package/template/AGENTS.md +55 -0
  20. package/template/Cargo.lock +2648 -0
  21. package/template/Cargo.toml +29 -0
  22. package/template/Justfile +140 -0
  23. package/template/README.md +72 -0
  24. package/template/TODO.md +1 -0
  25. package/template/_gitignore +12 -0
  26. package/template/buf.gen.yaml +7 -0
  27. package/template/buf.yaml +10 -0
  28. package/template/client/.oxfmtrc.json +8 -0
  29. package/template/client/.oxlintrc.json +57 -0
  30. package/template/client/README.md +19 -0
  31. package/template/client/_gitignore +5 -0
  32. package/template/client/index.html +12 -0
  33. package/template/client/package.json +47 -0
  34. package/template/client/packages/design-system/package.json +19 -0
  35. package/template/client/packages/design-system/src/index.ts +2 -0
  36. package/template/client/packages/design-system-basic/package.json +18 -0
  37. package/template/client/packages/design-system-basic/src/button.stories.tsx +50 -0
  38. package/template/client/packages/design-system-basic/src/button.tsx +26 -0
  39. package/template/client/packages/design-system-basic/src/empty-state.stories.tsx +18 -0
  40. package/template/client/packages/design-system-basic/src/empty-state.tsx +17 -0
  41. package/template/client/packages/design-system-basic/src/form-field.stories.tsx +15 -0
  42. package/template/client/packages/design-system-basic/src/form-field.tsx +10 -0
  43. package/template/client/packages/design-system-basic/src/form.stories.tsx +27 -0
  44. package/template/client/packages/design-system-basic/src/form.tsx +9 -0
  45. package/template/client/packages/design-system-basic/src/heading.stories.tsx +14 -0
  46. package/template/client/packages/design-system-basic/src/heading.tsx +25 -0
  47. package/template/client/packages/design-system-basic/src/index.tsx +15 -0
  48. package/template/client/packages/design-system-basic/src/inline.stories.tsx +13 -0
  49. package/template/client/packages/design-system-basic/src/inline.tsx +5 -0
  50. package/template/client/packages/design-system-basic/src/layout.stories.tsx +24 -0
  51. package/template/client/packages/design-system-basic/src/layout.tsx +14 -0
  52. package/template/client/packages/design-system-basic/src/loader.stories.tsx +8 -0
  53. package/template/client/packages/design-system-basic/src/loader.tsx +11 -0
  54. package/template/client/packages/design-system-basic/src/navigation.stories.tsx +16 -0
  55. package/template/client/packages/design-system-basic/src/navigation.tsx +18 -0
  56. package/template/client/packages/design-system-basic/src/notice.stories.tsx +13 -0
  57. package/template/client/packages/design-system-basic/src/notice.tsx +5 -0
  58. package/template/client/packages/design-system-basic/src/stack.stories.tsx +17 -0
  59. package/template/client/packages/design-system-basic/src/stack.tsx +5 -0
  60. package/template/client/packages/design-system-basic/src/styles.css +254 -0
  61. package/template/client/packages/design-system-basic/src/text-input.stories.tsx +13 -0
  62. package/template/client/packages/design-system-basic/src/text-input.tsx +5 -0
  63. package/template/client/packages/design-system-basic/src/text.stories.tsx +21 -0
  64. package/template/client/packages/design-system-basic/src/text.tsx +5 -0
  65. package/template/client/packages/design-system-contract/package.json +15 -0
  66. package/template/client/packages/design-system-contract/src/button.ts +10 -0
  67. package/template/client/packages/design-system-contract/src/empty-state.ts +9 -0
  68. package/template/client/packages/design-system-contract/src/form-field.ts +9 -0
  69. package/template/client/packages/design-system-contract/src/form.ts +9 -0
  70. package/template/client/packages/design-system-contract/src/heading.ts +9 -0
  71. package/template/client/packages/design-system-contract/src/index.ts +13 -0
  72. package/template/client/packages/design-system-contract/src/inline.ts +7 -0
  73. package/template/client/packages/design-system-contract/src/layout.ts +8 -0
  74. package/template/client/packages/design-system-contract/src/loader.ts +7 -0
  75. package/template/client/packages/design-system-contract/src/navigation.ts +13 -0
  76. package/template/client/packages/design-system-contract/src/notice.ts +8 -0
  77. package/template/client/packages/design-system-contract/src/stack.ts +8 -0
  78. package/template/client/packages/design-system-contract/src/text-input.ts +5 -0
  79. package/template/client/packages/design-system-contract/src/text.ts +9 -0
  80. package/template/client/packages/design-system-lint/fixtures/invalid/external-ui-import.tsx +5 -0
  81. package/template/client/packages/design-system-lint/fixtures/invalid/raw-dom-jsx.tsx +3 -0
  82. package/template/client/packages/design-system-lint/fixtures/invalid/two-violations.tsx +7 -0
  83. package/template/client/packages/design-system-lint/fixtures/valid/design-system-only.tsx +13 -0
  84. package/template/client/packages/design-system-lint/package.json +23 -0
  85. package/template/client/packages/design-system-lint/src/check-design-system-architecture.ts +22 -0
  86. package/template/client/packages/design-system-lint/src/design-system-architecture.ts +286 -0
  87. package/template/client/packages/design-system-lint/src/oxlint-plugin.ts +11 -0
  88. package/template/client/packages/design-system-lint/src/page-architecture.ts +382 -0
  89. package/template/client/packages/design-system-lint/src/rules.ts +111 -0
  90. package/template/client/packages/design-system-lint/test/design-system-architecture.test.ts +243 -0
  91. package/template/client/packages/design-system-lint/test/oxlint-fixtures.test.ts +159 -0
  92. package/template/client/packages/design-system-lint/test/page-architecture.test.ts +175 -0
  93. package/template/client/packages/design-system-lint/test/rules.test.ts +65 -0
  94. package/template/client/packages/design-system-lint/tsconfig.json +29 -0
  95. package/template/client/src/App.tsx +77 -0
  96. package/template/client/src/design-system-components.test.tsx +75 -0
  97. package/template/client/src/gen/helloworld/v1/helloworld_pb.ts +63 -0
  98. package/template/client/src/main.tsx +18 -0
  99. package/template/client/src/pages/hello/hello-page.stories.tsx +20 -0
  100. package/template/client/src/pages/hello/hello-page.test.tsx +90 -0
  101. package/template/client/src/pages/hello/hello-page.tsx +126 -0
  102. package/template/client/src/pages/page.ts +20 -0
  103. package/template/client/src/testing/create-preview-events.test.ts +36 -0
  104. package/template/client/src/testing/create-preview-events.ts +30 -0
  105. package/template/client/src/vite-env.d.ts +1 -0
  106. package/template/client/tsconfig.json +32 -0
  107. package/template/client/vite.config.ts +21 -0
  108. package/template/client/vite.ladle.config.ts +5 -0
  109. package/template/e2e/.gherkin-lintrc +20 -0
  110. package/template/e2e/.oxfmtrc.json +15 -0
  111. package/template/e2e/.oxlintrc.json +37 -0
  112. package/template/e2e/_gitignore +4 -0
  113. package/template/e2e/features/helloworld.feature +10 -0
  114. package/template/e2e/package.json +42 -0
  115. package/template/e2e/playwright.config.ts +16 -0
  116. package/template/e2e/support/app-gherkin.ts +4 -0
  117. package/template/e2e/support/fixtures.ts +236 -0
  118. package/template/e2e/support/gherkin-fixtures/duplicate-id.feature +9 -0
  119. package/template/e2e/support/gherkin-fixtures/duplicate-id.spec.ts +7 -0
  120. package/template/e2e/support/gherkin-fixtures/extra-implementation.spec.ts +7 -0
  121. package/template/e2e/support/gherkin-fixtures/extra-step.spec.ts +10 -0
  122. package/template/e2e/support/gherkin-fixtures/happy-path.spec.ts +4 -0
  123. package/template/e2e/support/gherkin-fixtures/missing-id.feature +4 -0
  124. package/template/e2e/support/gherkin-fixtures/missing-id.spec.ts +7 -0
  125. package/template/e2e/support/gherkin-fixtures/missing-implementation.spec.ts +7 -0
  126. package/template/e2e/support/gherkin-fixtures/missing-step.spec.ts +7 -0
  127. package/template/e2e/support/gherkin-fixtures/playwright.config.ts +7 -0
  128. package/template/e2e/support/gherkin-fixtures/scenario-outline.feature +9 -0
  129. package/template/e2e/support/gherkin-fixtures/scenario-outline.spec.ts +7 -0
  130. package/template/e2e/support/gherkin-fixtures/step-mismatch.spec.ts +9 -0
  131. package/template/e2e/support/gherkin-fixtures/valid-implementations.ts +23 -0
  132. package/template/e2e/support/gherkin-fixtures/valid-scenarios.feature +26 -0
  133. package/template/e2e/support/gherkin.test.ts +184 -0
  134. package/template/e2e/support/gherkin.ts +321 -0
  135. package/template/e2e/support/oxlint-plugin.test.ts +328 -0
  136. package/template/e2e/support/oxlint-plugin.ts +485 -0
  137. package/template/e2e/tests/helloworld.spec.ts +39 -0
  138. package/template/e2e/tsconfig.json +26 -0
  139. package/template/e2e/tsconfig.oxlint-plugin.json +12 -0
  140. package/template/package.json +9 -0
  141. package/template/pnpm-lock.yaml +10723 -0
  142. package/template/pnpm-workspace.yaml +8 -0
  143. package/template/pr-slide/README.md +95 -0
  144. package/template/pr-slide/package.json +23 -0
  145. package/template/pr-slide/src/cli.js +262 -0
  146. package/template/pr-slide/src/generate-pr-deck.js +833 -0
  147. package/template/pr-slide/src/git-context.js +91 -0
  148. package/template/pr-slide/src/presentation-paths.js +9 -0
  149. package/template/pr-slide/src/presentations.js +53 -0
  150. package/template/pr-slide/test/generate-pr-deck.test.js +118 -0
  151. package/template/pr-slide/test/presentation-paths.test.js +14 -0
  152. package/template/pr-slide/test/presentations.test.js +50 -0
  153. package/template/proto/helloworld/v1/helloworld.proto +15 -0
  154. package/template/scripts/run-e2e.sh +10 -0
  155. package/template/server/Cargo.toml +26 -0
  156. package/template/server/build.rs +9 -0
  157. package/template/server/dylint/backbone_server_lints/.cargo/config.toml +6 -0
  158. package/template/server/dylint/backbone_server_lints/Cargo.lock +1581 -0
  159. package/template/server/dylint/backbone_server_lints/Cargo.toml +21 -0
  160. package/template/server/dylint/backbone_server_lints/README.md +5 -0
  161. package/template/server/dylint/backbone_server_lints/_gitignore +1 -0
  162. package/template/server/dylint/backbone_server_lints/rust-toolchain +3 -0
  163. package/template/server/dylint/backbone_server_lints/src/lib.rs +612 -0
  164. package/template/server/dylint/backbone_server_lints/ui/lib.rs +4 -0
  165. package/template/server/dylint/backbone_server_lints/ui/lib.stderr +10 -0
  166. package/template/server/dylint/backbone_server_lints/ui/long_file.rs +303 -0
  167. package/template/server/dylint/backbone_server_lints/ui/long_file.stderr +6 -0
  168. package/template/server/dylint/backbone_server_lints/ui/main.rs +59 -0
  169. package/template/server/dylint/backbone_server_lints/ui/main.stderr +85 -0
  170. package/template/server/migrations/20260520120000_create_projects.sql +12 -0
  171. package/template/server/migrations/20260524160000_create_hello_world_inputs.sql +12 -0
  172. package/template/server/src/config.rs +27 -0
  173. package/template/server/src/db/hello_world.rs +34 -0
  174. package/template/server/src/db/hello_world_tests.rs +11 -0
  175. package/template/server/src/db/mod.rs +39 -0
  176. package/template/server/src/lib.rs +10 -0
  177. package/template/server/src/main.rs +43 -0
  178. package/template/server/src/rpc/greeter/mod.rs +31 -0
  179. package/template/server/src/rpc/greeter/say_hello.rs +27 -0
  180. package/template/server/src/rpc/mod.rs +8 -0
  181. package/template/server/src/state.rs +13 -0
  182. package/template/skills-lock.json +11 -0
@@ -0,0 +1,328 @@
1
+ import assert from "node:assert/strict"
2
+ import { spawnSync } from "node:child_process"
3
+ import fs from "node:fs"
4
+ import os from "node:os"
5
+ import path from "node:path"
6
+ import test from "node:test"
7
+
8
+ type OxlintDiagnostic = {
9
+ code?: string
10
+ filename?: string
11
+ labels?: Array<{
12
+ span?: {
13
+ column?: number
14
+ line?: number
15
+ }
16
+ }>
17
+ message?: string
18
+ }
19
+
20
+ type OxlintJsonOutput = {
21
+ diagnostics?: OxlintDiagnostic[]
22
+ }
23
+
24
+ const packageRoot = process.cwd()
25
+
26
+ test("valid Gherkin-backed Playwright specs pass real oxlint", () => {
27
+ const fixture = createFixture({
28
+ feature: `Feature: Greeting
29
+
30
+ @id:greeting.say-hello
31
+ Scenario: Say hello
32
+ Given the visitor is on the hello page
33
+ When they ask to greet Playwright
34
+ Then they see the Playwright greeting
35
+ `,
36
+ spec: `import { feature } from "../../support/gherkin"
37
+
38
+ feature("../features/greeting.feature", {
39
+ "greeting.say-hello": async ({ scenario }) => {
40
+ await scenario.step("Given the visitor is on the hello page", async () => {})
41
+ await scenario.step("When they ask to greet Playwright", async () => {})
42
+ await scenario.step("Then they see the Playwright greeting", async () => {})
43
+ },
44
+ })
45
+ `,
46
+ })
47
+
48
+ try {
49
+ const result = runOxlint(fixture.path)
50
+
51
+ assert.equal(result.status, 0)
52
+ assert.deepEqual(result.diagnostics, [])
53
+ } finally {
54
+ fixture.dispose()
55
+ }
56
+ })
57
+
58
+ test("drift between Gherkin and Playwright specs reports stable oxlint diagnostics", () => {
59
+ const fixture = createFixture({
60
+ feature: `Feature: Greeting
61
+
62
+ @id:greeting.say-hello
63
+ Scenario: Say hello
64
+ Given the visitor is on the hello page
65
+ When they ask to greet Playwright
66
+ Then they see the Playwright greeting
67
+ `,
68
+ spec: `import { feature } from "../../support/gherkin"
69
+
70
+ feature("../features/greeting.feature", {
71
+ "greeting.say-hello": async ({ scenario }) => {
72
+ await scenario.step("Given the visitor is on the hello page", async () => {})
73
+ await scenario.step("Then they see the Playwright greeting", async () => {})
74
+ },
75
+ "greeting.extra": async ({ scenario }) => {
76
+ await scenario.step("Given an extra implementation", async () => {})
77
+ },
78
+ })
79
+ `,
80
+ })
81
+
82
+ try {
83
+ const result = runOxlint(fixture.path)
84
+
85
+ assert.equal(result.status, 1)
86
+ assert.deepEqual(
87
+ result.diagnostics.map((diagnostic) => ({
88
+ code: diagnostic.code,
89
+ column: diagnostic.column,
90
+ file: path.basename(diagnostic.filename ?? ""),
91
+ line: diagnostic.line,
92
+ message: diagnostic.message,
93
+ })),
94
+ [
95
+ {
96
+ code: "backbone-e2e/valid-gherkin-feature",
97
+ column: 3,
98
+ file: "greeting.spec.ts",
99
+ line: 4,
100
+ message:
101
+ 'missing implemented step for "greeting.say-hello" from ' +
102
+ `${path.join(fixture.path, "features/greeting.feature")}:4: ` +
103
+ '"When they ask to greet Playwright".',
104
+ },
105
+ {
106
+ code: "backbone-e2e/valid-gherkin-feature",
107
+ column: 3,
108
+ file: "greeting.spec.ts",
109
+ line: 8,
110
+ message: 'implementation id "greeting.extra" has no matching scenario.',
111
+ },
112
+ ],
113
+ )
114
+ } finally {
115
+ fixture.dispose()
116
+ }
117
+ })
118
+
119
+ test("renamed scenario.step labels report missing and unexpected step diagnostics", () => {
120
+ const fixture = createFixture({
121
+ feature: `Feature: Greeting
122
+
123
+ @id:greeting.say-hello
124
+ Scenario: Say hello
125
+ Given the visitor is on the hello page
126
+ When they ask to greet Playwright
127
+ Then they see the Playwright greeting
128
+ `,
129
+ spec: `import { feature } from "../../support/gherkin"
130
+
131
+ feature("../features/greeting.feature", {
132
+ "greeting.say-hello": async ({ scenario }) => {
133
+ await scenario.step("Given the visitor is on the hello page", async () => {})
134
+ await scenario.step("When they ask to greet Cypress", async () => {})
135
+ await scenario.step("Then they see the Playwright greeting", async () => {})
136
+ },
137
+ })
138
+ `,
139
+ })
140
+
141
+ try {
142
+ const result = runOxlint(fixture.path)
143
+
144
+ assert.equal(result.status, 1)
145
+ assert.deepEqual(
146
+ result.diagnostics.map((diagnostic) => ({
147
+ code: diagnostic.code,
148
+ column: diagnostic.column,
149
+ file: path.basename(diagnostic.filename ?? ""),
150
+ line: diagnostic.line,
151
+ message: diagnostic.message,
152
+ })),
153
+ [
154
+ {
155
+ code: "backbone-e2e/valid-gherkin-feature",
156
+ column: 3,
157
+ file: "greeting.spec.ts",
158
+ line: 4,
159
+ message:
160
+ 'missing implemented step for "greeting.say-hello" from ' +
161
+ `${path.join(fixture.path, "features/greeting.feature")}:4: ` +
162
+ '"When they ask to greet Playwright".',
163
+ },
164
+ {
165
+ code: "backbone-e2e/valid-gherkin-feature",
166
+ column: 25,
167
+ file: "greeting.spec.ts",
168
+ line: 6,
169
+ message:
170
+ 'unexpected implemented step for "greeting.say-hello": ' +
171
+ '"When they ask to greet Cypress". No step with this label exists in the feature scenario.',
172
+ },
173
+ ],
174
+ )
175
+ } finally {
176
+ fixture.dispose()
177
+ }
178
+ })
179
+
180
+ test("extra scenario.step labels report implementation and step diagnostics", () => {
181
+ const fixture = createFixture({
182
+ feature: `Feature: Greeting
183
+
184
+ @id:greeting.say-hello
185
+ Scenario: Say hello
186
+ Given the visitor is on the hello page
187
+ When they ask to greet Playwright
188
+ Then they see the Playwright greeting
189
+ `,
190
+ spec: `import { feature } from "../../support/gherkin"
191
+
192
+ feature("../features/greeting.feature", {
193
+ "greeting.say-hello": async ({ scenario }) => {
194
+ await scenario.step("Given the visitor is on the hello page", async () => {})
195
+ await scenario.step("When they ask to greet Playwright", async () => {})
196
+ await scenario.step("Then they see an extra Cypress greeting", async () => {})
197
+ await scenario.step("Then they see the Playwright greeting", async () => {})
198
+ },
199
+ })
200
+ `,
201
+ })
202
+
203
+ try {
204
+ const result = runOxlint(fixture.path)
205
+
206
+ assert.equal(result.status, 1)
207
+ assert.deepEqual(
208
+ result.diagnostics.map((diagnostic) => ({
209
+ code: diagnostic.code,
210
+ column: diagnostic.column,
211
+ file: path.basename(diagnostic.filename ?? ""),
212
+ line: diagnostic.line,
213
+ message: diagnostic.message,
214
+ })),
215
+ [
216
+ {
217
+ code: "backbone-e2e/valid-gherkin-feature",
218
+ column: 3,
219
+ file: "greeting.spec.ts",
220
+ line: 4,
221
+ message:
222
+ 'extra implemented step(s) for "greeting.say-hello" not present in ' +
223
+ `${path.join(fixture.path, "features/greeting.feature")}:4: ` +
224
+ '"Then they see an extra Cypress greeting".',
225
+ },
226
+ {
227
+ code: "backbone-e2e/valid-gherkin-feature",
228
+ column: 25,
229
+ file: "greeting.spec.ts",
230
+ line: 7,
231
+ message:
232
+ 'unexpected implemented step for "greeting.say-hello": ' +
233
+ '"Then they see an extra Cypress greeting". ' +
234
+ "No step with this label exists in the feature scenario.",
235
+ },
236
+ ],
237
+ )
238
+ } finally {
239
+ fixture.dispose()
240
+ }
241
+ })
242
+
243
+ function createFixture(files: { feature: string; spec: string }) {
244
+ const fixturePath = fs.mkdtempSync(path.join(os.tmpdir(), "backbone-e2e-oxlint-"))
245
+
246
+ fs.mkdirSync(path.join(fixturePath, "features"))
247
+ fs.mkdirSync(path.join(fixturePath, "tests"))
248
+ fs.writeFileSync(path.join(fixturePath, "features/greeting.feature"), files.feature)
249
+ fs.writeFileSync(path.join(fixturePath, "tests/greeting.spec.ts"), files.spec)
250
+ fs.writeFileSync(
251
+ path.join(fixturePath, "oxlint.json"),
252
+ JSON.stringify({
253
+ jsPlugins: [path.join(packageRoot, "support/dist/oxlint-plugin.js")],
254
+ rules: {
255
+ "backbone-e2e/valid-gherkin-feature": "error",
256
+ },
257
+ }),
258
+ )
259
+
260
+ return {
261
+ path: fixturePath,
262
+ dispose() {
263
+ fs.rmSync(fixturePath, { force: true, recursive: true })
264
+ },
265
+ }
266
+ }
267
+
268
+ function runOxlint(fixturePath: string) {
269
+ const result = spawnSync(
270
+ process.platform === "win32" ? "pnpm.cmd" : "pnpm",
271
+ [
272
+ "exec",
273
+ "oxlint",
274
+ "--config",
275
+ path.join(fixturePath, "oxlint.json"),
276
+ "--format",
277
+ "json",
278
+ path.join(fixturePath, "tests"),
279
+ ],
280
+ {
281
+ cwd: packageRoot,
282
+ encoding: "utf8",
283
+ },
284
+ )
285
+
286
+ if (result.error !== undefined && result.status === null) {
287
+ throw result.error
288
+ }
289
+
290
+ return {
291
+ diagnostics: parseDiagnostics(result.stdout),
292
+ status: result.status,
293
+ stderr: result.stderr,
294
+ stdout: result.stdout,
295
+ }
296
+ }
297
+
298
+ function parseDiagnostics(output: string) {
299
+ if (output.trim() === "") {
300
+ return []
301
+ }
302
+
303
+ const parsed = JSON.parse(output) as OxlintJsonOutput
304
+
305
+ return (parsed.diagnostics ?? [])
306
+ .filter((diagnostic) => diagnostic.code?.startsWith("backbone-e2e"))
307
+ .map((diagnostic) => ({
308
+ code: normalizeRuleCode(diagnostic.code),
309
+ column: diagnostic.labels?.[0]?.span?.column,
310
+ filename: diagnostic.filename,
311
+ line: diagnostic.labels?.[0]?.span?.line,
312
+ message: diagnostic.message,
313
+ }))
314
+ .sort((left, right) => {
315
+ const leftLine = left.line ?? 0
316
+ const rightLine = right.line ?? 0
317
+
318
+ if (leftLine !== rightLine) {
319
+ return leftLine - rightLine
320
+ }
321
+
322
+ return (left.message ?? "").localeCompare(right.message ?? "")
323
+ })
324
+ }
325
+
326
+ function normalizeRuleCode(code: string | undefined) {
327
+ return code?.replace(/^backbone-e2e\((.+)\)$/, "backbone-e2e/$1")
328
+ }