Files
Danny AvilaandGitHub d259dfdf0d chore: Update TypeScript/ESLint configs, Check Circular Dependencies, Sort imports, Bump Node Engine, Test Sharding (#226)
* fix: Update import path for isGoogleLike utility in stream.ts due to circular dependency

* ci: add circular dependency check to CI workflow

* feat: add import sorting script and update ESLint config to include scripts/**/*.ts

- Introduced a new script to sort imports in TypeScript files according to project conventions.
- Updated ESLint configuration to include TypeScript files in the scripts directory for linting.
- Added new commands in package.json for sorting imports and checking import order.

* chore: sort imports

* chore: update TypeScript configuration for scripts and ESLint

- Added a new tsconfig.json for the scripts directory, extending the main tsconfig.json.
- Updated ESLint configuration to include the new scripts tsconfig for linting.
- Modified main tsconfig.json to specify types for node and jest, and adjusted included files.

* chore: update Node.js version and ESLint configuration

- Added .nvmrc file to specify Node.js version 24.
- Updated ESLint configuration to disable specific rules for test files.
- Modified package.json to require Node.js version 24 and updated @types/node dependency.
- Adjusted CI and publish workflows to use Node.js version 24.
- Enhanced tsconfig-paths-bootstrap.mjs for better baseUrl handling.
- Refactored logging.ts to improve type safety in write methods.

* chore: remove unused inline attachment mime types from attachments.ts

- Deleted the SUPPORTED_ATTACHMENT_MIMES constant, which included image and PDF mime types.
- This change simplifies the attachment handling by relying solely on SUPPORTED_IMAGE_MIMES.

* chore: update ESLint configuration to enforce no-nested-ternary rule as error

- Changed the severity of the "no-nested-ternary" rule from warning to error in the ESLint configuration, promoting stricter code quality standards.

* refactor: enhance workspace policy hook and improve Cloudflare execution engine

- Updated the `createWorkspacePolicyHook` to introduce a `resolvePolicy` function for clearer policy resolution based on tool names.
- Modified the `CloudflareSandboxExecutionEngine` to use a function for checking if the state is closed, preventing potential issues with concurrent mutations.
- Simplified promise handling in `LocalCodingTools` by removing unnecessary checks and added a new utility function for formatting fallback grep display.

* chore: update import sorting script to use Node instead of Bun

- Changed the shebang in `sort-imports.ts` from Bun to Node for better compatibility.
- Updated `package.json` scripts to reflect the change in runtime for sorting imports.
- Removed Bun types from `tsconfig.json` to streamline type definitions.
- Adjusted the import sorting logic to improve handling of file paths and sorting criteria.

* ci: refactor CI workflow to introduce unit and integration test jobs

- Renamed the existing test job to unit-tests and added a matrix strategy for sharding.
- Created a new integration-tests job for change-gated integration testing.
- Updated test execution commands to accommodate sharding in unit tests.

* ci: enhance CI workflow with summarization tests and environment variables

- Added a new job for summarization tests to the CI workflow.
- Updated the unit test command to exclude an additional test file.
- Introduced environment variables for various API keys to support the new summarization tests.
2026-06-06 11:53:22 -04:00

230 lines
6.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Sorts imports in all .ts files under src/ per project convention
* (AGENTS.md § Import Order):
*
* 1. Package value imports — shortest line to longest
* 2. import type from packages — longest line to shortest
* 3. import type from local — longest line to shortest
* 4. Local value imports — longest line to shortest
*
* Runtime-agnostic: uses only Node APIs, so it runs under either
* `node scripts/sort-imports.ts` (Node 24+ strips types natively) or
* `bun run scripts/sort-imports.ts`.
*
* Run: npm run sort-imports
* Check only: npm run sort-imports:check
*/
import { readFile, writeFile, readdir } from 'node:fs/promises';
import { join, relative, resolve, sep, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const SRC = join(ROOT, 'src');
const args = process.argv.slice(2);
const CHECK = args.includes('--check');
const FILE_ARGS = args.filter((arg) => !arg.startsWith('--'));
/** Directories under src/ excluded from linting (mirrors eslint.config.mjs). */
const EXCLUDED_DIRS = ['scripts/', 'proto/'];
function isLocal(spec: string): boolean {
return (
spec.startsWith('@/') ||
spec.startsWith('./') ||
spec.startsWith('../') ||
spec.startsWith('#/')
);
}
function isExcluded(rel: string): boolean {
return EXCLUDED_DIRS.some((dir) => rel.startsWith(dir));
}
interface Stmt {
raw: string;
spec: string;
isType: boolean;
isLocal: boolean;
len: number;
}
/** Per-file opt-out for modules where import order is load-bearing. */
const IGNORE_MARKER = /^\s*\/\/\s*sort-imports-ignore\b/;
function extractSpec(raw: string): string | null {
return raw.match(/from\s+['"]([^'"]+)['"]/)?.[1] ?? null;
}
/** Applies the AGENTS.md grouping/length ordering to a run of pure imports. */
function sortSegment(stmts: Stmt[]): string[] {
const g1 = stmts
.filter((s) => !s.isType && !s.isLocal)
.sort((a, b) => a.len - b.len);
const g2 = stmts
.filter((s) => s.isType && !s.isLocal)
.sort((a, b) => b.len - a.len);
const g3 = stmts
.filter((s) => s.isType && s.isLocal)
.sort((a, b) => b.len - a.len);
const g4 = stmts
.filter((s) => !s.isType && s.isLocal)
.sort((a, b) => b.len - a.len);
return [...g1, ...g2, ...g3, ...g4].map((s) => s.raw);
}
function sortFileImports(content: string): string | null {
const lines = content.split('\n');
// Honor an explicit opt-out anywhere in the file.
if (lines.some((line) => IGNORE_MARKER.test(line))) {
return null;
}
let i = 0;
while (i < lines.length) {
const t = lines[i].trimStart();
if (
t === '' ||
t.startsWith('//') ||
t.startsWith('/*') ||
t.startsWith('*') ||
t.startsWith('*/') ||
t.startsWith('\'use ') ||
t.startsWith('"use ')
) {
i++;
} else {
break;
}
}
const importStart = i;
// Side-effect imports (no `from` clause) are treated as immovable
// barriers: sorting is confined to each contiguous run of pure imports
// between them, so module-evaluation order around anything with side
// effects (polyfills, registration, etc.) is never changed.
const emitted: string[] = [];
const originalRaws: string[] = [];
let segment: Stmt[] = [];
let importEnd = i;
const flushSegment = (): void => {
if (segment.length === 0) return;
emitted.push(...sortSegment(segment));
segment = [];
};
while (i < lines.length) {
const t = lines[i].trimStart();
if (!t.startsWith('import ') && !t.startsWith('import{')) break;
let raw = lines[i];
let j = i;
while (!raw.includes(';') && j + 1 < lines.length) {
j++;
raw += '\n' + lines[j];
}
i = j + 1;
importEnd = i;
originalRaws.push(raw);
const spec = extractSpec(raw);
if (spec == null || spec === '') {
flushSegment();
emitted.push(raw);
while (i < lines.length && lines[i].trim() === '') i++;
continue;
}
segment.push({
raw,
spec,
isType: /^import\s+type[\s{]/.test(raw.trimStart()),
isLocal: isLocal(spec),
len: raw
.split('\n')
.map((l) => l.trim())
.join(' ').length,
});
while (i < lines.length && lines[i].trim() === '') i++;
}
flushSegment();
if (originalRaws.length < 2) return null;
if (originalRaws.join('\n') === emitted.join('\n')) return null;
return [
...lines.slice(0, importStart),
...emitted,
...lines.slice(importEnd),
].join('\n');
}
/** Recursively yields absolute paths of every `.ts` file under `dir`. */
async function* walkTypeScriptFiles(dir: string): AsyncGenerator<string> {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
yield* walkTypeScriptFiles(full);
} else if (entry.isFile() && entry.name.endsWith('.ts')) {
yield full;
}
}
}
/**
* Resolves the set of files to process. When explicit paths are passed
* (e.g. by lint-staged) only those `.ts` files under `src/` are sorted;
* otherwise every non-excluded file under `src/` is scanned.
*/
async function collectFiles(): Promise<string[]> {
if (FILE_ARGS.length > 0) {
return FILE_ARGS.map((file) => resolve(file)).filter(
(abs) =>
abs.endsWith('.ts') &&
abs.startsWith(`${SRC}${sep}`) &&
!isExcluded(relative(SRC, abs))
);
}
const files: string[] = [];
for await (const abs of walkTypeScriptFiles(SRC)) {
if (isExcluded(relative(SRC, abs))) continue;
files.push(abs);
}
return files;
}
let changed = 0;
let total = 0;
for (const filePath of await collectFiles()) {
const rel = relative(ROOT, filePath);
const content = await readFile(filePath, 'utf8');
const result = sortFileImports(content);
total++;
if (result === null) continue;
changed++;
if (CHECK) {
console.log(` ✗ ${rel}`);
} else {
await writeFile(filePath, result);
console.log(` ✓ ${rel}`);
}
}
if (CHECK && changed) {
console.log(
`\n${changed}/${total} files need sorting. Run: npm run sort-imports`
);
process.exit(1);
} else if (changed) {
console.log(`\nSorted ${changed}/${total} files.`);
} else {
console.log(`All ${total} files already sorted.`);
}