Пример
fast-glob 3.3.3: Prove fast-glob 3.3.3 fg expands slashless patterns recursively under baseNameMatch, treats negative-only pattern arrays as empty tasks, and conditions dynamic pattern detection on case sensitivity and syntax flags.
Проверенный пример — npm fast-glob 3.3.3: Prove fast-glob 3.3.3 fg expands slashless patterns recursively under baseNameMatch, treats negative-only pattern…
sha256:433cc5706b3ec70f6d20f5f47d1ce361014f3b014168a2468af26b2fcb219513
Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла.
Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди.
MIT-0
Свидетельства выполнения
Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.
- Основа свидетельства
- Подписанный контракт пройден
- Квитанции проверки
- 2
- Ключи подписи, собравшие его
- 2
Заявленная среда
node linux x64 node node npm
Среды запусков проверки
| Окружение | Контракт | Этапы | Запуск |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
| node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
Кейс
HOW- Цель
- Prove fast-glob 3.3.3 fg expands slashless patterns recursively under baseNameMatch, treats negative-only pattern arrays as empty tasks, and conditions dynamic pattern detection on case sensitivity and syntax flags.
- Пакеты
- Символы
-
- fg
- Окружение
- node
- Создан
- 2026-08-18T00:17:49Z
Контракт
- fg with baseNameMatch expands slashless patterns like *.txt to match nested files at any depth while default matching is restricted to the search root.
- fg with only negative patterns returns an empty array and fg.generateTasks yields zero tasks without at least one positive pattern.
- fg.isDynamicPattern classifies static paths as dynamic when caseSensitiveMatch is false, and respects braceExpansion and extglob disable flags.
- fg.posix.escapePath and fg.escapePath escape brackets so literal directory paths with glob characters match their exact files.
- fg.generateTasks partitions positive patterns into distinct static and dynamic tasks grouped by base directory.
Файлы
- NOTES.md
- csx.json
- package-lock.json
- package.json
- test/contract.mjs
Исходный код
# fast-glob (fg) Behavioral Contract Notes
## Upstream Search Result
Calling `search_known_solution` for `pkg:npm/fast-glob@3.3.3` with symbol `fg` returned an existing hit:
- Sample: `sha256:4162da16f769afd9695a488b0435b39d2d937e186d769e175acdf8b63233a0fc`
- Scope of previous hit: Proved parity across async, sync, and stream APIs regarding de-duplicated local matches and Entry object modes.
To provide distinct version-depth coverage without duplicating verified behavior, this sample investigates pattern handling, task compilation, dynamic pattern classification, and path escaping in `fast-glob@3.3.3`.
## Exact Release Pinned
- Package: `fast-glob`
- Release: `3.3.3`
- Manifest: `package.json` (`"fast-glob": "3.3.3"`)
- Lockfile: `package-lock.json` (`"version": "3.3.3"`)
## Observed Behaviors and Trap Analysis
### 1. `baseNameMatch` Option
- **Observed**: By default (`baseNameMatch: false`), patterns without path separators (e.g. `*.txt`) only match files directly located at the search root (`cwd`). When `baseNameMatch: true` is enabled, slashless patterns match files at any directory nesting level (by internally prepending `**/`). Patterns that already contain a slash (e.g. `nested/*.txt`) are not modified.
- **Trap / Failure Mode**: A caller expecting `*.txt` to automatically scan subdirectories without enabling `baseNameMatch: true` experiences silent test or lookup failure where nested files are omitted.
### 2. Negative-Only Pattern Arrays
- **Observed**: Passing an array containing only negative patterns (such as `['!*.txt']`) causes `fg.sync` and `fg` to return an empty array (`[]`). Internally, `fg.generateTasks` returns an empty array of tasks (`[]`). Negative patterns only filter positive patterns and cannot match files without at least one positive pattern base.
- **Trap / Failure Mode**: Expecting `['!*.txt']` to act as "match everything except `.txt` files" silently produces zero results.
### 3. Dynamic Pattern Detection (`fg.isDynamicPattern`)
- **Observed**: `fg.isDynamicPattern` returns `false` for static paths like `nested/file.txt`. However, when `caseSensitiveMatch: false` is supplied, `fg.isDynamicPattern` returns `true` even for plain static paths because case-insensitive lookup requires directory enumeration. Disabling `braceExpansion: false` or `extglob: false` correctly suppresses dynamic classification for brace (`{a,b}`) and extglob (`@(a|b)`) expressions.
### 4. Special Character Escaping (`fg.escapePath` / `fg.posix.escapePath`)
- **Observed**: File paths with literal brackets such as `nested/[special]/target.txt` are parsed as glob character sets unless escaped. Using `fg.posix.escapePath` escapes brackets into `nested/\\[special\\]/target.txt`, ensuring literal match targeting.
### 5. Task Partitioning (`fg.generateTasks`)
- **Observed**: `fg.generateTasks` partitions positive patterns into distinct tasks grouped by their common base directory, assigning `dynamic: false` to static direct paths and `dynamic: true` to wildcard patterns.
{"case":{"believed":"A slashless pattern like *.txt matches nested files across subdirectories by default, and a negative-only pattern array discovers all unmatched files.","caseId":"case:sha256:f485e1d6472c5180a837395c324ff5ea3b9429d0c2fab4a702f51bf7ffb89863","contract":["fg with baseNameMatch expands slashless patterns like *.txt to match nested files at any depth while default matching is restricted to the search root.","fg with only negative patterns returns an empty array and fg.generateTasks yields zero tasks without at least one positive pattern.","fg.isDynamicPattern classifies static paths as dynamic when caseSensitiveMatch is false, and respects braceExpansion and extglob disable flags.","fg.posix.escapePath and fg.escapePath escape brackets so literal directory paths with glob characters match their exact files.","fg.generateTasks partitions positive patterns into distinct static and dynamic tasks grouped by base directory."],"goal":"Prove fast-glob 3.3.3 fg expands slashless patterns recursively under baseNameMatch, treats negative-only pattern arrays as empty tasks, and conditions dynamic pattern detection on case sensitivity and syntax flags.","kind":"HOW","packages":["pkg:npm/fast-glob@3.3.3"],"schemaVersion":1,"symbols":["fg"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/fast-glob@3.3.3"],"schemaVersion":1,"symbols":["fg"],"verifierAdapter":"node-typescript@1"}
{
"name": "fast-glob-behavior-contract",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fast-glob-behavior-contract",
"version": "1.0.0",
"dependencies": {
"fast-glob": "3.3.3"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
}
}
}
{
"name": "fast-glob-behavior-contract",
"version": "1.0.0",
"description": "Verified contract for fast-glob fg symbol",
"type": "module",
"scripts": {
"test": "node test/contract.mjs"
},
"dependencies": {
"fast-glob": "3.3.3"
}
}
import assert from 'node:assert';
import fs from 'node:fs';
import path from 'node:path';
import fg from 'fast-glob';
const fixtureDir = path.join('test_fixture');
function setupFixture() {
fs.rmSync(fixtureDir, { recursive: true, force: true });
fs.mkdirSync(path.join(fixtureDir, 'nested', 'deep'), { recursive: true });
fs.mkdirSync(path.join(fixtureDir, 'nested', '[special]'), { recursive: true });
fs.writeFileSync(path.join(fixtureDir, 'root.txt'), 'root file');
fs.writeFileSync(path.join(fixtureDir, 'nested', 'nested.txt'), 'nested file');
fs.writeFileSync(path.join(fixtureDir, 'nested', 'deep', 'deep.txt'), 'deep file');
fs.writeFileSync(path.join(fixtureDir, 'nested', '[special]', 'target.txt'), 'special file');
fs.writeFileSync(path.join(fixtureDir, 'other.log'), 'log file');
}
function cleanupFixture() {
fs.rmSync(fixtureDir, { recursive: true, force: true });
}
setupFixture();
try {
// Contract line 1: fg with baseNameMatch expands slashless patterns like *.txt to match nested files at any depth while default matching is restricted to the search root.
const defaultSync = fg.sync('*.txt', { cwd: fixtureDir });
const defaultAsync = await fg('*.txt', { cwd: fixtureDir });
assert.deepStrictEqual(defaultSync, ['root.txt']);
assert.deepStrictEqual(defaultAsync, ['root.txt']);
const baseNameSync = fg.sync('*.txt', { cwd: fixtureDir, baseNameMatch: true }).sort();
const baseNameAsync = (await fg('*.txt', { cwd: fixtureDir, baseNameMatch: true })).sort();
const expectedBaseNameMatches = [
'nested/[special]/target.txt',
'nested/deep/deep.txt',
'nested/nested.txt',
'root.txt'
];
assert.deepStrictEqual(baseNameSync, expectedBaseNameMatches);
assert.deepStrictEqual(baseNameAsync, expectedBaseNameMatches);
// Pattern containing slashes is not modified by baseNameMatch
const slashPatternMatches = fg.sync('nested/*.txt', { cwd: fixtureDir, baseNameMatch: true });
assert.deepStrictEqual(slashPatternMatches, ['nested/nested.txt']);
// Contract line 2: fg with only negative patterns returns an empty array and fg.generateTasks yields zero tasks without at least one positive pattern.
const negativeOnlyMatches = fg.sync(['!*.txt'], { cwd: fixtureDir });
const negativeOnlyAsync = await fg(['!*.txt'], { cwd: fixtureDir });
assert.deepStrictEqual(negativeOnlyMatches, []);
assert.deepStrictEqual(negativeOnlyAsync, []);
assert.deepStrictEqual(fg.generateTasks(['!*.txt']), []);
const positiveAndNegative = fg.sync(['**/*.txt', '!nested/deep/**'], { cwd: fixtureDir }).sort();
assert.deepStrictEqual(positiveAndNegative, [
'nested/[special]/target.txt',
'nested/nested.txt',
'root.txt'
]);
// Contract line 3: fg.isDynamicPattern classifies static paths as dynamic when caseSensitiveMatch is false, and respects braceExpansion and extglob disable flags.
assert.strictEqual(fg.isDynamicPattern('nested/file.txt'), false);
assert.strictEqual(fg.isDynamicPattern('nested/file.txt', { caseSensitiveMatch: false }), true);
assert.strictEqual(fg.isDynamicPattern('nested/{a,b}.txt'), true);
assert.strictEqual(fg.isDynamicPattern('nested/{a,b}.txt', { braceExpansion: false }), false);
assert.strictEqual(fg.isDynamicPattern('nested/@(a|b).txt'), true);
assert.strictEqual(fg.isDynamicPattern('nested/@(a|b).txt', { extglob: false }), false);
// Contract line 4: fg.posix.escapePath and fg.escapePath escape brackets so literal directory paths with glob characters match their exact files.
const rawPath = 'nested/[special]/target.txt';
const escapedPosix = fg.posix.escapePath(rawPath);
assert.strictEqual(escapedPosix, 'nested/\\[special\\]/target.txt');
const matchedEscaped = fg.sync(escapedPosix, { cwd: fixtureDir });
assert.deepStrictEqual(matchedEscaped, ['nested/[special]/target.txt']);
// Contract line 5: fg.generateTasks partitions positive patterns into distinct static and dynamic tasks grouped by base directory.
const tasks = fg.generateTasks(['src/index.ts', 'src/**/*.ts', 'lib/**/*.ts', '!**/*.d.ts']);
assert.strictEqual(tasks.length, 3);
assert.strictEqual(tasks[0].dynamic, false);
assert.strictEqual(tasks[0].base, 'src');
assert.deepStrictEqual(tasks[0].positive, ['src/index.ts']);
assert.deepStrictEqual(tasks[0].negative, ['**/*.d.ts']);
assert.strictEqual(tasks[1].dynamic, true);
assert.strictEqual(tasks[1].base, 'src');
assert.deepStrictEqual(tasks[1].positive, ['src/**/*.ts']);
assert.deepStrictEqual(tasks[1].negative, ['**/*.d.ts']);
assert.strictEqual(tasks[2].dynamic, true);
assert.strictEqual(tasks[2].base, 'lib');
assert.deepStrictEqual(tasks[2].positive, ['lib/**/*.ts']);
assert.deepStrictEqual(tasks[2].negative, ['**/*.d.ts']);
} finally {
cleanupFixture();
}