Skip to content

Commit cab7d92

Browse files
committed
ultracite fix
1 parent 6ab4925 commit cab7d92

17 files changed

Lines changed: 230 additions & 68 deletions

File tree

sry/.cursor/hooks.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"version": 1,
3+
"hooks": {
4+
"afterFileEdit": [
5+
{
6+
"command": "npx ultracite fix"
7+
}
8+
]
9+
}
10+
}

sry/.cursor/rules/ultracite.mdc

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
description: Ultracite Rules - AI-Ready Formatter and Linter
3+
globs: "**/*.{ts,tsx,js,jsx,json,jsonc,html,vue,svelte,astro,css,yaml,yml,graphql,gql,md,mdx,grit}"
4+
alwaysApply: false
5+
---
6+
7+
# Ultracite Code Standards
8+
9+
This project uses **Ultracite**, a zero-config Biome preset that enforces strict code quality standards through automated formatting and linting.
10+
11+
## Quick Reference
12+
13+
- **Format code**: `npx ultracite fix`
14+
- **Check for issues**: `npx ultracite check`
15+
- **Diagnose setup**: `npx ultracite doctor`
16+
17+
Biome (the underlying engine) provides extremely fast Rust-based linting and formatting. Most issues are automatically fixable.
18+
19+
---
20+
21+
## Core Principles
22+
23+
Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity.
24+
25+
### Type Safety & Explicitness
26+
27+
- Use explicit types for function parameters and return values when they enhance clarity
28+
- Prefer `unknown` over `any` when the type is genuinely unknown
29+
- Use const assertions (`as const`) for immutable values and literal types
30+
- Leverage TypeScript's type narrowing instead of type assertions
31+
- Use meaningful variable names instead of magic numbers - extract constants with descriptive names
32+
33+
### Modern JavaScript/TypeScript
34+
35+
- Use arrow functions for callbacks and short functions
36+
- Prefer `for...of` loops over `.forEach()` and indexed `for` loops
37+
- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access
38+
- Prefer template literals over string concatenation
39+
- Use destructuring for object and array assignments
40+
- Use `const` by default, `let` only when reassignment is needed, never `var`
41+
42+
### Async & Promises
43+
44+
- Always `await` promises in async functions - don't forget to use the return value
45+
- Use `async/await` syntax instead of promise chains for better readability
46+
- Handle errors appropriately in async code with try-catch blocks
47+
- Don't use async functions as Promise executors
48+
49+
### Error Handling & Debugging
50+
51+
- Remove `console.log`, `debugger`, and `alert` statements from production code
52+
- Throw `Error` objects with descriptive messages, not strings or other values
53+
- Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them
54+
- Prefer early returns over nested conditionals for error cases
55+
56+
### Code Organization
57+
58+
- Keep functions focused and under reasonable cognitive complexity limits
59+
- Extract complex conditions into well-named boolean variables
60+
- Use early returns to reduce nesting
61+
- Prefer simple conditionals over nested ternary operators
62+
- Group related code together and separate concerns
63+
64+
### Security
65+
66+
- Add `rel="noopener"` when using `target="_blank"` on links
67+
- Avoid `dangerouslySetInnerHTML` unless absolutely necessary
68+
- Don't use `eval()` or assign directly to `document.cookie`
69+
- Validate and sanitize user input
70+
71+
### Performance
72+
73+
- Avoid spread syntax in accumulators within loops
74+
- Use top-level regex literals instead of creating them in loops
75+
- Prefer specific imports over namespace imports
76+
- Avoid barrel files (index files that re-export everything)
77+
- Use proper image components (e.g., Next.js `<Image>`) over `<img>` tags
78+
79+
---
80+
81+
## Testing
82+
83+
- Write assertions inside `it()` or `test()` blocks
84+
- Avoid done callbacks in async tests - use async/await instead
85+
- Don't use `.only` or `.skip` in committed code
86+
- Keep test suites reasonably flat - avoid excessive `describe` nesting
87+
88+
## When Biome Can't Help
89+
90+
Biome's linter will catch most issues automatically. Focus your attention on:
91+
92+
1. **Business logic correctness** - Biome can't validate your algorithms
93+
2. **Meaningful naming** - Use descriptive names for functions, variables, and types
94+
3. **Architecture decisions** - Component structure, data flow, and API design
95+
4. **Edge cases** - Handle boundary conditions and error states
96+
5. **User experience** - Accessibility, performance, and usability considerations
97+
6. **Documentation** - Add comments for complex logic, but prefer self-documenting code
98+
99+
---
100+
101+
Most formatting and common issues are automatically fixed by Biome. Run `npx ultracite fix` before committing to ensure compliance.

sry/.vscode/settings.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"[javascript]": {
4+
"editor.defaultFormatter": "biomejs.biome"
5+
},
6+
"[typescript]": {
7+
"editor.defaultFormatter": "biomejs.biome"
8+
},
9+
"[javascriptreact]": {
10+
"editor.defaultFormatter": "biomejs.biome"
11+
},
12+
"[typescriptreact]": {
13+
"editor.defaultFormatter": "biomejs.biome"
14+
},
15+
"[json]": {
16+
"editor.defaultFormatter": "biomejs.biome"
17+
},
18+
"[jsonc]": {
19+
"editor.defaultFormatter": "biomejs.biome"
20+
},
21+
"[css]": {
22+
"editor.defaultFormatter": "biomejs.biome"
23+
},
24+
"[graphql]": {
25+
"editor.defaultFormatter": "biomejs.biome"
26+
},
27+
"typescript.tsdk": "node_modules/typescript/lib",
28+
"editor.formatOnSave": true,
29+
"editor.formatOnPaste": true,
30+
"emmet.showExpandedAbbreviation": "never",
31+
"editor.codeActionsOnSave": {
32+
"source.fixAll.biome": "explicit",
33+
"source.organizeImports.biome": "explicit"
34+
}
35+
}

sry/biome.jsonc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
22
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3-
"extends": ["ultracite/core"]
4-
}
3+
"extends": [
4+
"ultracite/core"
5+
]
6+
}

sry/src/commands/api.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { buildCommand } from "@stricli/core";
22
import type { SryContext } from "../context.js";
33
import { rawApiRequest } from "../lib/api-client.js";
44

5-
interface ApiFlags {
5+
type ApiFlags = {
66
readonly method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
77
readonly field: string[];
88
readonly header: string[];
99
readonly include: boolean;
1010
readonly silent: boolean;
11-
}
11+
};
1212

1313
function parseFields(fields: string[]): Record<string, unknown> {
1414
const result: Record<string, unknown> = {};
@@ -39,7 +39,7 @@ function parseFields(fields: string[]): Record<string, unknown> {
3939
}
4040
current = current[k] as Record<string, unknown>;
4141
}
42-
current[keys[keys.length - 1]] = value;
42+
current[keys.at(-1)] = value;
4343
}
4444

4545
return result;
@@ -174,9 +174,9 @@ export const apiCommand = buildCommand({
174174
// Output body
175175
if (response.body !== null && response.body !== undefined) {
176176
if (typeof response.body === "object") {
177-
process.stdout.write(JSON.stringify(response.body, null, 2) + "\n");
177+
process.stdout.write(`${JSON.stringify(response.body, null, 2)}\n`);
178178
} else {
179-
process.stdout.write(String(response.body) + "\n");
179+
process.stdout.write(`${String(response.body)}\n`);
180180
}
181181
}
182182

sry/src/commands/auth/login.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import {
77
setApiToken,
88
} from "../../lib/oauth.js";
99

10-
interface LoginFlags {
10+
type LoginFlags = {
1111
readonly token?: string;
1212
readonly timeout: number;
13-
}
13+
};
1414

1515
export const loginCommand = buildCommand({
1616
docs: {
@@ -64,7 +64,7 @@ export const loginCommand = buildCommand({
6464
try {
6565
process.stdout.write("Opening browser for Sentry authorization...\n");
6666
process.stdout.write(
67-
"Waiting for authorization (timeout: " + flags.timeout + "s)...\n\n"
67+
`Waiting for authorization (timeout: ${flags.timeout}s)...\n\n`
6868
);
6969

7070
// Perform the full OAuth flow

sry/src/commands/auth/status.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import {
99
readConfig,
1010
} from "../../lib/config.js";
1111

12-
interface StatusFlags {
12+
type StatusFlags = {
1313
readonly showToken: boolean;
14-
}
14+
};
1515

1616
export const statusCommand = buildCommand({
1717
docs: {

sry/src/commands/dsn/detect.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { buildCommand } from "@stricli/core";
22
import type { SryContext } from "../../context.js";
33
import { detectDSN } from "../../lib/dsn-finder.js";
44

5-
interface DetectFlags {
5+
type DetectFlags = {
66
readonly json: boolean;
77
readonly save: boolean;
8-
}
8+
};
99

1010
export const detectCommand = buildCommand({
1111
docs: {
@@ -54,7 +54,7 @@ export const detectCommand = buildCommand({
5454
}
5555

5656
if (flags.json) {
57-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
57+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
5858
return;
5959
}
6060

sry/src/commands/issue/get.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import type { SryContext } from "../../context.js";
33
import { getIssue, getLatestEvent } from "../../lib/api-client.js";
44
import type { SentryEvent, SentryIssue } from "../../types/index.js";
55

6-
interface GetFlags {
6+
type GetFlags = {
77
readonly json: boolean;
88
readonly event: boolean;
9-
}
9+
};
1010

1111
function formatIssueDetails(issue: SentryIssue): string {
1212
const lines: string[] = [];
@@ -86,11 +86,18 @@ function formatEventDetails(event: SentryEvent): string {
8686
if (event.user) {
8787
lines.push("");
8888
lines.push("User:");
89-
if (event.user.email) lines.push(` Email: ${event.user.email}`);
90-
if (event.user.username) lines.push(` Username: ${event.user.username}`);
91-
if (event.user.id) lines.push(` ID: ${event.user.id}`);
92-
if (event.user.ip_address)
89+
if (event.user.email) {
90+
lines.push(` Email: ${event.user.email}`);
91+
}
92+
if (event.user.username) {
93+
lines.push(` Username: ${event.user.username}`);
94+
}
95+
if (event.user.id) {
96+
lines.push(` ID: ${event.user.id}`);
97+
}
98+
if (event.user.ip_address) {
9399
lines.push(` IP: ${event.user.ip_address}`);
100+
}
94101
}
95102

96103
if (event.sdk) {
@@ -163,14 +170,14 @@ export const getCommand = buildCommand({
163170

164171
if (flags.json) {
165172
const output = event ? { issue, event } : { issue };
166-
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
173+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
167174
return;
168175
}
169176

170-
process.stdout.write(formatIssueDetails(issue) + "\n");
177+
process.stdout.write(`${formatIssueDetails(issue)}\n`);
171178

172179
if (event) {
173-
process.stdout.write(formatEventDetails(event) + "\n");
180+
process.stdout.write(`${formatEventDetails(event)}\n`);
174181
}
175182
} catch (error) {
176183
const message = error instanceof Error ? error.message : String(error);

sry/src/commands/issue/list.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { getDefaultOrganization, getDefaultProject } from "../../lib/config.js";
55
import { detectDSN } from "../../lib/dsn-finder.js";
66
import type { SentryIssue } from "../../types/index.js";
77

8-
interface ListFlags {
8+
type ListFlags = {
99
readonly org?: string;
1010
readonly project?: string;
1111
readonly query?: string;
1212
readonly limit: number;
1313
readonly sort: "date" | "new" | "priority" | "freq" | "user";
1414
readonly json: boolean;
15-
}
15+
};
1616

1717
function formatIssue(issue: SentryIssue): string {
1818
const status =
@@ -122,7 +122,7 @@ export const listCommand = buildCommand({
122122
});
123123

124124
if (flags.json) {
125-
process.stdout.write(JSON.stringify(issues, null, 2) + "\n");
125+
process.stdout.write(`${JSON.stringify(issues, null, 2)}\n`);
126126
return;
127127
}
128128

@@ -136,15 +136,15 @@ export const listCommand = buildCommand({
136136
`Issues in ${org}/${project} (showing ${issues.length}):\n\n`
137137
);
138138
process.stdout.write(" STATUS SHORT ID COUNT TITLE\n");
139-
process.stdout.write("─".repeat(80) + "\n");
139+
process.stdout.write(`${"─".repeat(80)}\n`);
140140

141141
// Issues
142142
for (const issue of issues) {
143-
process.stdout.write(formatIssue(issue) + "\n");
143+
process.stdout.write(`${formatIssue(issue)}\n`);
144144
}
145145

146146
process.stdout.write(
147-
"\n" + `Tip: Use 'sry issue get <SHORT_ID>' to view issue details.\n`
147+
"\nTip: Use 'sry issue get <SHORT_ID>' to view issue details.\n"
148148
);
149149
} catch (error) {
150150
const message = error instanceof Error ? error.message : String(error);

0 commit comments

Comments
 (0)