@effected/package-json 0.19.1 → 0.20.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/EntryPoint.js CHANGED
@@ -6,9 +6,8 @@ import { Result, Schema } from "effect";
6
6
  *
7
7
  * @remarks
8
8
  * The reason is discriminated rather than a bare "not found" because the three
9
- * shapes call for different responses, and a caller staring at a consumer's
10
- * plugin at 3am needs to know which one it hit. Collapsing them into one
11
- * sentinel is the same class of quiet wrong answer as an untyped error channel.
9
+ * shapes call for different responses, and a caller needs to know which one it
10
+ * hit.
12
11
  *
13
12
  * @public
14
13
  */
@@ -74,11 +73,10 @@ const resolveConditions = (conditionsObject, conditions) => {
74
73
  * Resolve a package's root entry point from its manifest.
75
74
  *
76
75
  * @remarks
77
- * The half of "read something out of a published package" that has no home
78
- * anywhere else: given a manifest, which file is the package's `"."` entry?
79
- * It is pure and IO-free by design — nothing here touches a filesystem, so it
80
- * is testable against plain manifest objects with no package on disk, and it
81
- * composes with a directory that arrived by any route.
76
+ * Answers "given a manifest, which file is the package's `"."` entry?". It is
77
+ * pure and IO-free by design — nothing here touches a filesystem, so it works
78
+ * on plain manifest objects with no package on disk, and it composes with a
79
+ * directory that arrived by any route.
82
80
  *
83
81
  * All three legal `exports` spellings are honoured, because all three appear in
84
82
  * real published packages:
@@ -105,7 +103,6 @@ const resolveConditions = (conditionsObject, conditions) => {
105
103
  * @example
106
104
  * ```ts
107
105
  * import { resolveEntryPoint } from "@effected/package-json";
108
- * import { Result, Schema } from "effect";
109
106
  *
110
107
  * resolveEntryPoint({ exports: { import: "./esm.js", require: "./cjs.js" } });
111
108
  * // Result.succeed("./esm.js")
@@ -210,8 +210,8 @@ var LenientManifest = class LenientManifest extends Schema.Class("LenientManifes
210
210
  * {@link LenientManifest.decodeResult}, adding the tracing span.
211
211
  *
212
212
  * @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
213
- * @returns an Effect resolving to the decoded {@link LenientManifest}
214
- * @throws (typed) `PackageDecodeError` when `input` is not a JSON object
213
+ * @returns an Effect resolving to the decoded {@link LenientManifest},
214
+ * failing with {@link PackageDecodeError} when `input` is not a JSON object
215
215
  */
216
216
  static decode = Effect.fn("LenientManifest.decode")((input) => Effect.fromResult(LenientManifest.decodeResult(input)));
217
217
  /**
@@ -242,8 +242,8 @@ var LenientManifest = class LenientManifest extends Schema.Class("LenientManifes
242
242
  * {@link LenientManifest.parseResult}, adding the tracing span.
243
243
  *
244
244
  * @param text - the package.json source text
245
- * @returns an Effect resolving to the decoded {@link LenientManifest}
246
- * @throws (typed) `PackageJsonSyntaxError` when the text is not valid JSON
245
+ * @returns an Effect resolving to the decoded {@link LenientManifest},
246
+ * failing with {@link PackageJsonSyntaxError} when the text is not valid JSON
247
247
  * or is not a JSON object
248
248
  */
249
249
  static parse = Effect.fn("LenientManifest.parse")((text) => Effect.fromResult(LenientManifest.parseResult(text)));
package/License.js CHANGED
@@ -53,9 +53,8 @@ const SpdxLicense = Schema.String.pipe(Schema.check(Schema.makeFilter((value) =>
53
53
  * @remarks
54
54
  * This is the accessor to reach for whenever a branded `SpdxLicense` has
55
55
  * to become an actual expression — a license URL, a badge, structured data, a
56
- * policy check. It exists because the brand and the grammar disagree, and that
57
- * disagreement is knowledge these two packages jointly own rather than
58
- * something each consumer should rediscover.
56
+ * policy check. It exists because the brand and the grammar disagree, so no
57
+ * consumer has to rediscover that disagreement.
59
58
  *
60
59
  * `UNLICENSED` and `SEE LICENSE IN <file>` are legal in a manifest and are not
61
60
  * SPDX expressions, so they yield `Option.none()`. Everything else parses.
@@ -66,12 +65,14 @@ const SpdxLicense = Schema.String.pipe(Schema.check(Schema.makeFilter((value) =>
66
65
  *
67
66
  * @example
68
67
  * ```ts
69
- * import { licenseExpressionOf } from "@effected/package-json";
70
- * import { SpdxExpression } from "@effected/spdx";
68
+ * import { SpdxLicense, licenseExpressionOf } from "@effected/package-json";
69
+ * import { Option, Schema } from "effect";
71
70
  *
72
- * // "MIT" => Option.some(<LicenseNode MIT>)
73
- * // "UNLICENSED" => Option.none()
74
- * // "SEE LICENSE IN LICENSE.txt" => Option.none()
71
+ * const decode = Schema.decodeUnknownSync(SpdxLicense);
72
+ *
73
+ * Option.isSome(licenseExpressionOf(decode("MIT"))); // => true
74
+ * Option.isSome(licenseExpressionOf(decode("UNLICENSED"))); // => false
75
+ * Option.isSome(licenseExpressionOf(decode("SEE LICENSE IN LICENSE.txt"))); // => false
75
76
  * ```
76
77
  *
77
78
  * @param license - a branded manifest license value
package/Package.js CHANGED
@@ -67,8 +67,8 @@ const PeerDependenciesMetaField = Schema.Record(Schema.String, Schema.Struct({ o
67
67
  *
68
68
  * @deprecated Superseded by {@link Repository.FromValue}, which decodes both
69
69
  * encodings into a typed {@link Repository} with normalization getters and
70
- * round-trips the original form. Kept as a named type for consumers that were
71
- * matching on the raw union; it is no longer what `Package.repository` uses.
70
+ * round-trips the original form. Retained only as a name for the raw union;
71
+ * `Package.repository` is a {@link Repository}.
72
72
  *
73
73
  * @public
74
74
  */
@@ -161,8 +161,8 @@ var Package = class Package extends Schema.Class("Package")({
161
161
  * `SchemaError` to a typed {@link PackageDecodeError} at the boundary.
162
162
  *
163
163
  * @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
164
- * @returns an Effect resolving to the decoded `Package`
165
- * @throws (typed) `PackageDecodeError` when `input` does not satisfy the schema
164
+ * @returns an Effect resolving to the decoded `Package`, failing with
165
+ * {@link PackageDecodeError} when `input` does not satisfy the schema
166
166
  */
167
167
  static decode = Effect.fn("Package.decode")(function* (input) {
168
168
  return yield* Schema.decodeUnknownEffect(Package.schema)(input).pipe(Effect.catchTag("SchemaError", (cause) => new PackageDecodeError({ cause })));
@@ -262,20 +262,39 @@ var Package = class Package extends Schema.Class("Package")({
262
262
  /**
263
263
  * Resolve `catalog:` and `workspace:` specifiers across all four dependency
264
264
  * maps using the `CatalogResolver` and `WorkspaceResolver` from context,
265
- * classifying and projecting through `@effected/npm`'s `DependencySpecifier`
266
- * statics (`workspace:` uses the pnpm publish-time projection; the alias
267
- * form `workspace:<name>@<range>` resolves the TARGET package's version and
268
- * becomes the published `npm:<name>@<range>` alias). Specifiers the
269
- * resolvers return `None` for are left unchanged — resolution still
270
- * succeeds. A `CatalogResolver` whose catalog assembly failed surfaces
271
- * typed as `@effected/npm`'s `CatalogAssemblyError`, alongside the
272
- * contracts' `DependencyResolutionError`. This is the explicit resolution
273
- * step — `PackageJsonFile.write` never resolves.
265
+ * returning a new `Package`. This is the explicit resolution step —
266
+ * `PackageJsonFile.write` never resolves.
274
267
  *
275
268
  * @remarks
276
- * Leaves unresolvable specifiers unchanged. For fail-typed manifest
269
+ * Classification and projection go through `@effected/npm`'s
270
+ * `DependencySpecifier` statics: `workspace:` uses the pnpm publish-time
271
+ * projection, and the alias form `workspace:<name>@<range>` resolves the
272
+ * TARGET package's version and becomes the published `npm:<name>@<range>`
273
+ * alias. Specifiers the resolvers answer `Option.none()` for are left
274
+ * unchanged — resolution still succeeds. Fails with `@effected/npm`'s
275
+ * `CatalogAssemblyError` when catalog assembly failed, or
276
+ * `DependencyResolutionError` when a resolver's mechanism failed; requires
277
+ * `CatalogResolver` and `WorkspaceResolver` in `R`. For fail-typed
277
278
  * resolution over the tolerant model, see `@effected/npm`'s
278
279
  * `Manifest#resolve`.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * import { Default } from "@effected/npm";
284
+ * import { Package } from "@effected/package-json";
285
+ * import { Effect } from "effect";
286
+ *
287
+ * const program = Effect.gen(function* () {
288
+ * const pkg = yield* Package.decode({
289
+ * name: "my-pkg",
290
+ * version: "1.0.0",
291
+ * dependencies: { effect: "catalog:" },
292
+ * });
293
+ * return yield* Package.resolve(pkg);
294
+ * }).pipe(Effect.provide(Default)); // the no-op resolvers leave `catalog:` unchanged
295
+ * ```
296
+ *
297
+ * @param pkg - the package whose specifiers to resolve
279
298
  */
280
299
  static resolve = Effect.fn("Package.resolve")(function* (pkg) {
281
300
  const workspace = yield* WorkspaceResolver;
@@ -66,9 +66,10 @@ var PackageJsonWriteError = class extends Schema.TaggedError()("PackageJsonWrite
66
66
  }
67
67
  };
68
68
  /**
69
- * Reads and writes package.json over core `FileSystem` / `Path`. The layer
70
- * requires those services; provide `@effect/platform-node`'s `NodeFileSystem` /
71
- * `NodePath` (or a bun equivalent) at the application boundary.
69
+ * Reads, writes and surgically edits package.json files over core `FileSystem`
70
+ * / `Path`. The layer requires those services; provide
71
+ * `@effect/platform-node`'s `NodeFileSystem` / `NodePath` (or a bun equivalent)
72
+ * at the application boundary.
72
73
  *
73
74
  * @example
74
75
  * ```ts
package/PackageManager.js CHANGED
@@ -27,9 +27,8 @@ const invalid = (input, message) => Effect.fail(new SchemaIssue.InvalidValue({ m
27
27
  * - Corepack 0.34.0 (`specUtils.ts`, `parseSpec`) recognises **three** names —
28
28
  * `npm`, `pnpm`, `yarn` — and throws an "unsupported package manager
29
29
  * specification" usage error for any other. Adopting that set here would reject
30
- * `bun@1.2.20`, which is real: six published packages in this repo's own
31
- * `node_modules` carry exactly that value, and a manifest model that cannot
32
- * read them is useless for the job it has.
30
+ * `bun@1.2.20`, which real published manifests carry, and a manifest model
31
+ * that cannot read them is useless for the job it has.
33
32
  * - Corepack does not treat the set as closed either. `parseSpec` skips the
34
33
  * name check entirely when the spec is a URL, so a custom name is reachable
35
34
  * in corepack's own grammar (behind `COREPACK_ENABLE_UNSAFE_CUSTOM_URLS`).
@@ -42,6 +41,17 @@ const invalid = (input, message) => Effect.fail(new SchemaIssue.InvalidValue({ m
42
41
  * and simply will not be installable through the pin — which is the honest
43
42
  * relationship between a document model and a provisioning contract.
44
43
  *
44
+ * @example
45
+ * ```ts
46
+ * import { PackageManager } from "@effected/package-json";
47
+ * import { Schema } from "effect";
48
+ *
49
+ * const pm = Schema.decodeUnknownSync(PackageManager.FromString)("pnpm@10.33.0+sha512.abc");
50
+ * pm.name; // => "pnpm"
51
+ * pm.version; // => "10.33.0"
52
+ * pm.hasIntegrity; // => true
53
+ * ```
54
+ *
45
55
  * @public
46
56
  */
47
57
  var PackageManager = class PackageManager extends Schema.Class("PackageManager")({
@@ -54,8 +64,7 @@ var PackageManager = class PackageManager extends Schema.Class("PackageManager")
54
64
  * allowed (`10.0.0-rc.1`); ranges, partial versions, dist-tags,
55
65
  * leading-zero components and padded values are not, and a version
56
66
  * carrying build metadata is rejected at construction because the grammar
57
- * cannot express it. The shared schema is consumed by identity, not
58
- * copied — the suite asserts `fields.version === SemVer.PinnableVersionString`.
67
+ * cannot express it.
59
68
  */
60
69
  version: SemVer.PinnableVersionString,
61
70
  /**
@@ -198,8 +198,7 @@ var PackageManagerRange = class PackageManagerRange extends Schema.Class("Packag
198
198
  * @remarks
199
199
  * The entry's `name` is the package-manager name and its `version` is the
200
200
  * same `<range>[+<integrity>]` tail the `packageManager` field carries after
201
- * its `@` — `^12.6.0`, `12.6.0`, or `12.6.0+sha512.<hex>` as
202
- * `pnpm self-update` historically wrote it. Both are validated exactly as
201
+ * its `@` — `^12.6.0`, `12.6.0`, or `12.6.0+sha512.<hex>`. Both are validated exactly as
203
202
  * {@link PackageManagerRange.parseResult} validates them. The `version`
204
203
  * slot is optional on a {@link DevEngine}, but an entry without one names
205
204
  * no range, so it fails with `reason: "range"`; `onFail` is ignored. The
@@ -40,11 +40,11 @@ import { SemVer } from "@effected/semver";
40
40
  * @example
41
41
  * ```ts
42
42
  * import { PackageManifest } from "@effected/package-json";
43
- * import { Effect, Option } from "effect";
43
+ * import { Effect } from "effect";
44
44
  *
45
45
  * const program = Effect.gen(function* () {
46
46
  * const root = yield* PackageManifest.decode({ private: true, packageManager: "pnpm@^11.20.0" });
47
- * console.log(root.isPrivate, root.packageManager?.isExact); // true false
47
+ * console.log(root.isPrivate); // => true
48
48
  * });
49
49
  * ```
50
50
  *
@@ -67,9 +67,8 @@ var PackageManifest = class PackageManifest extends Schema.Class("PackageManifes
67
67
  * any `SchemaError` to a typed {@link PackageDecodeError} at the boundary.
68
68
  *
69
69
  * @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
70
- * @returns an Effect resolving to the decoded `PackageManifest`
71
- * @throws (typed) `PackageDecodeError` when a present field does not satisfy
72
- * its codec
70
+ * @returns an Effect resolving to the decoded `PackageManifest`, failing with
71
+ * {@link PackageDecodeError} when a present field does not satisfy its codec
73
72
  */
74
73
  static decode = Effect.fn("PackageManifest.decode")(function* (input) {
75
74
  return yield* Schema.decodeUnknownEffect(PackageManifest.schema)(input).pipe(Effect.catchTag("SchemaError", (cause) => new PackageDecodeError({ cause })));
package/PackageName.js CHANGED
@@ -45,9 +45,19 @@ const unscoped = (name) => {
45
45
  const isScoped = (name) => name.startsWith("@");
46
46
  /**
47
47
  * The union of `ScopedPackageName` and `UnscopedPackageName`,
48
- * carrying the classification statics (`PackageName.isValid` and friends)
49
- * that absorb the v3 floating `PackageNameUtil` object. Use it as the schema
50
- * for a package-name field and reach for the statics to inspect a raw string.
48
+ * carrying the classification statics (`PackageName.isValid`,
49
+ * `PackageName.scope`, `PackageName.unscoped`, `PackageName.isScoped`). Use it as
50
+ * the schema for a package-name field and reach for the statics to inspect a raw
51
+ * string.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { PackageName } from "@effected/package-json";
56
+ *
57
+ * PackageName.isValid("@effected/semver"); // => true
58
+ * PackageName.scope("@effected/semver"); // => Option.some("effected")
59
+ * PackageName.unscoped("@effected/semver"); // => "semver"
60
+ * ```
51
61
  *
52
62
  * @public
53
63
  */
@@ -112,7 +112,9 @@ const runRules = Effect.fn("PackageValidator.validate")(function* (pkg, rules) {
112
112
  });
113
113
  /**
114
114
  * Validates a {@link Package} against a set of {@link ValidationRule}s,
115
- * aggregating every failure into one {@link PackageValidationError}.
115
+ * aggregating every failure into one {@link PackageValidationError}. Use
116
+ * `PackageValidator.layer` for {@link defaultRules} or `PackageValidator.layerRules`
117
+ * for a custom rule set.
116
118
  *
117
119
  * @example
118
120
  * ```ts
@@ -124,6 +126,8 @@ const runRules = Effect.fn("PackageValidator.validate")(function* (pkg, rules) {
124
126
  * const validator = yield* PackageValidator;
125
127
  * yield* validator.validate(pkg);
126
128
  * }).pipe(Effect.provide(PackageValidator.layer));
129
+ * // fails with PackageValidationError: the default rules require a license,
130
+ * // a description and a repository, and forbid `private: true`
127
131
  * ```
128
132
  *
129
133
  * @public
package/Person.js CHANGED
@@ -66,6 +66,17 @@ const encodePersonObject = (person) => {
66
66
  * A structured person object with `name`, optional `email` / `url`, and a
67
67
  * `rest` catch-all preserving any additional keys across a read/write cycle.
68
68
  *
69
+ * @example
70
+ * ```ts
71
+ * import { Person } from "@effected/package-json";
72
+ * import { Schema } from "effect";
73
+ *
74
+ * const ann = Schema.decodeUnknownSync(Person.FromValue)("Ann <ann@example.com> (https://example.com)");
75
+ * ann.name; // => "Ann"
76
+ * ann.email; // => "ann@example.com"
77
+ * Schema.encodeSync(Person.FromValue)(ann); // => "Ann <ann@example.com> (https://example.com)"
78
+ * ```
79
+ *
69
80
  * @public
70
81
  */
71
82
  var Person = class Person extends Schema.Class("Person")({
package/README.md CHANGED
@@ -7,10 +7,10 @@
7
7
 
8
8
  package.json parsing, editing, validation and file IO as Effect schemas. `Package` is a `Schema.Class` with the manifest's known fields typed — `name` is a branded npm name, `version` is a real `SemVer`, `packageManager` decodes into `{ name, version, integrity }` — and a `rest` catch-all that carries every unknown top-level key through a read, edit and write cycle without losing it. Editing is immutable and dual-signature, validation is a rule set you can replace, and `catalog:` / `workspace:` specifiers expand through the `@effected/npm` resolver contracts as an explicit step you opt into.
9
9
 
10
- > **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
11
- > development against a single pinned Effect v4 prerelease. Packages graduate to
12
- > `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
13
- > exactly the ones the kit is built and tested against, install
10
+ > **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
11
+ > Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
12
+ > makes a kit `1.0.0` possible, not automatic. To keep your `effect` and
13
+ > `@effect/*` versions on the line the kit is built and tested against, install
14
14
  > [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
15
15
  >
16
16
  > **Stability: unstable.** This package's API surface is not yet considered
package/Repository.js CHANGED
@@ -28,8 +28,8 @@ const stripGitSuffix = (value) => value.endsWith(".git") ? value.slice(0, -4) :
28
28
  * The browsable `https://host/path` form of a repository reference, or none
29
29
  * when the value is not one this model recognizes.
30
30
  *
31
- * Total by construction: `repository` is caller data, and a value we cannot
32
- * interpret is a missing answer rather than a failure.
31
+ * Total by construction: `repository` is caller data, and a value that cannot
32
+ * be interpreted is a missing answer rather than a failure.
33
33
  */
34
34
  const browseUrlOf = (raw) => {
35
35
  const url = raw.trim();
@@ -102,8 +102,13 @@ const isFaithfulBugs = (wire, bugs) => wire.url === bugs.url && wire.email === b
102
102
  *
103
103
  * @example
104
104
  * ```ts
105
- * // "effected/kit" → https://github.com/effected/kit
106
- * // "git@github.com:effected/kit.git" → https://github.com/effected/kit
105
+ * import { Repository } from "@effected/package-json";
106
+ * import { Schema } from "effect";
107
+ *
108
+ * const repo = Schema.decodeUnknownSync(Repository.FromValue)("effected/kit");
109
+ * repo.url; // => "effected/kit"
110
+ * repo.browseUrl; // => Option.some("https://github.com/effected/kit")
111
+ * // "git@github.com:effected/kit.git" browses to the same URL
107
112
  * ```
108
113
  *
109
114
  * @public
@@ -166,8 +171,15 @@ var Repository = class Repository extends Schema.Class("Repository")({
166
171
  *
167
172
  * @example
168
173
  * ```ts
169
- * // { url: "effected/kit", directory: "packages/spdx" }
170
- * // => https://github.com/effected/kit/tree/HEAD/packages/spdx
174
+ * import { Repository } from "@effected/package-json";
175
+ * import { Schema } from "effect";
176
+ *
177
+ * const repo = Schema.decodeUnknownSync(Repository.FromValue)({
178
+ * url: "effected/kit",
179
+ * directory: "packages/spdx",
180
+ * });
181
+ * repo.directoryUrl;
182
+ * // => Option.some("https://github.com/effected/kit/tree/HEAD/packages/spdx")
171
183
  * ```
172
184
  */
173
185
  get directoryUrl() {
package/index.d.ts CHANGED
@@ -144,9 +144,8 @@ declare const UnresolvedEntryPointError_base: Schema.Class<UnresolvedEntryPointE
144
144
  *
145
145
  * @remarks
146
146
  * The reason is discriminated rather than a bare "not found" because the three
147
- * shapes call for different responses, and a caller staring at a consumer's
148
- * plugin at 3am needs to know which one it hit. Collapsing them into one
149
- * sentinel is the same class of quiet wrong answer as an untyped error channel.
147
+ * shapes call for different responses, and a caller needs to know which one it
148
+ * hit.
150
149
  *
151
150
  * @public
152
151
  */
@@ -165,18 +164,19 @@ export declare class UnresolvedEntryPointError extends UnresolvedEntryPointError
165
164
  * @public
166
165
  */
167
166
  interface EntryPointManifest {
167
+ /** The `exports` field, in any of its legal shapes. */
168
168
  readonly exports?: unknown;
169
+ /** The `main` field; consulted only when `exports` is absent. */
169
170
  readonly main?: unknown;
170
171
  }
171
172
  /**
172
173
  * Resolve a package's root entry point from its manifest.
173
174
  *
174
175
  * @remarks
175
- * The half of "read something out of a published package" that has no home
176
- * anywhere else: given a manifest, which file is the package's `"."` entry?
177
- * It is pure and IO-free by design — nothing here touches a filesystem, so it
178
- * is testable against plain manifest objects with no package on disk, and it
179
- * composes with a directory that arrived by any route.
176
+ * Answers "given a manifest, which file is the package's `"."` entry?". It is
177
+ * pure and IO-free by design — nothing here touches a filesystem, so it works
178
+ * on plain manifest objects with no package on disk, and it composes with a
179
+ * directory that arrived by any route.
180
180
  *
181
181
  * All three legal `exports` spellings are honoured, because all three appear in
182
182
  * real published packages:
@@ -203,7 +203,6 @@ interface EntryPointManifest {
203
203
  * @example
204
204
  * ```ts
205
205
  * import { resolveEntryPoint } from "@effected/package-json";
206
- * import { Result, Schema } from "effect";
207
206
  *
208
207
  * resolveEntryPoint({ exports: { import: "./esm.js", require: "./cjs.js" } });
209
208
  * // Result.succeed("./esm.js")
@@ -345,9 +344,8 @@ export type SpdxLicense = string & Brand.Brand<"SpdxLicense">;
345
344
  * @remarks
346
345
  * This is the accessor to reach for whenever a branded `SpdxLicense` has
347
346
  * to become an actual expression — a license URL, a badge, structured data, a
348
- * policy check. It exists because the brand and the grammar disagree, and that
349
- * disagreement is knowledge these two packages jointly own rather than
350
- * something each consumer should rediscover.
347
+ * policy check. It exists because the brand and the grammar disagree, so no
348
+ * consumer has to rediscover that disagreement.
351
349
  *
352
350
  * `UNLICENSED` and `SEE LICENSE IN <file>` are legal in a manifest and are not
353
351
  * SPDX expressions, so they yield `Option.none()`. Everything else parses.
@@ -358,12 +356,14 @@ export type SpdxLicense = string & Brand.Brand<"SpdxLicense">;
358
356
  *
359
357
  * @example
360
358
  * ```ts
361
- * import { licenseExpressionOf } from "@effected/package-json";
362
- * import { SpdxExpression } from "@effected/spdx";
359
+ * import { SpdxLicense, licenseExpressionOf } from "@effected/package-json";
360
+ * import { Option, Schema } from "effect";
363
361
  *
364
- * // "MIT" => Option.some(<LicenseNode MIT>)
365
- * // "UNLICENSED" => Option.none()
366
- * // "SEE LICENSE IN LICENSE.txt" => Option.none()
362
+ * const decode = Schema.decodeUnknownSync(SpdxLicense);
363
+ *
364
+ * Option.isSome(licenseExpressionOf(decode("MIT"))); // => true
365
+ * Option.isSome(licenseExpressionOf(decode("UNLICENSED"))); // => false
366
+ * Option.isSome(licenseExpressionOf(decode("SEE LICENSE IN LICENSE.txt"))); // => false
367
367
  * ```
368
368
  *
369
369
  * @param license - a branded manifest license value
@@ -384,8 +384,7 @@ declare const PackageManager_base: Schema.Class<PackageManager, Schema.Struct<{
384
384
  * allowed (`10.0.0-rc.1`); ranges, partial versions, dist-tags,
385
385
  * leading-zero components and padded values are not, and a version
386
386
  * carrying build metadata is rejected at construction because the grammar
387
- * cannot express it. The shared schema is consumed by identity, not
388
- * copied — the suite asserts `fields.version === SemVer.PinnableVersionString`.
387
+ * cannot express it.
389
388
  */
390
389
  readonly version: Schema.String;
391
390
  /**
@@ -420,9 +419,8 @@ declare const PackageManager_base: Schema.Class<PackageManager, Schema.Struct<{
420
419
  * - Corepack 0.34.0 (`specUtils.ts`, `parseSpec`) recognises **three** names —
421
420
  * `npm`, `pnpm`, `yarn` — and throws an "unsupported package manager
422
421
  * specification" usage error for any other. Adopting that set here would reject
423
- * `bun@1.2.20`, which is real: six published packages in this repo's own
424
- * `node_modules` carry exactly that value, and a manifest model that cannot
425
- * read them is useless for the job it has.
422
+ * `bun@1.2.20`, which real published manifests carry, and a manifest model
423
+ * that cannot read them is useless for the job it has.
426
424
  * - Corepack does not treat the set as closed either. `parseSpec` skips the
427
425
  * name check entirely when the spec is a URL, so a custom name is reachable
428
426
  * in corepack's own grammar (behind `COREPACK_ENABLE_UNSAFE_CUSTOM_URLS`).
@@ -435,6 +433,17 @@ declare const PackageManager_base: Schema.Class<PackageManager, Schema.Struct<{
435
433
  * and simply will not be installable through the pin — which is the honest
436
434
  * relationship between a document model and a provisioning contract.
437
435
  *
436
+ * @example
437
+ * ```ts
438
+ * import { PackageManager } from "@effected/package-json";
439
+ * import { Schema } from "effect";
440
+ *
441
+ * const pm = Schema.decodeUnknownSync(PackageManager.FromString)("pnpm@10.33.0+sha512.abc");
442
+ * pm.name; // => "pnpm"
443
+ * pm.version; // => "10.33.0"
444
+ * pm.hasIntegrity; // => true
445
+ * ```
446
+ *
438
447
  * @public
439
448
  */
440
449
  export declare class PackageManager extends PackageManager_base {
@@ -504,9 +513,19 @@ export type UnscopedPackageName = string & Brand.Brand<"UnscopedPackageName">;
504
513
  export type PackageName = ScopedPackageName | UnscopedPackageName;
505
514
  /**
506
515
  * The union of `ScopedPackageName` and `UnscopedPackageName`,
507
- * carrying the classification statics (`PackageName.isValid` and friends)
508
- * that absorb the v3 floating `PackageNameUtil` object. Use it as the schema
509
- * for a package-name field and reach for the statics to inspect a raw string.
516
+ * carrying the classification statics (`PackageName.isValid`,
517
+ * `PackageName.scope`, `PackageName.unscoped`, `PackageName.isScoped`). Use it as
518
+ * the schema for a package-name field and reach for the statics to inspect a raw
519
+ * string.
520
+ *
521
+ * @example
522
+ * ```ts
523
+ * import { PackageName } from "@effected/package-json";
524
+ *
525
+ * PackageName.isValid("@effected/semver"); // => true
526
+ * PackageName.scope("@effected/semver"); // => Option.some("effected")
527
+ * PackageName.unscoped("@effected/semver"); // => "semver"
528
+ * ```
510
529
  *
511
530
  * @public
512
531
  */
@@ -532,6 +551,17 @@ declare const Person_base: Schema.Class<Person, Schema.Struct<{
532
551
  * A structured person object with `name`, optional `email` / `url`, and a
533
552
  * `rest` catch-all preserving any additional keys across a read/write cycle.
534
553
  *
554
+ * @example
555
+ * ```ts
556
+ * import { Person } from "@effected/package-json";
557
+ * import { Schema } from "effect";
558
+ *
559
+ * const ann = Schema.decodeUnknownSync(Person.FromValue)("Ann <ann@example.com> (https://example.com)");
560
+ * ann.name; // => "Ann"
561
+ * ann.email; // => "ann@example.com"
562
+ * Schema.encodeSync(Person.FromValue)(ann); // => "Ann <ann@example.com> (https://example.com)"
563
+ * ```
564
+ *
535
565
  * @public
536
566
  */
537
567
  export declare class Person extends Person_base {
@@ -603,8 +633,13 @@ declare const Repository_base: Schema.Class<Repository, Schema.Struct<{
603
633
  *
604
634
  * @example
605
635
  * ```ts
606
- * // "effected/kit" → https://github.com/effected/kit
607
- * // "git@github.com:effected/kit.git" → https://github.com/effected/kit
636
+ * import { Repository } from "@effected/package-json";
637
+ * import { Schema } from "effect";
638
+ *
639
+ * const repo = Schema.decodeUnknownSync(Repository.FromValue)("effected/kit");
640
+ * repo.url; // => "effected/kit"
641
+ * repo.browseUrl; // => Option.some("https://github.com/effected/kit")
642
+ * // "git@github.com:effected/kit.git" browses to the same URL
608
643
  * ```
609
644
  *
610
645
  * @public
@@ -654,8 +689,15 @@ export declare class Repository extends Repository_base {
654
689
  *
655
690
  * @example
656
691
  * ```ts
657
- * // { url: "effected/kit", directory: "packages/spdx" }
658
- * // => https://github.com/effected/kit/tree/HEAD/packages/spdx
692
+ * import { Repository } from "@effected/package-json";
693
+ * import { Schema } from "effect";
694
+ *
695
+ * const repo = Schema.decodeUnknownSync(Repository.FromValue)({
696
+ * url: "effected/kit",
697
+ * directory: "packages/spdx",
698
+ * });
699
+ * repo.directoryUrl;
700
+ * // => Option.some("https://github.com/effected/kit/tree/HEAD/packages/spdx")
659
701
  * ```
660
702
  */
661
703
  get directoryUrl(): Option.Option<string>;
@@ -745,8 +787,8 @@ export declare const PeerDependenciesMetaField: Schema.$Record<Schema.String, Sc
745
787
  *
746
788
  * @deprecated Superseded by {@link Repository.FromValue}, which decodes both
747
789
  * encodings into a typed {@link Repository} with normalization getters and
748
- * round-trips the original form. Kept as a named type for consumers that were
749
- * matching on the raw union; it is no longer what `Package.repository` uses.
790
+ * round-trips the original form. Retained only as a name for the raw union;
791
+ * `Package.repository` is a {@link Repository}.
750
792
  *
751
793
  * @public
752
794
  */
@@ -912,8 +954,8 @@ export declare class Package extends Package_base {
912
954
  * `SchemaError` to a typed {@link PackageDecodeError} at the boundary.
913
955
  *
914
956
  * @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
915
- * @returns an Effect resolving to the decoded `Package`
916
- * @throws (typed) `PackageDecodeError` when `input` does not satisfy the schema
957
+ * @returns an Effect resolving to the decoded `Package`, failing with
958
+ * {@link PackageDecodeError} when `input` does not satisfy the schema
917
959
  */
918
960
  static readonly decode: (input: unknown) => Effect.Effect<Package, PackageDecodeError, never>;
919
961
  /** Whether the package is marked private. */
@@ -1002,20 +1044,39 @@ export declare class Package extends Package_base {
1002
1044
  /**
1003
1045
  * Resolve `catalog:` and `workspace:` specifiers across all four dependency
1004
1046
  * maps using the `CatalogResolver` and `WorkspaceResolver` from context,
1005
- * classifying and projecting through `@effected/npm`'s `DependencySpecifier`
1006
- * statics (`workspace:` uses the pnpm publish-time projection; the alias
1007
- * form `workspace:<name>@<range>` resolves the TARGET package's version and
1008
- * becomes the published `npm:<name>@<range>` alias). Specifiers the
1009
- * resolvers return `None` for are left unchanged — resolution still
1010
- * succeeds. A `CatalogResolver` whose catalog assembly failed surfaces
1011
- * typed as `@effected/npm`'s `CatalogAssemblyError`, alongside the
1012
- * contracts' `DependencyResolutionError`. This is the explicit resolution
1013
- * step — `PackageJsonFile.write` never resolves.
1047
+ * returning a new `Package`. This is the explicit resolution step —
1048
+ * `PackageJsonFile.write` never resolves.
1014
1049
  *
1015
1050
  * @remarks
1016
- * Leaves unresolvable specifiers unchanged. For fail-typed manifest
1051
+ * Classification and projection go through `@effected/npm`'s
1052
+ * `DependencySpecifier` statics: `workspace:` uses the pnpm publish-time
1053
+ * projection, and the alias form `workspace:<name>@<range>` resolves the
1054
+ * TARGET package's version and becomes the published `npm:<name>@<range>`
1055
+ * alias. Specifiers the resolvers answer `Option.none()` for are left
1056
+ * unchanged — resolution still succeeds. Fails with `@effected/npm`'s
1057
+ * `CatalogAssemblyError` when catalog assembly failed, or
1058
+ * `DependencyResolutionError` when a resolver's mechanism failed; requires
1059
+ * `CatalogResolver` and `WorkspaceResolver` in `R`. For fail-typed
1017
1060
  * resolution over the tolerant model, see `@effected/npm`'s
1018
1061
  * `Manifest#resolve`.
1062
+ *
1063
+ * @example
1064
+ * ```ts
1065
+ * import { Default } from "@effected/npm";
1066
+ * import { Package } from "@effected/package-json";
1067
+ * import { Effect } from "effect";
1068
+ *
1069
+ * const program = Effect.gen(function* () {
1070
+ * const pkg = yield* Package.decode({
1071
+ * name: "my-pkg",
1072
+ * version: "1.0.0",
1073
+ * dependencies: { effect: "catalog:" },
1074
+ * });
1075
+ * return yield* Package.resolve(pkg);
1076
+ * }).pipe(Effect.provide(Default)); // the no-op resolvers leave `catalog:` unchanged
1077
+ * ```
1078
+ *
1079
+ * @param pkg - the package whose specifiers to resolve
1019
1080
  */
1020
1081
  static readonly resolve: (pkg: Package) => Effect.Effect<Package, import("@effected/npm").CatalogAssemblyError | import("@effected/npm").DependencyResolutionError, CatalogResolver | WorkspaceResolver>;
1021
1082
  /**
@@ -1363,8 +1424,8 @@ export declare class LenientManifest extends LenientManifest_base {
1363
1424
  * {@link LenientManifest.decodeResult}, adding the tracing span.
1364
1425
  *
1365
1426
  * @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
1366
- * @returns an Effect resolving to the decoded {@link LenientManifest}
1367
- * @throws (typed) `PackageDecodeError` when `input` is not a JSON object
1427
+ * @returns an Effect resolving to the decoded {@link LenientManifest},
1428
+ * failing with {@link PackageDecodeError} when `input` is not a JSON object
1368
1429
  */
1369
1430
  static readonly decode: (input: unknown) => Effect.Effect<LenientManifest, PackageDecodeError, never>;
1370
1431
  /**
@@ -1383,8 +1444,8 @@ export declare class LenientManifest extends LenientManifest_base {
1383
1444
  * {@link LenientManifest.parseResult}, adding the tracing span.
1384
1445
  *
1385
1446
  * @param text - the package.json source text
1386
- * @returns an Effect resolving to the decoded {@link LenientManifest}
1387
- * @throws (typed) `PackageJsonSyntaxError` when the text is not valid JSON
1447
+ * @returns an Effect resolving to the decoded {@link LenientManifest},
1448
+ * failing with {@link PackageJsonSyntaxError} when the text is not valid JSON
1388
1449
  * or is not a JSON object
1389
1450
  */
1390
1451
  static readonly parse: (text: string) => Effect.Effect<LenientManifest, PackageJsonSyntaxError, never>;
@@ -1404,7 +1465,9 @@ export declare class LenientManifest extends LenientManifest_base {
1404
1465
  * @public
1405
1466
  */
1406
1467
  interface DevEnginePackageManagerEntry {
1468
+ /** The package-manager name (e.g. `pnpm`). */
1407
1469
  readonly name: string;
1470
+ /** The `<range>[+<integrity>]` version constraint; an entry without one names no range. */
1408
1471
  readonly version?: string | undefined;
1409
1472
  }
1410
1473
  declare const InvalidPackageManagerRangeError_base: Schema.Class<InvalidPackageManagerRangeError, Schema.TaggedStruct<"InvalidPackageManagerRangeError", {
@@ -1542,8 +1605,7 @@ export declare class PackageManagerRange extends PackageManagerRange_base {
1542
1605
  * @remarks
1543
1606
  * The entry's `name` is the package-manager name and its `version` is the
1544
1607
  * same `<range>[+<integrity>]` tail the `packageManager` field carries after
1545
- * its `@` — `^12.6.0`, `12.6.0`, or `12.6.0+sha512.<hex>` as
1546
- * `pnpm self-update` historically wrote it. Both are validated exactly as
1608
+ * its `@` — `^12.6.0`, `12.6.0`, or `12.6.0+sha512.<hex>`. Both are validated exactly as
1547
1609
  * {@link PackageManagerRange.parseResult} validates them. The `version`
1548
1610
  * slot is optional on a {@link DevEngine}, but an entry without one names
1549
1611
  * no range, so it fails with `reason: "range"`; `onFail` is ignored. The
@@ -1725,11 +1787,11 @@ declare const PackageManifest_base: Schema.Class<PackageManifest, Schema.Struct<
1725
1787
  * @example
1726
1788
  * ```ts
1727
1789
  * import { PackageManifest } from "@effected/package-json";
1728
- * import { Effect, Option } from "effect";
1790
+ * import { Effect } from "effect";
1729
1791
  *
1730
1792
  * const program = Effect.gen(function* () {
1731
1793
  * const root = yield* PackageManifest.decode({ private: true, packageManager: "pnpm@^11.20.0" });
1732
- * console.log(root.isPrivate, root.packageManager?.isExact); // true false
1794
+ * console.log(root.isPrivate); // => true
1733
1795
  * });
1734
1796
  * ```
1735
1797
  *
@@ -1749,9 +1811,8 @@ export declare class PackageManifest extends PackageManifest_base {
1749
1811
  * any `SchemaError` to a typed {@link PackageDecodeError} at the boundary.
1750
1812
  *
1751
1813
  * @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
1752
- * @returns an Effect resolving to the decoded `PackageManifest`
1753
- * @throws (typed) `PackageDecodeError` when a present field does not satisfy
1754
- * its codec
1814
+ * @returns an Effect resolving to the decoded `PackageManifest`, failing with
1815
+ * {@link PackageDecodeError} when a present field does not satisfy its codec
1755
1816
  */
1756
1817
  static readonly decode: (input: unknown) => Effect.Effect<PackageManifest, PackageDecodeError, never>;
1757
1818
  /** Whether the manifest is marked private. */
@@ -1891,9 +1952,10 @@ interface PackageJsonFileShape {
1891
1952
  }
1892
1953
  declare const PackageJsonFile_base: Context.ServiceClass<PackageJsonFile, "@effected/package-json/PackageJsonFile", PackageJsonFileShape>;
1893
1954
  /**
1894
- * Reads and writes package.json over core `FileSystem` / `Path`. The layer
1895
- * requires those services; provide `@effect/platform-node`'s `NodeFileSystem` /
1896
- * `NodePath` (or a bun equivalent) at the application boundary.
1955
+ * Reads, writes and surgically edits package.json files over core `FileSystem`
1956
+ * / `Path`. The layer requires those services; provide
1957
+ * `@effect/platform-node`'s `NodeFileSystem` / `NodePath` (or a bun equivalent)
1958
+ * at the application boundary.
1897
1959
  *
1898
1960
  * @example
1899
1961
  * ```ts
@@ -1989,7 +2051,9 @@ declare const PackageValidator_base: Context.ServiceClass<PackageValidator, "@ef
1989
2051
  }>;
1990
2052
  /**
1991
2053
  * Validates a {@link Package} against a set of {@link ValidationRule}s,
1992
- * aggregating every failure into one {@link PackageValidationError}.
2054
+ * aggregating every failure into one {@link PackageValidationError}. Use
2055
+ * `PackageValidator.layer` for {@link defaultRules} or `PackageValidator.layerRules`
2056
+ * for a custom rule set.
1993
2057
  *
1994
2058
  * @example
1995
2059
  * ```ts
@@ -2001,6 +2065,8 @@ declare const PackageValidator_base: Context.ServiceClass<PackageValidator, "@ef
2001
2065
  * const validator = yield* PackageValidator;
2002
2066
  * yield* validator.validate(pkg);
2003
2067
  * }).pipe(Effect.provide(PackageValidator.layer));
2068
+ * // fails with PackageValidationError: the default rules require a license,
2069
+ * // a description and a repository, and forbid `private: true`
2004
2070
  * ```
2005
2071
  *
2006
2072
  * @public
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/package-json",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "private": false,
5
5
  "description": "package.json parsing, editing, validation and file IO as Effect schemas.",
6
6
  "keywords": [
@@ -38,13 +38,13 @@
38
38
  "./package.json": "./package.json"
39
39
  },
40
40
  "dependencies": {
41
- "@effected/jsonc": "^0.14.0",
42
- "@effected/npm": "^0.19.0",
43
- "@effected/semver": "^0.10.1",
44
- "@effected/spdx": "^0.10.0"
41
+ "@effected/jsonc": "^0.15.0",
42
+ "@effected/npm": "^0.20.0",
43
+ "@effected/semver": "^0.11.0",
44
+ "@effected/spdx": "^0.11.0"
45
45
  },
46
46
  "peerDependencies": {
47
- "effect": "4.0.0-rc.118"
47
+ "effect": "^4.0.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24.11.0"