Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .chronus/changes/remove-globby-2026-3-10-15-46-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: internal
packages:
- "@typespec/compiler"
- "@typespec/http-client-js"
- "@typespec/http-server-csharp"
- "@typespec/http-server-js"
- "@typespec/spector"
---

Replace globby with node built-ins
1 change: 0 additions & 1 deletion cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ words:
- genproto
- getpgid
- giacamo
- globby
- graalvm
- Gson
- Hdvcmxk
Expand Down
1 change: 0 additions & 1 deletion packages/bundle-uploader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"@azure/storage-blob": "catalog:",
"@pnpm/workspace.find-packages": "catalog:",
"@typespec/bundler": "workspace:^",
"globby": "catalog:",
"picocolors": "catalog:",
"semver": "catalog:"
},
Expand Down
8 changes: 5 additions & 3 deletions packages/bundle-uploader/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { AzureCliCredential } from "@azure/identity";
import { findWorkspacePackagesNoCheck } from "@pnpm/workspace.find-packages";
import { createTypeSpecBundle } from "@typespec/bundler";
import { readFile } from "fs/promises";
import { globby } from "globby";
import { glob, readFile } from "fs/promises";
import { relative, resolve } from "path";
import { join as joinUnix } from "path/posix";
import pc from "picocolors";
Expand Down Expand Up @@ -99,7 +98,10 @@ async function uploadPlaygroundAssets(
// Upload static assets (e.g. .whl files)
if (config.assets) {
for (const asset of config.assets) {
const matchedFiles = await globby(asset.path, { cwd: packagePath, absolute: true });
const matchedFiles: string[] = [];
for await (const file of glob(asset.path, { cwd: packagePath })) {
matchedFiles.push(resolve(packagePath, file));
}
if (matchedFiles.length === 0) {
logInfo(pc.yellow(`⚠ No files matched asset pattern: ${asset.path}`));
continue;
Expand Down
1 change: 0 additions & 1 deletion packages/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
"ajv": "catalog:",
"change-case": "catalog:",
"env-paths": "catalog:",
"globby": "catalog:",
"is-unicode-supported": "catalog:",
"mustache": "catalog:",
"picocolors": "catalog:",
Expand Down
55 changes: 47 additions & 8 deletions packages/compiler/src/core/formatter-fs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFile, writeFile } from "fs/promises";
import { globby } from "globby";
import { glob, readFile, stat, writeFile } from "fs/promises";
import { join } from "path";
import { resolveConfig } from "prettier";
import { PrettierParserError } from "../formatter/parser.js";
import { checkFormat, format, getFormatterFromFilename } from "./formatter.js";
Expand Down Expand Up @@ -177,10 +177,49 @@ export async function checkFileFormat(filename: string): Promise<CheckFormatResu
}

async function findFiles(include: string[], ignore: string[] = []): Promise<string[]> {
const patterns = [
...include.map(normalizePath),
"!**/node_modules",
...ignore.map((x) => `!${normalizePath(x)}`),
];
return globby(patterns);
const expandedInclude = await expandDirectoryPatterns(include);
const results: string[] = [];
for await (const entry of glob(expandedInclude, {
withFileTypes: true,
exclude: ["**/node_modules", ...ignore],
})) {
if (entry.isFile()) {
results.push(join(entry.parentPath, entry.name));
}
}
return results;
}

/**
* Expand bare directory paths to glob patterns.
* A directory "src" becomes both "src" and "src/**\/*" so it matches the
* directory entry itself (for exclude short-circuiting) and its contents.
* Glob patterns ending in "/**" also get the bare directory form added.
*/
async function expandDirectoryPatterns(patterns: string[]): Promise<string[]> {
const expanded: string[] = [];
for (const pattern of patterns) {
if (/[*?{[]/.test(pattern)) {
expanded.push(normalizePath(pattern));
// Also match the directory itself so exclude can short-circuit traversal
const normalized = normalizePath(pattern);
if (normalized.endsWith("/**/*")) {
expanded.push(normalized.slice(0, -4));
} else if (normalized.endsWith("/**")) {
expanded.push(normalized.slice(0, -3));
}
} else {
try {
if ((await stat(pattern)).isDirectory()) {
expanded.push(normalizePath(pattern));
expanded.push(normalizePath(`${pattern}/**/*`));
continue;
}
} catch {
// not a valid path — treat as glob pattern
}
expanded.push(normalizePath(pattern));
}
}
return expanded;
}
13 changes: 8 additions & 5 deletions packages/compiler/src/testing/test-host.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from "assert";
import { globby } from "globby";
import { glob } from "fs/promises";
import { logDiagnostics, logVerboseTestOutput } from "../core/diagnostics.js";
import { createLogger } from "../core/logger/logger.js";
import { CompilerOptions } from "../core/options.js";
Expand Down Expand Up @@ -82,9 +82,12 @@ async function createTestHostInternal(): Promise<TestHost> {
}

export async function findFilesFromPattern(directory: string, pattern: string): Promise<string[]> {
return globby(pattern, {
const results: string[] = [];
for await (const file of glob(pattern, {
cwd: directory,
onlyFiles: true,
ignore: ["**/*.{test,spec}.{ts,tsx,js,jsx}"],
});
exclude: ["**/*.{test,spec}.{ts,tsx,js,jsx}"],
})) {
results.push(file);
}
return results;
}
119 changes: 119 additions & 0 deletions packages/compiler/test/core/formatter-fs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { mkdir, rm, writeFile } from "fs/promises";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { beforeAll, describe, expect, it } from "vitest";
import { checkFilesFormat } from "../../src/core/formatter-fs.js";
import { resolvePath } from "../../src/core/path-utils.js";

const __dirname = dirname(fileURLToPath(import.meta.url));
const fixtureRoot = resolvePath(__dirname, "../../temp/test/formatter-fs");

const wellFormattedTsp = `model Foo {
name: string;
}
`;
const unformattedTsp = `model
Foo {
name: string;
}
`;

function fixturePath(...segments: string[]) {
return join(fixtureRoot, ...segments);
}

async function createFixtureFile(relativePath: string, content = "") {
const fullPath = fixturePath(relativePath);
await mkdir(dirname(fullPath), { recursive: true });
await writeFile(fullPath, content);
}

function allFiles(result: Awaited<ReturnType<typeof checkFilesFormat>>): string[] {
return [...result.formatted, ...result.needsFormat, ...result.ignored].sort();
}

beforeAll(async () => {
await rm(fixtureRoot, { recursive: true, force: true });
await mkdir(fixtureRoot, { recursive: true });

await createFixtureFile("project/main.tsp", wellFormattedTsp);
await createFixtureFile("project/lib.tsp", unformattedTsp);
await createFixtureFile("project/sub/nested.tsp", wellFormattedTsp);
await createFixtureFile("project/readme.md", "# Readme");
await createFixtureFile("project/node_modules/dep/index.tsp", unformattedTsp);
await createFixtureFile("project/excluded/skip.tsp", unformattedTsp);
});

describe("formatter-fs: findFiles", () => {
it("finds .tsp files with explicit glob pattern", async () => {
const result = await checkFilesFormat([fixturePath("project/**/*.tsp")], {});
expect(allFiles(result)).toEqual([
fixturePath("project/excluded/skip.tsp"),
fixturePath("project/lib.tsp"),
fixturePath("project/main.tsp"),
fixturePath("project/sub/nested.tsp"),
]);
});

it("expands bare directory path to find files recursively", async () => {
const result = await checkFilesFormat([fixturePath("project")], {});
expect(allFiles(result)).toEqual([
fixturePath("project/excluded/skip.tsp"),
fixturePath("project/lib.tsp"),
fixturePath("project/main.tsp"),
fixturePath("project/readme.md"),
fixturePath("project/sub/nested.tsp"),
]);
});

it("excludes node_modules automatically", async () => {
const result = await checkFilesFormat([fixturePath("project/**/*.tsp")], {});
expect(allFiles(result)).not.toContainEqual(expect.stringContaining("node_modules"));
});

it("excludes node_modules when using directory expansion", async () => {
const result = await checkFilesFormat([fixturePath("project")], {});
expect(allFiles(result)).not.toContainEqual(expect.stringContaining("node_modules"));
});

it("respects user-provided exclude patterns", async () => {
const result = await checkFilesFormat([fixturePath("project/**/*.tsp")], {
exclude: [fixturePath("project/excluded/**")],
});
expect(allFiles(result)).toEqual([
fixturePath("project/lib.tsp"),
fixturePath("project/main.tsp"),
fixturePath("project/sub/nested.tsp"),
]);
});

it("handles multiple include patterns", async () => {
const result = await checkFilesFormat(
[fixturePath("project/main.tsp"), fixturePath("project/sub/**/*.tsp")],
{},
);
expect(allFiles(result)).toEqual([
fixturePath("project/main.tsp"),
fixturePath("project/sub/nested.tsp"),
]);
});

it("classifies non-tsp files as ignored", async () => {
const result = await checkFilesFormat([fixturePath("project/readme.md")], {});
expect(result.ignored).toEqual([fixturePath("project/readme.md")]);
});

it("returns empty results when nothing matches", async () => {
const result = await checkFilesFormat([fixturePath("project/**/*.py")], {});
expect(allFiles(result)).toEqual([]);
});

it("correctly identifies formatted vs needs-format files", async () => {
const result = await checkFilesFormat(
[fixturePath("project/main.tsp"), fixturePath("project/lib.tsp")],
{},
);
expect(result.formatted).toEqual([fixturePath("project/main.tsp")]);
expect(result.needsFormat).toEqual([fixturePath("project/lib.tsp")]);
});
});
8 changes: 5 additions & 3 deletions packages/http-client-js/eng/scripts/emit-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
/* eslint-disable no-console */
import { select } from "@inquirer/prompts";
import { execa } from "execa";
import { access, copyFile, mkdir, readFile, rm, stat, writeFile } from "fs/promises";
import { globby } from "globby";
import { access, copyFile, glob, mkdir, readFile, rm, stat, writeFile } from "fs/promises";
import ora from "ora";
import pLimit from "p-limit";
import { basename, dirname, join, resolve } from "path";
Expand Down Expand Up @@ -93,7 +92,10 @@ async function processPaths(paths, ignoreList, mainOnly) {
results.push({ fullPath, relativePath });
} else if (stats.isDirectory()) {
const patterns = mainOnly ? ["**/main.tsp"] : ["**/client.tsp", "**/main.tsp"];
const discoveredPaths = await globby(patterns, { cwd: fullPath });
const discoveredPaths = [];
for await (const p of glob(patterns, { cwd: fullPath })) {
discoveredPaths.push(p);
}
const validFiles = discoveredPaths
.map((p) => ({
fullPath: join(fullPath, p),
Expand Down
1 change: 0 additions & 1 deletion packages/http-client-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"concurrently": "catalog:",
"cross-env": "catalog:",
"execa": "catalog:",
"globby": "catalog:",
"@inquirer/prompts": "catalog:",
"ora": "catalog:",
"p-limit": "catalog:",
Expand Down
13 changes: 9 additions & 4 deletions packages/http-server-csharp/eng/scripts/emit-scenarios.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* eslint-disable no-console */
import { select } from "@inquirer/prompts";
import { run } from "@typespec/internal-build-utils";
import { access, copyFile, mkdir, readFile, rm, writeFile } from "fs/promises";
import { globby } from "globby";
import { access, copyFile, glob, mkdir, readFile, rm, writeFile } from "fs/promises";
import ora from "ora";
import pLimit from "p-limit";
import { basename, dirname, join, resolve } from "pathe";
Expand Down Expand Up @@ -80,7 +79,10 @@ async function copySelectiveFiles(
sourceDir: string,
targetDir: string,
): Promise<void> {
const files = await globby(extension, { cwd: sourceDir });
const files: string[] = [];
for await (const file of glob(extension, { cwd: sourceDir })) {
files.push(file);
}
for (const file of files) {
const src = join(sourceDir, file);
const dest = join(targetDir, file);
Expand Down Expand Up @@ -291,7 +293,10 @@ async function main(): Promise<void> {
const ignoreList = await getIgnoreList();

const patterns = ["**/main.tsp"];
const specsList = await globby(patterns, { cwd: specDir });
const specsList: string[] = [];
for await (const spec of glob(patterns, { cwd: specDir })) {
specsList.push(spec);
}

const paths = specsList.filter((item) => !ignoreList.includes(item));

Expand Down
1 change: 0 additions & 1 deletion packages/http-server-csharp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
"@typespec/versioning": "workspace:^",
"@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:",
"globby": "catalog:",
"@inquirer/prompts": "catalog:",
"ora": "catalog:",
"p-limit": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { readFileSync } from "fs";
import { globby } from "globby";
import { glob } from "fs/promises";
import { dirname, join, resolve } from "pathe";
import { describe, expect, it } from "vitest";
import { Server, getIgnoreList } from "./helpers.js"; // Import the custom Server class
Expand All @@ -11,13 +11,11 @@ const generatedRoot = join(testRoot, "generated"); // Root folder for generated
const ignoreList = await getIgnoreList(join(testRoot, ".testignore"));

// Get all unique service directories
const allGeneratedServices = Array.from(
new Set(
(await globby("**/ServiceProject.csproj", { cwd: generatedRoot })).map((service) =>
dirname(service),
),
),
);
const allGeneratedServicesList: string[] = [];
for await (const service of glob("**/ServiceProject.csproj", { cwd: generatedRoot })) {
allGeneratedServicesList.push(dirname(service));
}
const allGeneratedServices = Array.from(new Set(allGeneratedServicesList));

// Filter out ignored services
const services = allGeneratedServices.filter((item) => !ignoreList.includes(`${item}/main.tsp`));
Expand Down
8 changes: 5 additions & 3 deletions packages/http-server-js/eng/scripts/emit-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
/* eslint-disable no-console */
import { select } from "@inquirer/prompts";
import { run } from "@typespec/internal-build-utils";
import { access, copyFile, mkdir, readFile, rm, stat, writeFile } from "fs/promises";
import { globby } from "globby";
import { access, copyFile, glob, mkdir, readFile, rm, stat, writeFile } from "fs/promises";
import ora from "ora";
import pLimit from "p-limit";
import { basename, dirname, join, resolve } from "path";
Expand Down Expand Up @@ -88,7 +87,10 @@ async function processPaths(paths, ignoreList) {
results.push({ fullPath, relativePath });
} else if (stats.isDirectory()) {
const patterns = ["**/main.tsp"];
const discoveredPaths = await globby(patterns, { cwd: fullPath });
const discoveredPaths = [];
for await (const p of glob(patterns, { cwd: fullPath })) {
discoveredPaths.push(p);
}
const validFiles = discoveredPaths
.map((p) => ({
fullPath: join(fullPath, p),
Expand Down
1 change: 0 additions & 1 deletion packages/http-server-js/generated-defs/package.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const hsjsDependencies: Record<string, string> = {
"@vitest/ui": "^4.1.3",
"decimal.js": "^10.6.0",
"express": "^5.2.1",
"globby": "^16.2.0",
"@inquirer/prompts": "^8.4.1",
"morgan": "^1.10.1",
"ora": "^9.3.0",
Expand Down
Loading
Loading