@effected/workspaces 0.28.0-next-20260927071821 → 0.28.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/ConfigDependencyHooks.js +13 -1
- package/LockfileReader.js +2 -1
- package/WorkspaceCatalogs.js +2 -1
- package/WorkspaceDiscovery.js +2 -2
- package/WorkspaceSnapshots.js +2 -1
- package/Workspaces.js +6 -2
- package/index.d.ts +94 -5
- package/internal/configDependencyFetch.js +39 -6
- package/internal/configDependencyResolution.js +1 -0
- package/internal/layerRoot.js +14 -0
- package/package.json +4 -4
- package/testing.d.ts +5 -0
package/ConfigDependencyHooks.js
CHANGED
|
@@ -482,7 +482,19 @@ var ConfigDependencyHooks = class ConfigDependencyHooks extends Context.Service(
|
|
|
482
482
|
* with `reason: "integrityUnavailable"` — nothing is fetched in either case
|
|
483
483
|
* — and a fetch that fails with `reason: "fetchFailed"`, keeping the
|
|
484
484
|
* not-installed remediation. The fetch needs `pnpm` 11 or 12 on `PATH` and
|
|
485
|
-
* registry access, and is bounded at two minutes.
|
|
485
|
+
* registry access, and is bounded at two minutes.
|
|
486
|
+
*
|
|
487
|
+
* The scratch workspace fetches through the same registries a
|
|
488
|
+
* `pnpm install` in the workspace would: the workspace root's `.npmrc` (scoped
|
|
489
|
+
* registries, mirrors, auth) is copied into it as-is, with `${VAR}`
|
|
490
|
+
* references left for pnpm to expand, never read or logged, and removed
|
|
491
|
+
* with the scratch; and the root `pnpm-workspace.yaml`'s `registry` and
|
|
492
|
+
* `registries` keys are carried into the scratch's. That is the CURRENT
|
|
493
|
+
* checkout's registry config for both sides of a diff — a base ref's
|
|
494
|
+
* `.npmrc` is not read through git — and relative paths inside the `.npmrc`
|
|
495
|
+
* (a `cafile=./ca.pem`) resolve against the scratch, not the root.
|
|
496
|
+
*
|
|
497
|
+
* An empty `configDependencies`, or one whose dependencies all
|
|
486
498
|
* ship no pnpmfile, returns the seed without spawning anything; a `..` path
|
|
487
499
|
* segment in a dependency name fails typed **before** any spawn; a
|
|
488
500
|
* load failure in the child — a syntax error, a throwing top level, an
|
package/LockfileReader.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLayerRoot } from "./internal/layerRoot.js";
|
|
1
2
|
import { WorkspaceRoot } from "./WorkspaceRoot.js";
|
|
2
3
|
import { WorkspaceDiscovery } from "./WorkspaceDiscovery.js";
|
|
3
4
|
import { PackageManagerDetector } from "./PackageManagerName.js";
|
|
@@ -60,7 +61,7 @@ var LockfileReader = class LockfileReader extends Context.Service()("@effected/w
|
|
|
60
61
|
const fs = yield* FileSystem.FileSystem;
|
|
61
62
|
const path = yield* Path.Path;
|
|
62
63
|
const init = Effect.gen(function* () {
|
|
63
|
-
const root = yield*
|
|
64
|
+
const root = yield* findLayerRoot(roots, options);
|
|
64
65
|
const format = (yield* detector.detect(root)).name;
|
|
65
66
|
const lockfilePath = path.join(root, filenameFor(format));
|
|
66
67
|
const content = yield* fs.readFileString(lockfilePath).pipe(Effect.mapError((cause) => new LockfileReadError({
|
package/WorkspaceCatalogs.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLayerRoot } from "./internal/layerRoot.js";
|
|
1
2
|
import { WorkspaceRoot } from "./WorkspaceRoot.js";
|
|
2
3
|
import { inlineCatalogs, merge, normalize, rangeOf } from "./internal/catalogs.js";
|
|
3
4
|
import { ConfigDependencyHooks, NoPeerDependencyRules } from "./ConfigDependencyHooks.js";
|
|
@@ -342,7 +343,7 @@ var WorkspaceCatalogs = class WorkspaceCatalogs extends Context.Service()("@effe
|
|
|
342
343
|
}));
|
|
343
344
|
}));
|
|
344
345
|
const assemble = Effect.gen(function* () {
|
|
345
|
-
const root = yield*
|
|
346
|
+
const root = yield* findLayerRoot(roots, options);
|
|
346
347
|
const lockfileOutputs = yield* lockfiles.read().pipe(Effect.map((lockfile) => ({
|
|
347
348
|
catalogs: CatalogSet.fromLockfile(lockfile),
|
|
348
349
|
importerVersions: importerVersionsOf(lockfile)
|
package/WorkspaceDiscovery.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { enumerate } from "./internal/enumerate.js";
|
|
2
|
+
import { findLayerRoot } from "./internal/layerRoot.js";
|
|
2
3
|
import { readPatterns } from "./internal/patterns.js";
|
|
3
4
|
import { WorkspacePackage } from "./WorkspacePackage.js";
|
|
4
5
|
import { WorkspaceRoot } from "./WorkspaceRoot.js";
|
|
@@ -256,8 +257,7 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
|
|
|
256
257
|
};
|
|
257
258
|
}).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
258
259
|
const discover = Effect.gen(function* () {
|
|
259
|
-
const
|
|
260
|
-
const root = yield* Effect.suspend(() => roots.find(options?.cwd ?? process.cwd(), stopAt === void 0 ? void 0 : { stopAt }));
|
|
260
|
+
const root = yield* findLayerRoot(roots, options);
|
|
261
261
|
return yield* discoverAt(root);
|
|
262
262
|
});
|
|
263
263
|
const [resolveOnce, invalidate] = yield* Effect.cachedInvalidateWithTTL(discover, Duration.infinity);
|
package/WorkspaceSnapshots.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLayerRoot } from "./internal/layerRoot.js";
|
|
1
2
|
import { manifestPatternsOf, pnpmPatternsOf } from "./internal/patterns.js";
|
|
2
3
|
import { WorkspaceRoot } from "./WorkspaceRoot.js";
|
|
3
4
|
import { WorkspaceDiscovery } from "./WorkspaceDiscovery.js";
|
|
@@ -210,7 +211,7 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
|
|
|
210
211
|
const atCaches = /* @__PURE__ */ new Map();
|
|
211
212
|
return {
|
|
212
213
|
at: Effect.fn("WorkspaceSnapshots.at")(function* (ref) {
|
|
213
|
-
const root = yield*
|
|
214
|
+
const root = yield* findLayerRoot(roots, options);
|
|
214
215
|
const key = `${root}\0${ref}`;
|
|
215
216
|
let memo = atCaches.get(key);
|
|
216
217
|
if (memo === void 0) {
|
package/Workspaces.js
CHANGED
|
@@ -48,7 +48,8 @@ const localExecLayer = (options) => Layer.effect(LocalExec, Effect.gen(function*
|
|
|
48
48
|
const detector = yield* PackageManagerDetector;
|
|
49
49
|
return { context: Effect.gen(function* () {
|
|
50
50
|
const cwd = options?.cwd ?? globalThis.process?.cwd?.() ?? "/";
|
|
51
|
-
const
|
|
51
|
+
const stopAt = options?.stopAt;
|
|
52
|
+
const root = yield* roots.find(cwd, stopAt === void 0 ? void 0 : { stopAt }).pipe(Effect.asSome, Effect.orElseSucceed(Option.none));
|
|
52
53
|
if (Option.isNone(root)) return Option.none();
|
|
53
54
|
const detected = yield* detector.detect(root.value).pipe(Effect.asSome, Effect.catchTag("PackageManagerDetectionError", () => Effect.succeed(Option.none())), Effect.mapError((cause) => new LocalExecError({
|
|
54
55
|
directory: root.value,
|
|
@@ -289,7 +290,10 @@ var Workspaces = class {
|
|
|
289
290
|
* verbatim.
|
|
290
291
|
*
|
|
291
292
|
* `directory` is the resolved **workspace root**, not the caller's cwd: a
|
|
292
|
-
* project-local launcher has to run where the workspace is.
|
|
293
|
+
* project-local launcher has to run where the workspace is. `stopAt` caps
|
|
294
|
+
* that ascent exactly as {@link WorkspacesOptions.stopAt} does, and a
|
|
295
|
+
* ceiling with no root at or below it is the same `Option.none()` as no
|
|
296
|
+
* root at all.
|
|
293
297
|
*
|
|
294
298
|
* A consumer with no monorepo never needs this layer, and therefore never
|
|
295
299
|
* installs this package — `LocalExec.layerNone` and `LocalExec.layerFor`
|
package/index.d.ts
CHANGED
|
@@ -560,6 +560,11 @@ interface WorkspaceDiscoveryOptions {
|
|
|
560
560
|
* directory the caller names, which a single layer-level ceiling cannot
|
|
561
561
|
* sensibly bound, so they ascend unbounded as before.
|
|
562
562
|
*
|
|
563
|
+
* `LockfileReader`, `WorkspaceCatalogs` and `WorkspaceSnapshots` each
|
|
564
|
+
* take the same option; give them the same
|
|
565
|
+
* value when wiring by hand, or let a `Workspaces.*` composite forward one
|
|
566
|
+
* `stopAt` to all of them, so no service adopts a root another refused.
|
|
567
|
+
*
|
|
563
568
|
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
564
569
|
*/
|
|
565
570
|
readonly stopAt?: string | undefined;
|
|
@@ -1252,7 +1257,19 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
|
|
|
1252
1257
|
* with `reason: "integrityUnavailable"` — nothing is fetched in either case
|
|
1253
1258
|
* — and a fetch that fails with `reason: "fetchFailed"`, keeping the
|
|
1254
1259
|
* not-installed remediation. The fetch needs `pnpm` 11 or 12 on `PATH` and
|
|
1255
|
-
* registry access, and is bounded at two minutes.
|
|
1260
|
+
* registry access, and is bounded at two minutes.
|
|
1261
|
+
*
|
|
1262
|
+
* The scratch workspace fetches through the same registries a
|
|
1263
|
+
* `pnpm install` in the workspace would: the workspace root's `.npmrc` (scoped
|
|
1264
|
+
* registries, mirrors, auth) is copied into it as-is, with `${VAR}`
|
|
1265
|
+
* references left for pnpm to expand, never read or logged, and removed
|
|
1266
|
+
* with the scratch; and the root `pnpm-workspace.yaml`'s `registry` and
|
|
1267
|
+
* `registries` keys are carried into the scratch's. That is the CURRENT
|
|
1268
|
+
* checkout's registry config for both sides of a diff — a base ref's
|
|
1269
|
+
* `.npmrc` is not read through git — and relative paths inside the `.npmrc`
|
|
1270
|
+
* (a `cafile=./ca.pem`) resolve against the scratch, not the root.
|
|
1271
|
+
*
|
|
1272
|
+
* An empty `configDependencies`, or one whose dependencies all
|
|
1256
1273
|
* ship no pnpmfile, returns the seed without spawning anything; a `..` path
|
|
1257
1274
|
* segment in a dependency name fails typed **before** any spawn; a
|
|
1258
1275
|
* load failure in the child — a syntax error, a throwing top level, an
|
|
@@ -2054,6 +2071,21 @@ interface LockfileReaderOptions {
|
|
|
2054
2071
|
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
2055
2072
|
*/
|
|
2056
2073
|
readonly cwd?: string;
|
|
2074
|
+
/**
|
|
2075
|
+
* A ceiling for the root ascent from `cwd`, passed straight through to the
|
|
2076
|
+
* `stopAt` of {@link WorkspaceRoot}'s `find`.
|
|
2077
|
+
*
|
|
2078
|
+
* @remarks
|
|
2079
|
+
* Inclusive, and resolved to an absolute path exactly as `cwd` is. When no
|
|
2080
|
+
* root is found at or below the ceiling, `read` fails with
|
|
2081
|
+
* {@link WorkspaceRootNotFoundError} carrying the resolved `stopAt` rather
|
|
2082
|
+
* than adopting an enclosing directory's workspace. Pass the same value as
|
|
2083
|
+
* {@link WorkspaceDiscoveryOptions.stopAt} so every service agrees on the
|
|
2084
|
+
* root; the `Workspaces.*` composites forward one `stopAt` to all of them.
|
|
2085
|
+
*
|
|
2086
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
2087
|
+
*/
|
|
2088
|
+
readonly stopAt?: string | undefined;
|
|
2057
2089
|
}
|
|
2058
2090
|
declare const LockfileReader_base: Context.ServiceClass<LockfileReader, "@effected/workspaces/LockfileReader", LockfileReaderShape>;
|
|
2059
2091
|
/**
|
|
@@ -3131,6 +3163,21 @@ interface WorkspaceCatalogsOptions {
|
|
|
3131
3163
|
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
3132
3164
|
*/
|
|
3133
3165
|
readonly cwd?: string;
|
|
3166
|
+
/**
|
|
3167
|
+
* A ceiling for the root ascent from `cwd`, passed straight through to the
|
|
3168
|
+
* `stopAt` of {@link WorkspaceRoot}'s `find`.
|
|
3169
|
+
*
|
|
3170
|
+
* @remarks
|
|
3171
|
+
* Inclusive, and resolved to an absolute path exactly as `cwd` is. When no
|
|
3172
|
+
* root is found at or below the ceiling, catalog assembly fails with
|
|
3173
|
+
* {@link WorkspaceRootNotFoundError} carrying the resolved `stopAt` rather
|
|
3174
|
+
* than adopting an enclosing directory's workspace. Pass the same value as
|
|
3175
|
+
* {@link WorkspaceDiscoveryOptions.stopAt} so every service agrees on the
|
|
3176
|
+
* root; the `Workspaces.*` composites forward one `stopAt` to all of them.
|
|
3177
|
+
*
|
|
3178
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
3179
|
+
*/
|
|
3180
|
+
readonly stopAt?: string | undefined;
|
|
3134
3181
|
}
|
|
3135
3182
|
declare const WorkspaceCatalogs_base: Context.ServiceClass<WorkspaceCatalogs, "@effected/workspaces/WorkspaceCatalogs", WorkspaceCatalogsShape>;
|
|
3136
3183
|
/**
|
|
@@ -3684,6 +3731,21 @@ interface WorkspaceSnapshotsOptions {
|
|
|
3684
3731
|
* first call is honoured.
|
|
3685
3732
|
*/
|
|
3686
3733
|
readonly cwd?: string;
|
|
3734
|
+
/**
|
|
3735
|
+
* A ceiling for the root ascent from `cwd`, passed straight through to the
|
|
3736
|
+
* `stopAt` of {@link WorkspaceRoot}'s `find`.
|
|
3737
|
+
*
|
|
3738
|
+
* @remarks
|
|
3739
|
+
* Inclusive, and resolved to an absolute path exactly as `cwd` is. When no
|
|
3740
|
+
* root is found at or below the ceiling, `at(ref)` fails with
|
|
3741
|
+
* {@link WorkspaceRootNotFoundError} carrying the resolved `stopAt` rather
|
|
3742
|
+
* than adopting an enclosing directory's workspace. Pass the same value as
|
|
3743
|
+
* {@link WorkspaceDiscoveryOptions.stopAt} so every service agrees on the
|
|
3744
|
+
* root; the `Workspaces.*` composites forward one `stopAt` to all of them.
|
|
3745
|
+
*
|
|
3746
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
3747
|
+
*/
|
|
3748
|
+
readonly stopAt?: string | undefined;
|
|
3687
3749
|
/**
|
|
3688
3750
|
* Catalogs every snapshot this service produces carries as its
|
|
3689
3751
|
* `seededCatalogs` — consulted only where the snapshot's own catalogs cannot
|
|
@@ -3851,9 +3913,17 @@ export declare class WorkspaceSnapshots extends WorkspaceSnapshots_base {
|
|
|
3851
3913
|
/**
|
|
3852
3914
|
* Options shared by the composite layers.
|
|
3853
3915
|
*
|
|
3916
|
+
* @remarks
|
|
3917
|
+
* Derived from the option shapes of the services a composite builds —
|
|
3918
|
+
* {@link WorkspaceDiscoveryOptions}, {@link LockfileReaderOptions} and
|
|
3919
|
+
* {@link WorkspaceCatalogsOptions} — and handed to each of them unchanged, so a
|
|
3920
|
+
* root-resolving option one service grows cannot silently stop at the
|
|
3921
|
+
* composite. `cwd` and `stopAt` are re-declared here only to document them as
|
|
3922
|
+
* the one concern they are across the composite.
|
|
3923
|
+
*
|
|
3854
3924
|
* @public
|
|
3855
3925
|
*/
|
|
3856
|
-
interface WorkspacesOptions {
|
|
3926
|
+
interface WorkspacesOptions extends WorkspaceDiscoveryOptions, LockfileReaderOptions, WorkspaceCatalogsOptions {
|
|
3857
3927
|
/**
|
|
3858
3928
|
* The directory every root-consuming service resolves the workspace root
|
|
3859
3929
|
* from — one explicit concern, applied uniformly.
|
|
@@ -3861,8 +3931,23 @@ interface WorkspacesOptions {
|
|
|
3861
3931
|
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
3862
3932
|
*/
|
|
3863
3933
|
readonly cwd?: string;
|
|
3864
|
-
/**
|
|
3865
|
-
|
|
3934
|
+
/**
|
|
3935
|
+
* A ceiling for the root ascent from `cwd`, forwarded to EVERY
|
|
3936
|
+
* root-resolving service the composite builds — {@link WorkspaceDiscovery},
|
|
3937
|
+
* {@link LockfileReader}, {@link WorkspaceCatalogs} and, on the git
|
|
3938
|
+
* composites, {@link WorkspaceSnapshots}.
|
|
3939
|
+
*
|
|
3940
|
+
* @remarks
|
|
3941
|
+
* Inclusive and resolved to an absolute path, as {@link WorkspaceRoot}'s
|
|
3942
|
+
* `find` does. Pass `stopAt: cwd` for a checkout nested inside someone
|
|
3943
|
+
* else's workspace: every service then fails with
|
|
3944
|
+
* {@link WorkspaceRootNotFoundError} consistently, instead of discovery
|
|
3945
|
+
* refusing the enclosing workspace while the lockfile and catalog reads
|
|
3946
|
+
* adopt it. A checkout that is itself a workspace root still resolves.
|
|
3947
|
+
*
|
|
3948
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
3949
|
+
*/
|
|
3950
|
+
readonly stopAt?: string | undefined;
|
|
3866
3951
|
}
|
|
3867
3952
|
/**
|
|
3868
3953
|
* Options for the composites that additionally provide {@link WorkspaceSnapshots}
|
|
@@ -4108,7 +4193,10 @@ export declare class Workspaces {
|
|
|
4108
4193
|
* verbatim.
|
|
4109
4194
|
*
|
|
4110
4195
|
* `directory` is the resolved **workspace root**, not the caller's cwd: a
|
|
4111
|
-
* project-local launcher has to run where the workspace is.
|
|
4196
|
+
* project-local launcher has to run where the workspace is. `stopAt` caps
|
|
4197
|
+
* that ascent exactly as {@link WorkspacesOptions.stopAt} does, and a
|
|
4198
|
+
* ceiling with no root at or below it is the same `Option.none()` as no
|
|
4199
|
+
* root at all.
|
|
4112
4200
|
*
|
|
4113
4201
|
* A consumer with no monorepo never needs this layer, and therefore never
|
|
4114
4202
|
* installs this package — `LocalExec.layerNone` and `LocalExec.layerFor`
|
|
@@ -4137,6 +4225,7 @@ export declare class Workspaces {
|
|
|
4137
4225
|
*/
|
|
4138
4226
|
static readonly localExecLayer: (options?: {
|
|
4139
4227
|
readonly cwd?: string;
|
|
4228
|
+
readonly stopAt?: string | undefined;
|
|
4140
4229
|
}) => Layer.Layer<LocalExec, never, PackageManagerDetector | WorkspaceRoot>;
|
|
4141
4230
|
/**
|
|
4142
4231
|
* Resolve every `catalog:` and `workspace:` specifier in one `Manifest`
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { carries, manifestVersion, messageOf, sideLabel } from "./configDependencyShared.js";
|
|
2
2
|
import { splitConfigDependencySpec } from "./configDependencySpecGrammar.js";
|
|
3
|
-
import { Duration, Effect } from "effect";
|
|
3
|
+
import { Duration, Effect, Predicate } from "effect";
|
|
4
4
|
import { IntegrityHash } from "@effected/npm";
|
|
5
|
+
import { Yaml } from "@effected/yaml";
|
|
5
6
|
import { Run } from "@effected/commands";
|
|
6
7
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
7
|
-
import { mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
8
|
+
import { copyFile, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { basename, dirname, join } from "node:path";
|
|
10
11
|
|
|
@@ -47,12 +48,36 @@ const expectedIntegrity = (request) => Effect.gen(function* () {
|
|
|
47
48
|
if (integrity === void 0) return yield* Effect.fail(fetchFailure("integrityUnavailable", `config dependency ${key} must be fetched, but ${sideLabel(side)} records no integrity for it (no inline integrity in pnpm-workspace.yaml and no pnpm-lock.yaml entry for that version), so a fetched copy could not be verified and nothing was fetched.`));
|
|
48
49
|
return integrity;
|
|
49
50
|
});
|
|
51
|
+
/** The {@link RegistrySettings} of a parsed `pnpm-workspace.yaml` document. */
|
|
52
|
+
const registrySettingsOf = (document) => {
|
|
53
|
+
if (!Predicate.isObject(document)) return { registries: {} };
|
|
54
|
+
const registries = {};
|
|
55
|
+
if (Predicate.isObject(document.registries)) {
|
|
56
|
+
for (const [scope, url] of Object.entries(document.registries)) if (typeof url === "string") registries[scope] = url;
|
|
57
|
+
}
|
|
58
|
+
return typeof document.registry === "string" ? {
|
|
59
|
+
registry: document.registry,
|
|
60
|
+
registries
|
|
61
|
+
} : { registries };
|
|
62
|
+
};
|
|
50
63
|
/**
|
|
51
|
-
* The scratch workspace's `pnpm-workspace.yaml
|
|
64
|
+
* The scratch workspace's `pnpm-workspace.yaml`: the one config dependency,
|
|
65
|
+
* plus the declaring workspace's registry settings. Every key and scalar is a
|
|
52
66
|
* JSON-quoted string, which YAML reads verbatim, so no declared text can
|
|
53
67
|
* change the document's structure.
|
|
54
68
|
*/
|
|
55
|
-
const scratchWorkspaceYaml = (name, version
|
|
69
|
+
const scratchWorkspaceYaml = (name, version, settings = { registries: {} }) => {
|
|
70
|
+
const lines = ["configDependencies:", ` ${JSON.stringify(name)}: ${JSON.stringify(version)}`];
|
|
71
|
+
if (settings.registry !== void 0) lines.push(`registry: ${JSON.stringify(settings.registry)}`);
|
|
72
|
+
const scopes = Object.entries(settings.registries);
|
|
73
|
+
if (scopes.length > 0) {
|
|
74
|
+
lines.push("registries:");
|
|
75
|
+
for (const [scope, url] of scopes) lines.push(` ${JSON.stringify(scope)}: ${JSON.stringify(url)}`);
|
|
76
|
+
}
|
|
77
|
+
return `${lines.join("\n")}\n`;
|
|
78
|
+
};
|
|
79
|
+
/** Whether `error` is a node filesystem "no such file" failure. */
|
|
80
|
+
const isNotFound = (error) => Predicate.isObject(error) && error.code === "ENOENT";
|
|
56
81
|
/**
|
|
57
82
|
* The scratch workspace's `pnpm-lock.yaml`: the env preamble pnpm 11 and 12
|
|
58
83
|
* write for a lone config dependency, pinned to `integrity`, followed by the
|
|
@@ -123,14 +148,22 @@ const makeFetchConfigDependency = (spawner) => (request) => Effect.scoped(Effect
|
|
|
123
148
|
catch: (cause) => failed(`${what} failed: ${messageOf(cause)}`, cause)
|
|
124
149
|
});
|
|
125
150
|
const integrity = yield* expectedIntegrity(request);
|
|
151
|
+
const rootYaml = yield* io("reading the workspace pnpm-workspace.yaml", () => readFile(join(request.root, "pnpm-workspace.yaml"), "utf8").catch((error) => {
|
|
152
|
+
if (isNotFound(error)) return void 0;
|
|
153
|
+
throw error;
|
|
154
|
+
}));
|
|
155
|
+
const settings = rootYaml === void 0 ? registrySettingsOf(void 0) : registrySettingsOf(yield* Yaml.parse(rootYaml).pipe(Effect.mapError((cause) => failed(`reading the workspace pnpm-workspace.yaml failed: ${messageOf(cause)}`, cause))));
|
|
126
156
|
const scratch = yield* Effect.acquireRelease(io("creating a scratch workspace", () => mkdtemp(join(tmpdir(), "effected-config-dependency-"))), (dir) => Effect.promise(() => rm(dir, {
|
|
127
157
|
recursive: true,
|
|
128
158
|
force: true
|
|
129
159
|
}).catch(() => void 0)));
|
|
130
160
|
const lockfile = scratchLockfile(name, version, integrity);
|
|
131
161
|
yield* io("writing the scratch workspace", async () => {
|
|
132
|
-
await writeFile(join(scratch, "pnpm-workspace.yaml"), scratchWorkspaceYaml(name, version));
|
|
162
|
+
await writeFile(join(scratch, "pnpm-workspace.yaml"), scratchWorkspaceYaml(name, version, settings));
|
|
133
163
|
await writeFile(join(scratch, "pnpm-lock.yaml"), lockfile);
|
|
164
|
+
await copyFile(join(request.root, ".npmrc"), join(scratch, ".npmrc")).catch((error) => {
|
|
165
|
+
if (!isNotFound(error)) throw error;
|
|
166
|
+
});
|
|
134
167
|
});
|
|
135
168
|
const command = ChildProcess.make("pnpm", fetchArgs(scratch, request.stores), { cwd: scratch });
|
|
136
169
|
yield* Run.text(command, { timeout: FETCH_TIMEOUT }).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.mapError((cause) => failed(`fetching ${key} with \`pnpm ${fetchArgs("<scratch>", request.stores).join(" ")}\` failed: ${messageOf(cause)}`, cause)));
|
|
@@ -143,4 +176,4 @@ const makeFetchConfigDependency = (spawner) => (request) => Effect.scoped(Effect
|
|
|
143
176
|
}));
|
|
144
177
|
|
|
145
178
|
//#endregion
|
|
146
|
-
export { expectedIntegrity, fetchArgs, makeFetchConfigDependency, storeDirArgument };
|
|
179
|
+
export { expectedIntegrity, fetchArgs, makeFetchConfigDependency, registrySettingsOf, scratchWorkspaceYaml, storeDirArgument };
|
|
@@ -266,6 +266,7 @@ const resolveDirectory = (root, entry, stores, locks, options) => Effect.gen(fun
|
|
|
266
266
|
if (options.fetch === void 0) return yield* Effect.fail(notInstalled(Option.none()));
|
|
267
267
|
return {
|
|
268
268
|
dir: yield* options.fetch({
|
|
269
|
+
root,
|
|
269
270
|
name,
|
|
270
271
|
version: declared,
|
|
271
272
|
spec: entry.spec,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/layerRoot.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the workspace root a layer's options name. `Effect.suspend` so the
|
|
6
|
+
* ambient `process.cwd()` is read at first use, not at layer construction.
|
|
7
|
+
*/
|
|
8
|
+
const findLayerRoot = (roots, options) => Effect.suspend(() => {
|
|
9
|
+
const stopAt = options?.stopAt;
|
|
10
|
+
return roots.find(options?.cwd ?? process.cwd(), stopAt === void 0 ? void 0 : { stopAt });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { findLayerRoot };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/workspaces",
|
|
3
|
-
"version": "0.28.0
|
|
3
|
+
"version": "0.28.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": [
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"@effected/git": "^0.17.0",
|
|
56
56
|
"@effected/glob": "^0.8.0",
|
|
57
57
|
"@effected/jsonc": "^0.13.0",
|
|
58
|
-
"@effected/lockfiles": "^0.12.0
|
|
59
|
-
"@effected/npm": "^0.17.0
|
|
60
|
-
"@effected/package-json": "^0.18.0
|
|
58
|
+
"@effected/lockfiles": "^0.12.0",
|
|
59
|
+
"@effected/npm": "^0.17.0",
|
|
60
|
+
"@effected/package-json": "^0.18.0",
|
|
61
61
|
"@effected/semver": "^0.9.0",
|
|
62
62
|
"@effected/walker": "^0.12.0",
|
|
63
63
|
"@effected/yaml": "^0.17.0",
|
package/testing.d.ts
CHANGED
|
@@ -652,6 +652,11 @@ interface WorkspaceDiscoveryOptions {
|
|
|
652
652
|
* directory the caller names, which a single layer-level ceiling cannot
|
|
653
653
|
* sensibly bound, so they ascend unbounded as before.
|
|
654
654
|
*
|
|
655
|
+
* `LockfileReader`, `WorkspaceCatalogs` and `WorkspaceSnapshots` each
|
|
656
|
+
* take the same option; give them the same
|
|
657
|
+
* value when wiring by hand, or let a `Workspaces.*` composite forward one
|
|
658
|
+
* `stopAt` to all of them, so no service adopts a root another refused.
|
|
659
|
+
*
|
|
655
660
|
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
656
661
|
*/
|
|
657
662
|
readonly stopAt?: string | undefined;
|