@effected/workspaces 0.15.1 → 0.17.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.
- package/README.md +20 -1
- package/WorkspaceCatalogs.js +8 -1
- package/WorkspacesSync.js +37 -10
- package/index.d.ts +73 -1
- package/node-sync.d.ts +48 -1
- package/node-sync.js +6 -1
- package/package.json +3 -3
- package/tsdoc-metadata.json +1 -1
package/README.md
CHANGED
|
@@ -218,6 +218,25 @@ const packages = root === null ? [] : getWorkspacePackagesSync(root, options);
|
|
|
218
218
|
// packages: the discovered workspace packages, empty when there is no root
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
+
Those four operations are the whole requirement. A fifth, `readDirectoryWithTypes`, is optional: supply it and package enumeration reads a directory's entries and their types in one call instead of a `readDirectory` plus an `isDirectory` per entry, which on a large workspace is a syscall per file. `nodeFileSystem` already implements it over `readdirSync(p, { withFileTypes: true })`, so the `node-sync` bindings get the fast path for free. Omit it and enumeration falls back to the four required operations with identical results — a cost optimization, never a behavior switch.
|
|
222
|
+
|
|
223
|
+
Each entry reports `name`, `isDirectory` and `isSymbolicLink` as a `SyncDirectoryEntry`, which Node's `Dirent` satisfies once its predicate methods are called. The link flag is load-bearing: a `Dirent` describes the entry itself, so a symlink pointing at a directory reports `isDirectory: false`, while the `stat`-based path resolves the link and calls the same entry a directory. Enumeration re-resolves links through `isDirectory` rather than trusting the flag, which is what keeps a workspace with symlinked packages discovered identically on both paths.
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { readdirSync } from "node:fs";
|
|
227
|
+
import type { SyncDirectoryEntry } from "@effected/workspaces";
|
|
228
|
+
|
|
229
|
+
const readDirectoryWithTypes = (p: string): ReadonlyArray<SyncDirectoryEntry> =>
|
|
230
|
+
readdirSync(p, { withFileTypes: true }).map((entry) => ({
|
|
231
|
+
name: entry.name,
|
|
232
|
+
isDirectory: entry.isDirectory(),
|
|
233
|
+
isSymbolicLink: entry.isSymbolicLink(),
|
|
234
|
+
}));
|
|
235
|
+
// pass alongside the four required operations: { ...options.fileSystem, readDirectoryWithTypes }
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
For a test fake, `@effected/memfs`' `MemoryFileSystem.syncFileSystem(volume)` satisfies `SyncFileSystem` structurally — neither package imports the other — so a config-time discovery path can be exercised against a virtual workspace with nothing on disk.
|
|
239
|
+
|
|
221
240
|
Windows correctness is therefore the operations you pass, and nothing else. Both entry points drive one traversal state machine (the same dequeue order, depth rule, visit budget and `node_modules` prune), so the sync and Effect surfaces can never disagree about what a pattern means. The one deliberate difference is at a bound: the Effect enumerator fails typed, the sync one truncates. Prefer the Effect API everywhere you can run one.
|
|
222
241
|
|
|
223
242
|
## Error handling
|
|
@@ -286,7 +305,7 @@ A name miss in the derived `getPackage` fails with the service's own typed `Pack
|
|
|
286
305
|
- `ReleaseTag` / `TrackingTag` — release-tag formatting (`ReleaseTag.single` / `.scoped`, strict SemVer by default with no `v` prefix) and the floating major/minor alias derivation GitHub Actions-style consumers expect (`v1`, `v1.2`), plus `classifyTag` to tell a release tag from a tracking alias.
|
|
287
306
|
- `VersioningStrategy` — classify a workspace as `single`, `fixed-group` or `independent` from package names and fixed groups, or detect it live against `PublishabilityDetector`, and produce the release tags for a batch with `tagsFor`.
|
|
288
307
|
- `findWorkspaceRootSync` / `getWorkspacePackagesSync` — the synchronous escape hatch for config-time callers that cannot await, over file and path operations you supply.
|
|
289
|
-
- `@effected/workspaces/node-sync` — a second entry point holding the Node bindings for those operations (`nodeFileSystem`, `nodePath` and the `nodeSyncOps` bag), kept off the main entry so `node:*` never reaches a consumer that supplies its own.
|
|
308
|
+
- `@effected/workspaces/node-sync` — a second entry point holding the Node bindings for those operations (`nodeFileSystem`, `nodePath` and the `nodeSyncOps` bag), kept off the main entry so `node:*` never reaches a consumer that supplies its own. `nodeFileSystem` implements the optional `readDirectoryWithTypes` fast path, so the bindings enumerate a workspace in one `readdirSync` per directory.
|
|
290
309
|
|
|
291
310
|
## License
|
|
292
311
|
|
package/WorkspaceCatalogs.js
CHANGED
|
@@ -444,7 +444,8 @@ var WorkspaceCatalogs = class WorkspaceCatalogs extends Context.Service()("@effe
|
|
|
444
444
|
}),
|
|
445
445
|
importerVersions: Effect.fn("WorkspaceCatalogs.importerVersions")(function* () {
|
|
446
446
|
return (yield* memo).importerVersions;
|
|
447
|
-
})
|
|
447
|
+
}),
|
|
448
|
+
refresh: () => invalidate
|
|
448
449
|
};
|
|
449
450
|
});
|
|
450
451
|
/**
|
|
@@ -509,6 +510,11 @@ var WorkspaceCatalogs = class WorkspaceCatalogs extends Context.Service()("@effe
|
|
|
509
510
|
* the importer index from the lockfile's importer blocks — neither is in a
|
|
510
511
|
* `CatalogSet`) and always die unless stubbed.
|
|
511
512
|
*
|
|
513
|
+
* `refresh` defaults to `Effect.void` honestly: the double holds no memo,
|
|
514
|
+
* so "drop the memoized assembly" is genuinely a no-op — the stubs answer
|
|
515
|
+
* fresh on every call already. This mirrors `WorkspaceDiscovery.makeTest`'s
|
|
516
|
+
* `refresh`.
|
|
517
|
+
*
|
|
512
518
|
* @example
|
|
513
519
|
* ```ts
|
|
514
520
|
* import { CatalogSet, WorkspaceCatalogs } from "@effected/workspaces";
|
|
@@ -528,6 +534,7 @@ var WorkspaceCatalogs = class WorkspaceCatalogs extends Context.Service()("@effe
|
|
|
528
534
|
peerDependencyRules: () => unstubbed("peerDependencyRules"),
|
|
529
535
|
releaseAgeGate: () => unstubbed("releaseAgeGate"),
|
|
530
536
|
importerVersions: () => unstubbed("importerVersions"),
|
|
537
|
+
refresh: () => Effect.void,
|
|
531
538
|
...overrides
|
|
532
539
|
};
|
|
533
540
|
};
|
package/WorkspacesSync.js
CHANGED
|
@@ -35,6 +35,37 @@ const isDirectory = (fileSystem, dir) => {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* The entries of `absoluteDir`, each carrying whether it is a directory, or
|
|
40
|
+
* `undefined` when the directory could not be read at all (the degraded-skip
|
|
41
|
+
* case). Uses {@link SyncFileSystem.readDirectoryWithTypes} when the consumer
|
|
42
|
+
* supplied it and the four-operation fallback otherwise — the two agree by
|
|
43
|
+
* construction, including on symbolic links, which are always re-resolved
|
|
44
|
+
* through `isDirectory` because a `Dirent` describes the link and not its
|
|
45
|
+
* target.
|
|
46
|
+
*/
|
|
47
|
+
const readResolvedEntries = (options, absoluteDir) => {
|
|
48
|
+
const { fileSystem, path } = options;
|
|
49
|
+
const withTypes = fileSystem.readDirectoryWithTypes;
|
|
50
|
+
if (withTypes !== void 0) try {
|
|
51
|
+
return withTypes(absoluteDir).map((entry) => ({
|
|
52
|
+
name: entry.name,
|
|
53
|
+
directory: entry.isSymbolicLink ? isDirectory(fileSystem, path.join(absoluteDir, entry.name)) : entry.isDirectory
|
|
54
|
+
}));
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let names;
|
|
59
|
+
try {
|
|
60
|
+
names = fileSystem.readDirectory(absoluteDir);
|
|
61
|
+
} catch {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
return names.map((name) => ({
|
|
65
|
+
name,
|
|
66
|
+
directory: isDirectory(fileSystem, path.join(absoluteDir, name))
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
38
69
|
/** Whether `dir` holds a `package.json`. */
|
|
39
70
|
const isPackage = (options, dir) => options.fileSystem.exists(options.path.join(dir, "package.json"));
|
|
40
71
|
/**
|
|
@@ -212,17 +243,13 @@ const getWorkspacePackagesSync = (root, options) => {
|
|
|
212
243
|
let stopped = false;
|
|
213
244
|
for (let current = traversal.next(); current !== void 0 && !stopped; current = traversal.next()) {
|
|
214
245
|
if (traversal.charge() !== void 0) break;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
entries = fileSystem.readDirectory(current.absolute);
|
|
218
|
-
} catch {
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
246
|
+
const entries = readResolvedEntries(options, current.absolute);
|
|
247
|
+
if (entries === void 0) continue;
|
|
221
248
|
for (const entry of entries) {
|
|
222
|
-
if (isPruned(entry)) continue;
|
|
223
|
-
const relative = joinRelative(current.relative, entry);
|
|
224
|
-
const absolute = path.join(current.absolute, entry);
|
|
225
|
-
if (!
|
|
249
|
+
if (isPruned(entry.name)) continue;
|
|
250
|
+
const relative = joinRelative(current.relative, entry.name);
|
|
251
|
+
const absolute = path.join(current.absolute, entry.name);
|
|
252
|
+
if (!entry.directory) continue;
|
|
226
253
|
if (wildcard.crossesSegments && !traversal.admits(current)) {
|
|
227
254
|
stopped = true;
|
|
228
255
|
break;
|
package/index.d.ts
CHANGED
|
@@ -2449,6 +2449,26 @@ interface WorkspaceCatalogsShape {
|
|
|
2449
2449
|
* nothing rather than failing.
|
|
2450
2450
|
*/
|
|
2451
2451
|
readonly importerVersions: () => Effect.Effect<ImporterVersions, CatalogAssemblyFailure>;
|
|
2452
|
+
/**
|
|
2453
|
+
* Discard the memoized assembly so the **next** read re-assembles — the same
|
|
2454
|
+
* single read and hook replay as the first, over the workspace as it stands
|
|
2455
|
+
* then.
|
|
2456
|
+
*
|
|
2457
|
+
* @remarks
|
|
2458
|
+
* The explicit memoization boundary for a tool that **mutates the workspace
|
|
2459
|
+
* mid-run** — installs, bumps a config dependency, regenerates the lockfile.
|
|
2460
|
+
* Release-age gating wants the before-state, a post-install peer check the
|
|
2461
|
+
* after-state, and one infinite memo cannot serve both without this call in
|
|
2462
|
+
* between; without it, `peerDependencyRules` keeps answering from the
|
|
2463
|
+
* pre-mutation hook replay and the checker reports findings the new rules
|
|
2464
|
+
* suppress.
|
|
2465
|
+
*
|
|
2466
|
+
* Unconditional and infallible: the memo is already success-only (a failed
|
|
2467
|
+
* or interrupted assembly retries by itself), so `refresh` exists solely to
|
|
2468
|
+
* discard a *successful* assembly that mutation has made stale. Calling it
|
|
2469
|
+
* before any read is harmless.
|
|
2470
|
+
*/
|
|
2471
|
+
readonly refresh: () => Effect.Effect<void>;
|
|
2452
2472
|
}
|
|
2453
2473
|
/**
|
|
2454
2474
|
* Options for the {@link WorkspaceCatalogs} layer.
|
|
@@ -2554,6 +2574,11 @@ declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
|
|
|
2554
2574
|
* the importer index from the lockfile's importer blocks — neither is in a
|
|
2555
2575
|
* `CatalogSet`) and always die unless stubbed.
|
|
2556
2576
|
*
|
|
2577
|
+
* `refresh` defaults to `Effect.void` honestly: the double holds no memo,
|
|
2578
|
+
* so "drop the memoized assembly" is genuinely a no-op — the stubs answer
|
|
2579
|
+
* fresh on every call already. This mirrors `WorkspaceDiscovery.makeTest`'s
|
|
2580
|
+
* `refresh`.
|
|
2581
|
+
*
|
|
2557
2582
|
* @example
|
|
2558
2583
|
* ```ts
|
|
2559
2584
|
* import { CatalogSet, WorkspaceCatalogs } from "@effected/workspaces";
|
|
@@ -3227,6 +3252,53 @@ interface SyncFileSystem {
|
|
|
3227
3252
|
readonly readDirectory: (path: string) => ReadonlyArray<string>;
|
|
3228
3253
|
/** Whether `path` is a directory. May throw; a throw reads as `false`. */
|
|
3229
3254
|
readonly isDirectory: (path: string) => boolean;
|
|
3255
|
+
/**
|
|
3256
|
+
* Optional fast path: the entries inside `path` with their types already
|
|
3257
|
+
* known, in ONE call.
|
|
3258
|
+
*
|
|
3259
|
+
* @remarks
|
|
3260
|
+
* Package enumeration otherwise costs a `readDirectory` plus one
|
|
3261
|
+
* `isDirectory` per entry — the readdir-then-stat-per-entry shape, which on
|
|
3262
|
+
* a large workspace is a syscall per file. Supplying this collapses that to
|
|
3263
|
+
* a single `readdirSync(path, { withFileTypes: true })`; `nodeFileSystem`
|
|
3264
|
+
* does. Omit it and enumeration falls back to the four required operations
|
|
3265
|
+
* with identical results, so this is purely a cost optimization and never a
|
|
3266
|
+
* behavior switch.
|
|
3267
|
+
*
|
|
3268
|
+
* May throw; a throw skips the directory, exactly like `readDirectory`.
|
|
3269
|
+
*/
|
|
3270
|
+
readonly readDirectoryWithTypes?: ((path: string) => ReadonlyArray<SyncDirectoryEntry>) | undefined;
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* One directory entry with its type resolved, as the optional
|
|
3274
|
+
* {@link SyncFileSystem.readDirectoryWithTypes} fast path reports it. Node's
|
|
3275
|
+
* `Dirent` satisfies it after mapping its predicate methods to booleans.
|
|
3276
|
+
*
|
|
3277
|
+
* @remarks
|
|
3278
|
+
* `isSymbolicLink` is not decoration. A `Dirent` describes the entry ITSELF, so
|
|
3279
|
+
* a symbolic link pointing at a directory reports `isDirectory: false` — while
|
|
3280
|
+
* the `stat`-based slow path, which resolves the link, calls the same entry a
|
|
3281
|
+
* directory. Enumeration therefore re-resolves links through `isDirectory`
|
|
3282
|
+
* rather than trusting `isDirectory` on a link, which is what keeps the fast
|
|
3283
|
+
* and slow paths in agreement on a workspace whose packages are symlinked.
|
|
3284
|
+
*
|
|
3285
|
+
* Which behavior is *correct* is domain-dependent, so the flag is reported
|
|
3286
|
+
* rather than resolved away. Enumeration follows links because a symlinked
|
|
3287
|
+
* package is still a package. A test-file discovery walk usually must NOT: in a
|
|
3288
|
+
* pnpm workspace `node_modules` is a farm of links into the content-addressed
|
|
3289
|
+
* store, and following them walks the whole store or hits a cycle. A consumer
|
|
3290
|
+
* building its own walker on this shape wants `isDirectory` verbatim — there,
|
|
3291
|
+
* not following is the requirement, not the hazard.
|
|
3292
|
+
*
|
|
3293
|
+
* @public
|
|
3294
|
+
*/
|
|
3295
|
+
interface SyncDirectoryEntry {
|
|
3296
|
+
/** The entry's own name, not a path. */
|
|
3297
|
+
readonly name: string;
|
|
3298
|
+
/** Whether the entry itself is a directory. `false` for a symbolic link, even one targeting a directory. */
|
|
3299
|
+
readonly isDirectory: boolean;
|
|
3300
|
+
/** Whether the entry itself is a symbolic link. */
|
|
3301
|
+
readonly isSymbolicLink: boolean;
|
|
3230
3302
|
}
|
|
3231
3303
|
/**
|
|
3232
3304
|
* The synchronous path operations the sync entry points need, supplied by the
|
|
@@ -3382,5 +3454,5 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
|
|
|
3382
3454
|
*/
|
|
3383
3455
|
declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
|
|
3384
3456
|
//#endregion
|
|
3385
|
-
export { type CatalogAssemblyFailure, CatalogSet, ChangeDetectionError, type ChangeDetectionFailure, ChangeDetectionOptions, ChangeDetector, type ChangeDetectorShape, type ClassifyOptions, ConfigDependencyHooks, type ConfigDependencyHooksShape, CyclicDependencyError, type DependencyDiff, DependencyGraph, DetectedPackageManager, type FindWorkspaceRootOptions, type GetWorkspacePackagesSyncOptions, type HookInjection, type ImporterVersions, LockfileReadError, type LockfileReadFailure, LockfileReader, type LockfileReaderOptions, type LockfileReaderShape, NoPeerDependencyRules, PackageManagerDetectionError, type PackageManagerDetectionFailure, PackageManagerDetector, type PackageManagerDetectorShape, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, type PackageRelease, PackageStateSnapshot, PeerCheck, type PeerCheckOptions, type PeerDependencyRules, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, type PublishabilityDetectorShape, ReleaseTag, type SyncFileSystem, type SyncPath, type TagClassification, type TagFormatOptions, TagStyle, TrackingTag, type TrackingTagOptions, UnsatisfiedPeer, type UnverifiedReason, type VersioningDetectOptions, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, type WorkspaceCatalogsOptions, type WorkspaceCatalogsShape, WorkspaceDiscovery, WorkspaceDiscoveryError, type WorkspaceDiscoveryFailure, type WorkspaceDiscoveryOptions, type WorkspaceDiscoveryShape, WorkspaceInfo, type WorkspaceLookupFailure, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, type WorkspaceRootShape, type WorkspaceSnapshotAtFailure, type WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshots, type WorkspaceSnapshotsOptions, type WorkspaceSnapshotsShape, WorkspaceStateSnapshot, Workspaces, type WorkspacesOptions, type WorkspacesServices, type WorkspacesSyncOptions, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
|
|
3457
|
+
export { type CatalogAssemblyFailure, CatalogSet, ChangeDetectionError, type ChangeDetectionFailure, ChangeDetectionOptions, ChangeDetector, type ChangeDetectorShape, type ClassifyOptions, ConfigDependencyHooks, type ConfigDependencyHooksShape, CyclicDependencyError, type DependencyDiff, DependencyGraph, DetectedPackageManager, type FindWorkspaceRootOptions, type GetWorkspacePackagesSyncOptions, type HookInjection, type ImporterVersions, LockfileReadError, type LockfileReadFailure, LockfileReader, type LockfileReaderOptions, type LockfileReaderShape, NoPeerDependencyRules, PackageManagerDetectionError, type PackageManagerDetectionFailure, PackageManagerDetector, type PackageManagerDetectorShape, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, type PackageRelease, PackageStateSnapshot, PeerCheck, type PeerCheckOptions, type PeerDependencyRules, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, type PublishabilityDetectorShape, ReleaseTag, type SyncDirectoryEntry, type SyncFileSystem, type SyncPath, type TagClassification, type TagFormatOptions, TagStyle, TrackingTag, type TrackingTagOptions, UnsatisfiedPeer, type UnverifiedReason, type VersioningDetectOptions, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, type WorkspaceCatalogsOptions, type WorkspaceCatalogsShape, WorkspaceDiscovery, WorkspaceDiscoveryError, type WorkspaceDiscoveryFailure, type WorkspaceDiscoveryOptions, type WorkspaceDiscoveryShape, WorkspaceInfo, type WorkspaceLookupFailure, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, type WorkspaceRootShape, type WorkspaceSnapshotAtFailure, type WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshots, type WorkspaceSnapshotsOptions, type WorkspaceSnapshotsShape, WorkspaceStateSnapshot, Workspaces, type WorkspacesOptions, type WorkspacesServices, type WorkspacesSyncOptions, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
|
|
3386
3458
|
//# sourceMappingURL=index.d.ts.map
|
package/node-sync.d.ts
CHANGED
|
@@ -36,6 +36,53 @@ interface SyncFileSystem {
|
|
|
36
36
|
readonly readDirectory: (path: string) => ReadonlyArray<string>;
|
|
37
37
|
/** Whether `path` is a directory. May throw; a throw reads as `false`. */
|
|
38
38
|
readonly isDirectory: (path: string) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Optional fast path: the entries inside `path` with their types already
|
|
41
|
+
* known, in ONE call.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Package enumeration otherwise costs a `readDirectory` plus one
|
|
45
|
+
* `isDirectory` per entry — the readdir-then-stat-per-entry shape, which on
|
|
46
|
+
* a large workspace is a syscall per file. Supplying this collapses that to
|
|
47
|
+
* a single `readdirSync(path, { withFileTypes: true })`; `nodeFileSystem`
|
|
48
|
+
* does. Omit it and enumeration falls back to the four required operations
|
|
49
|
+
* with identical results, so this is purely a cost optimization and never a
|
|
50
|
+
* behavior switch.
|
|
51
|
+
*
|
|
52
|
+
* May throw; a throw skips the directory, exactly like `readDirectory`.
|
|
53
|
+
*/
|
|
54
|
+
readonly readDirectoryWithTypes?: ((path: string) => ReadonlyArray<SyncDirectoryEntry>) | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* One directory entry with its type resolved, as the optional
|
|
58
|
+
* {@link SyncFileSystem.readDirectoryWithTypes} fast path reports it. Node's
|
|
59
|
+
* `Dirent` satisfies it after mapping its predicate methods to booleans.
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* `isSymbolicLink` is not decoration. A `Dirent` describes the entry ITSELF, so
|
|
63
|
+
* a symbolic link pointing at a directory reports `isDirectory: false` — while
|
|
64
|
+
* the `stat`-based slow path, which resolves the link, calls the same entry a
|
|
65
|
+
* directory. Enumeration therefore re-resolves links through `isDirectory`
|
|
66
|
+
* rather than trusting `isDirectory` on a link, which is what keeps the fast
|
|
67
|
+
* and slow paths in agreement on a workspace whose packages are symlinked.
|
|
68
|
+
*
|
|
69
|
+
* Which behavior is *correct* is domain-dependent, so the flag is reported
|
|
70
|
+
* rather than resolved away. Enumeration follows links because a symlinked
|
|
71
|
+
* package is still a package. A test-file discovery walk usually must NOT: in a
|
|
72
|
+
* pnpm workspace `node_modules` is a farm of links into the content-addressed
|
|
73
|
+
* store, and following them walks the whole store or hits a cycle. A consumer
|
|
74
|
+
* building its own walker on this shape wants `isDirectory` verbatim — there,
|
|
75
|
+
* not following is the requirement, not the hazard.
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
interface SyncDirectoryEntry {
|
|
80
|
+
/** The entry's own name, not a path. */
|
|
81
|
+
readonly name: string;
|
|
82
|
+
/** Whether the entry itself is a directory. `false` for a symbolic link, even one targeting a directory. */
|
|
83
|
+
readonly isDirectory: boolean;
|
|
84
|
+
/** Whether the entry itself is a symbolic link. */
|
|
85
|
+
readonly isSymbolicLink: boolean;
|
|
39
86
|
}
|
|
40
87
|
/**
|
|
41
88
|
* The synchronous path operations the sync entry points need, supplied by the
|
|
@@ -114,5 +161,5 @@ declare const nodePath: SyncPath;
|
|
|
114
161
|
*/
|
|
115
162
|
declare const nodeSyncOps: WorkspacesSyncOptions;
|
|
116
163
|
//#endregion
|
|
117
|
-
export { type SyncFileSystem, type SyncPath, type WorkspacesSyncOptions, nodeFileSystem, nodePath, nodeSyncOps };
|
|
164
|
+
export { type SyncDirectoryEntry, type SyncFileSystem, type SyncPath, type WorkspacesSyncOptions, nodeFileSystem, nodePath, nodeSyncOps };
|
|
118
165
|
//# sourceMappingURL=node-sync.d.ts.map
|
package/node-sync.js
CHANGED
|
@@ -41,7 +41,12 @@ const nodeFileSystem = {
|
|
|
41
41
|
exists: existsSync,
|
|
42
42
|
readFile: (p) => readFileSync(p, "utf8"),
|
|
43
43
|
readDirectory: (p) => readdirSync(p),
|
|
44
|
-
isDirectory: (p) => statSync(p).isDirectory()
|
|
44
|
+
isDirectory: (p) => statSync(p).isDirectory(),
|
|
45
|
+
readDirectoryWithTypes: (p) => readdirSync(p, { withFileTypes: true }).map((entry) => ({
|
|
46
|
+
name: entry.name,
|
|
47
|
+
isDirectory: entry.isDirectory(),
|
|
48
|
+
isSymbolicLink: entry.isSymbolicLink()
|
|
49
|
+
}))
|
|
45
50
|
};
|
|
46
51
|
/**
|
|
47
52
|
* `SyncPath` as the running platform's `node:path` — win32 semantics on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/workspaces",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Monorepo workspace tooling as Effect services — root discovery, package enumeration, the dependency graph, package-manager detection, pnpm catalog resolution, lockfile IO and git-based change detection.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"@effected/commands": "^0.5.0",
|
|
50
50
|
"@effected/git": "^0.9.0",
|
|
51
51
|
"@effected/glob": "^0.4.0",
|
|
52
|
-
"@effected/lockfiles": "^0.6.
|
|
53
|
-
"@effected/npm": "^0.11.
|
|
52
|
+
"@effected/lockfiles": "^0.6.2",
|
|
53
|
+
"@effected/npm": "^0.11.1",
|
|
54
54
|
"@effected/package-json": "^0.10.2",
|
|
55
55
|
"@effected/semver": "^0.5.0",
|
|
56
56
|
"@effected/walker": "^0.5.0",
|