@effected/package-json 0.11.0 → 0.12.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/LenientManifest.js +256 -0
- package/Package.js +1 -1
- package/PackageManifest.js +6 -4
- package/README.md +27 -0
- package/index.d.ts +163 -5
- package/index.js +2 -1
- package/package.json +2 -2
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { ExportsField, PackageDecodeError, PublishConfigField } from "./Package.js";
|
|
2
|
+
import { PackageJsonSyntaxError } from "./PackageJsonFormat.js";
|
|
3
|
+
import { Cause, Effect, Exit, Result, Schema } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/LenientManifest.ts
|
|
6
|
+
const LenientFieldIssueSchema = Schema.Struct({
|
|
7
|
+
field: Schema.String,
|
|
8
|
+
expected: Schema.String,
|
|
9
|
+
value: Schema.Unknown
|
|
10
|
+
});
|
|
11
|
+
const StringRecord = Schema.Record(Schema.String, Schema.String);
|
|
12
|
+
const UnknownRecord = Schema.Record(Schema.String, Schema.Unknown);
|
|
13
|
+
const StringOrRecord = Schema.Union([Schema.String, UnknownRecord]);
|
|
14
|
+
const isString = (value) => typeof value === "string";
|
|
15
|
+
const isBoolean = (value) => typeof value === "boolean";
|
|
16
|
+
const isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
const isStringRecord = (value) => isPlainRecord(value) && Object.values(value).every(isString);
|
|
18
|
+
const isStringArray = (value) => Array.isArray(value) && value.every(isString);
|
|
19
|
+
const isStringOrRecord = (value) => isString(value) || isPlainRecord(value);
|
|
20
|
+
const isStringOrRecordArray = (value) => Array.isArray(value) && value.every(isStringOrRecord);
|
|
21
|
+
const stringGuard = {
|
|
22
|
+
expected: "a string",
|
|
23
|
+
test: isString
|
|
24
|
+
};
|
|
25
|
+
const stringRecordGuard = {
|
|
26
|
+
expected: "an object of string values",
|
|
27
|
+
test: isStringRecord
|
|
28
|
+
};
|
|
29
|
+
const stringOrRecordGuard = {
|
|
30
|
+
expected: "a string or an object",
|
|
31
|
+
test: isStringOrRecord
|
|
32
|
+
};
|
|
33
|
+
const recordGuard = {
|
|
34
|
+
expected: "an object",
|
|
35
|
+
test: isPlainRecord
|
|
36
|
+
};
|
|
37
|
+
const FIELD_GUARDS = /* @__PURE__ */ new Map([
|
|
38
|
+
["name", stringGuard],
|
|
39
|
+
["version", stringGuard],
|
|
40
|
+
["description", stringGuard],
|
|
41
|
+
["private", {
|
|
42
|
+
expected: "a boolean",
|
|
43
|
+
test: isBoolean
|
|
44
|
+
}],
|
|
45
|
+
["type", stringGuard],
|
|
46
|
+
["main", stringGuard],
|
|
47
|
+
["license", stringGuard],
|
|
48
|
+
["author", stringOrRecordGuard],
|
|
49
|
+
["contributors", {
|
|
50
|
+
expected: "an array of strings or objects",
|
|
51
|
+
test: isStringOrRecordArray
|
|
52
|
+
}],
|
|
53
|
+
["maintainers", {
|
|
54
|
+
expected: "an array of strings or objects",
|
|
55
|
+
test: isStringOrRecordArray
|
|
56
|
+
}],
|
|
57
|
+
["keywords", {
|
|
58
|
+
expected: "an array of strings",
|
|
59
|
+
test: isStringArray
|
|
60
|
+
}],
|
|
61
|
+
["repository", stringOrRecordGuard],
|
|
62
|
+
["bugs", stringOrRecordGuard],
|
|
63
|
+
["homepage", stringGuard],
|
|
64
|
+
["dependencies", stringRecordGuard],
|
|
65
|
+
["devDependencies", stringRecordGuard],
|
|
66
|
+
["peerDependencies", stringRecordGuard],
|
|
67
|
+
["optionalDependencies", stringRecordGuard],
|
|
68
|
+
["peerDependenciesMeta", recordGuard],
|
|
69
|
+
["scripts", stringRecordGuard],
|
|
70
|
+
["bin", {
|
|
71
|
+
expected: "a string or an object of string values",
|
|
72
|
+
test: (v) => isString(v) || isStringRecord(v)
|
|
73
|
+
}],
|
|
74
|
+
["engines", stringRecordGuard],
|
|
75
|
+
["exports", stringOrRecordGuard],
|
|
76
|
+
["publishConfig", recordGuard],
|
|
77
|
+
["packageManager", stringGuard],
|
|
78
|
+
["devEngines", recordGuard]
|
|
79
|
+
]);
|
|
80
|
+
const sift = (raw) => {
|
|
81
|
+
const known = {};
|
|
82
|
+
const rest = Object.create(null);
|
|
83
|
+
const issues = [];
|
|
84
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
85
|
+
const guard = FIELD_GUARDS.get(key);
|
|
86
|
+
if (guard === void 0) rest[key] = value;
|
|
87
|
+
else if (guard.test(value)) known[key] = value;
|
|
88
|
+
else {
|
|
89
|
+
rest[key] = value;
|
|
90
|
+
issues.push({
|
|
91
|
+
field: key,
|
|
92
|
+
expected: guard.expected,
|
|
93
|
+
value
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return LenientManifest.make({
|
|
98
|
+
...known,
|
|
99
|
+
rest,
|
|
100
|
+
issues
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
const decodeRecord = Schema.decodeUnknownExit(UnknownRecord);
|
|
104
|
+
/**
|
|
105
|
+
* The shape-lenient view of a package.json document, for discovery and
|
|
106
|
+
* sniffing — probing a fetched tarball's manifest, walking a `node_modules`
|
|
107
|
+
* tree, listing candidate packages — where the document is other people's
|
|
108
|
+
* data and one malformed field must not fail the read.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* **This is the discovery tier, not a validation bypass.** Every field shares
|
|
112
|
+
* its name with the strict `Package` model, but is typed as its plain permissive JSON
|
|
113
|
+
* shape: `name` and `version` are any string (a legacy uppercase name or a
|
|
114
|
+
* non-semver `"1.0"` is recovered, not rejected), `license` is any string (no
|
|
115
|
+
* SPDX check), the dependency maps and `scripts` are plain string→string
|
|
116
|
+
* records rather than `HashMap`s. A present field that is not even that shape
|
|
117
|
+
* **degrades to absence** rather than failing the document: the raw value is
|
|
118
|
+
* preserved verbatim in `rest` and the degradation is reported on `issues`,
|
|
119
|
+
* so callers can surface what was ignored. Degradation granularity is the
|
|
120
|
+
* top-level field — one junk entry degrades its whole map, with the raw map
|
|
121
|
+
* still in `rest`.
|
|
122
|
+
*
|
|
123
|
+
* Leniency is per-field, never per-syntax: text that is not valid JSON fails
|
|
124
|
+
* {@link LenientManifest.parseResult} as a typed
|
|
125
|
+
* {@link PackageJsonSyntaxError}, and a value that is not a JSON object fails
|
|
126
|
+
* {@link LenientManifest.decodeResult} as a typed {@link PackageDecodeError}.
|
|
127
|
+
*
|
|
128
|
+
* An empty `issues` array does **not** mean the strict tiers would accept the
|
|
129
|
+
* document — the permissive shapes check JSON shape, not npm semantics. The
|
|
130
|
+
* upgrade path is to decode the *original* input through
|
|
131
|
+
* `PackageManifest.decode` (presence-lenient, shape-strict) or
|
|
132
|
+
* `Package.decode` (strict, publishable) when validation is actually
|
|
133
|
+
* wanted. This class deliberately carries no mutation statics and no write
|
|
134
|
+
* path; editing belongs to the strict tiers and to
|
|
135
|
+
* `PackageJsonFormat.modifyToString` / `PackageJsonFile.modify`.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { LenientManifest } from "@effected/package-json";
|
|
140
|
+
* import { Effect } from "effect";
|
|
141
|
+
*
|
|
142
|
+
* const program = Effect.gen(function* () {
|
|
143
|
+
* const sniffed = yield* LenientManifest.decode({ name: "JSONStream", version: "1.0", license: 42 });
|
|
144
|
+
* console.log(sniffed.name, sniffed.version); // "JSONStream" "1.0"
|
|
145
|
+
* console.log(sniffed.issues); // [{ field: "license", expected: "a string", value: 42 }]
|
|
146
|
+
* console.log(sniffed.rest?.license); // 42 — degraded, preserved verbatim
|
|
147
|
+
* });
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* @public
|
|
151
|
+
*/
|
|
152
|
+
var LenientManifest = class LenientManifest extends Schema.Class("LenientManifest")({
|
|
153
|
+
name: Schema.optionalKey(Schema.String),
|
|
154
|
+
version: Schema.optionalKey(Schema.String),
|
|
155
|
+
description: Schema.optionalKey(Schema.String),
|
|
156
|
+
private: Schema.optionalKey(Schema.Boolean),
|
|
157
|
+
type: Schema.optionalKey(Schema.String),
|
|
158
|
+
main: Schema.optionalKey(Schema.String),
|
|
159
|
+
license: Schema.optionalKey(Schema.String),
|
|
160
|
+
author: Schema.optionalKey(StringOrRecord),
|
|
161
|
+
contributors: Schema.optionalKey(Schema.Array(StringOrRecord)),
|
|
162
|
+
maintainers: Schema.optionalKey(Schema.Array(StringOrRecord)),
|
|
163
|
+
keywords: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
164
|
+
repository: Schema.optionalKey(StringOrRecord),
|
|
165
|
+
bugs: Schema.optionalKey(StringOrRecord),
|
|
166
|
+
homepage: Schema.optionalKey(Schema.String),
|
|
167
|
+
dependencies: Schema.optionalKey(StringRecord),
|
|
168
|
+
devDependencies: Schema.optionalKey(StringRecord),
|
|
169
|
+
peerDependencies: Schema.optionalKey(StringRecord),
|
|
170
|
+
optionalDependencies: Schema.optionalKey(StringRecord),
|
|
171
|
+
peerDependenciesMeta: Schema.optionalKey(UnknownRecord),
|
|
172
|
+
scripts: Schema.optionalKey(StringRecord),
|
|
173
|
+
bin: Schema.optionalKey(Schema.Union([Schema.String, StringRecord])),
|
|
174
|
+
engines: Schema.optionalKey(StringRecord),
|
|
175
|
+
exports: Schema.optionalKey(ExportsField),
|
|
176
|
+
publishConfig: Schema.optionalKey(PublishConfigField),
|
|
177
|
+
packageManager: Schema.optionalKey(Schema.String),
|
|
178
|
+
devEngines: Schema.optionalKey(UnknownRecord),
|
|
179
|
+
/**
|
|
180
|
+
* Unknown top-level keys, plus every degraded known field's raw value,
|
|
181
|
+
* verbatim. Always present after a lenient decode (possibly empty).
|
|
182
|
+
*/
|
|
183
|
+
rest: Schema.optionalKey(UnknownRecord),
|
|
184
|
+
/** The degradations collected by the decode — empty when nothing degraded. */
|
|
185
|
+
issues: Schema.Array(LenientFieldIssueSchema)
|
|
186
|
+
}) {
|
|
187
|
+
/**
|
|
188
|
+
* Decode an unknown JSON value leniently, degrading malformed fields instead
|
|
189
|
+
* of failing the document. The sync primitive backing
|
|
190
|
+
* {@link LenientManifest.decode}.
|
|
191
|
+
*
|
|
192
|
+
* @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
|
|
193
|
+
* @returns the lenient manifest, or a {@link PackageDecodeError} when
|
|
194
|
+
* `input` is not a JSON object at all (`null`, an array or a scalar) — the
|
|
195
|
+
* one failure leniency does not cover
|
|
196
|
+
*/
|
|
197
|
+
static decodeResult(input) {
|
|
198
|
+
const exit = decodeRecord(input);
|
|
199
|
+
if (Exit.isFailure(exit)) return Result.fail(new PackageDecodeError({ cause: Cause.squash(exit.cause) }));
|
|
200
|
+
return Result.succeed(sift(exit.value));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Decode an unknown JSON value leniently, degrading malformed fields instead
|
|
204
|
+
* of failing the document. The `Effect` form of
|
|
205
|
+
* {@link LenientManifest.decodeResult}, adding the tracing span.
|
|
206
|
+
*
|
|
207
|
+
* @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
|
|
208
|
+
* @returns an Effect resolving to the decoded {@link LenientManifest}
|
|
209
|
+
* @throws (typed) `PackageDecodeError` when `input` is not a JSON object
|
|
210
|
+
*/
|
|
211
|
+
static decode = Effect.fn("LenientManifest.decode")((input) => Effect.fromResult(LenientManifest.decodeResult(input)));
|
|
212
|
+
/**
|
|
213
|
+
* Parse package.json text and decode it leniently. The sync primitive
|
|
214
|
+
* backing {@link LenientManifest.parse}.
|
|
215
|
+
*
|
|
216
|
+
* @param text - the package.json source text
|
|
217
|
+
* @returns the lenient manifest, or a {@link PackageJsonSyntaxError} when
|
|
218
|
+
* the text is not valid JSON (`"invalid-json"`) or parses to something
|
|
219
|
+
* other than a JSON object (`"not-an-object"`) — leniency is per-field,
|
|
220
|
+
* never per-syntax
|
|
221
|
+
*/
|
|
222
|
+
static parseResult(text) {
|
|
223
|
+
let raw;
|
|
224
|
+
try {
|
|
225
|
+
raw = JSON.parse(text);
|
|
226
|
+
} catch (cause) {
|
|
227
|
+
return Result.fail(new PackageJsonSyntaxError({
|
|
228
|
+
reason: "invalid-json",
|
|
229
|
+
cause
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
if (!isPlainRecord(raw)) return Result.fail(new PackageJsonSyntaxError({ reason: "not-an-object" }));
|
|
233
|
+
return Result.succeed(sift(raw));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Parse package.json text and decode it leniently. The `Effect` form of
|
|
237
|
+
* {@link LenientManifest.parseResult}, adding the tracing span.
|
|
238
|
+
*
|
|
239
|
+
* @param text - the package.json source text
|
|
240
|
+
* @returns an Effect resolving to the decoded {@link LenientManifest}
|
|
241
|
+
* @throws (typed) `PackageJsonSyntaxError` when the text is not valid JSON
|
|
242
|
+
* or is not a JSON object
|
|
243
|
+
*/
|
|
244
|
+
static parse = Effect.fn("LenientManifest.parse")((text) => Effect.fromResult(LenientManifest.parseResult(text)));
|
|
245
|
+
/** Whether the manifest is marked private. */
|
|
246
|
+
get isPrivate() {
|
|
247
|
+
return this.private ?? false;
|
|
248
|
+
}
|
|
249
|
+
/** Whether the manifest declares ESM (`"type": "module"`, exact comparison). */
|
|
250
|
+
get isESM() {
|
|
251
|
+
return this.type === "module";
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
export { LenientManifest };
|
package/Package.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Dependency } from "./Dependency.js";
|
|
2
2
|
import { DevEnginesSchema } from "./DevEngines.js";
|
|
3
|
-
import { InvalidSpdxLicenseError, SpdxLicense, isValidSpdx } from "./License.js";
|
|
4
3
|
import { renderJson, resolveFormatOptions } from "./internal/format.js";
|
|
5
4
|
import { makeWire } from "./internal/wire.js";
|
|
5
|
+
import { InvalidSpdxLicenseError, SpdxLicense, isValidSpdx } from "./License.js";
|
|
6
6
|
import { PackageManager } from "./PackageManager.js";
|
|
7
7
|
import { InvalidPackageNameError, PackageName } from "./PackageName.js";
|
|
8
8
|
import { Person } from "./Person.js";
|
package/PackageManifest.js
CHANGED
|
@@ -23,10 +23,12 @@ import { SemVer } from "@effected/semver";
|
|
|
23
23
|
* typed), a present `name` must still satisfy the npm grammar, a present
|
|
24
24
|
* `packageManager` must still parse — though here the version position may be
|
|
25
25
|
* a semver range (`pnpm@^11.20.0`), decoded as {@link PackageManagerRange}.
|
|
26
|
-
* For
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
26
|
+
* For tolerance of malformed fields, use the shape-lenient `LenientManifest`
|
|
27
|
+
* discovery tier — which degrades them to absence, preserved in `rest` and
|
|
28
|
+
* reported on `issues` — or the decode-free {@link PackageJsonFormat} text
|
|
29
|
+
* path (or `@effected/npm`'s shape-blind `Manifest`); here, silently carrying
|
|
30
|
+
* a value the type claims to have validated would be a lie, and silently
|
|
31
|
+
* dropping it would break round-trip fidelity.
|
|
30
32
|
*
|
|
31
33
|
* The model is deliberately lean — fields, the `rest` catch-all wire codec,
|
|
32
34
|
* {@link PackageManifest.decode} and {@link PackageManifest.toJsonString} —
|
package/README.md
CHANGED
|
@@ -184,6 +184,32 @@ console.log(Effect.runSync(program));
|
|
|
184
184
|
|
|
185
185
|
The `workspace:` range modifier is honored: `workspace:*` takes the bare version, `workspace:^` and `workspace:~` prefix it, and an explicit modifier is used as-is. The projection is `@effected/npm`'s `DependencySpecifier` statics with full pnpm publish semantics: the alias form `workspace:<name>@<range>` resolves the *target* package's version and becomes the `npm:<name>@<range>` alias pnpm publishes, and a blank catalog name selects the default catalog. A failed catalog assembly surfaces typed as `@effected/npm`'s `CatalogAssemblyError`, alongside the contracts' `DependencyResolutionError`.
|
|
186
186
|
|
|
187
|
+
## Lenient discovery
|
|
188
|
+
|
|
189
|
+
`Package.decode` and `PackageManifest` are strict: a malformed field fails the whole document. That is the right behavior for a manifest you are about to write or publish, and the wrong one for a manifest you are only sniffing — a fetched tarball, a `node_modules` walk, a registry response — where the document is someone else's data and one bad field should not sink the read. `LenientManifest` is that discovery tier: every `Package` field decodes to its plain permissive JSON shape (`name` and `version` accept any string, not the branded npm grammar; `license` accepts any string, no SPDX check; the dependency maps are plain records, not `HashMap`s). A field present but not even that shape degrades to absence instead of failing the document, its raw value is preserved verbatim in `rest` (a malformed known field is treated exactly like an unknown one), and the degradation is reported on `issues`:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { LenientManifest } from "@effected/package-json";
|
|
193
|
+
import { Effect } from "effect";
|
|
194
|
+
|
|
195
|
+
const program = Effect.gen(function* () {
|
|
196
|
+
const sniffed = yield* LenientManifest.decode({ name: "JSONStream", version: "1.0", license: 42 });
|
|
197
|
+
return [sniffed.name, sniffed.version, sniffed.issues, sniffed.rest?.license] as const;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
console.log(Effect.runSync(program));
|
|
201
|
+
// [
|
|
202
|
+
// "JSONStream",
|
|
203
|
+
// "1.0",
|
|
204
|
+
// [{ field: "license", expected: "a string", value: 42 }],
|
|
205
|
+
// 42,
|
|
206
|
+
// ]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Leniency is per-field, never per-syntax: `decodeResult`/`decode` still fail typed with `PackageDecodeError` when the input is not a JSON object at all (`null`, an array, a scalar), and `parseResult`/`parse` — the pair that also handles the raw `JSON.parse` — fail typed with `PackageJsonSyntaxError` when the text is not valid JSON or does not parse to an object. Each pair follows the package's usual shape: `decodeResult`/`parseResult` are the synchronous `Result` primitives, `decode`/`parse` are their `Effect` forms with a tracing span.
|
|
210
|
+
|
|
211
|
+
An empty `issues` array is not a validity guarantee — the permissive shapes check JSON shape, not npm semantics, so a `LenientManifest` with no issues can still fail `Package.decode`. `LenientManifest` carries no mutation statics and no write path; once you need to validate or edit, re-decode the *original* input through `PackageManifest.decode` (presence-lenient, shape-on-presence strict) or `Package.decode` (strict, publishable).
|
|
212
|
+
|
|
187
213
|
## Resolving an entry point
|
|
188
214
|
|
|
189
215
|
`resolveEntryPoint` answers one question about a manifest — which file is the package's `"."` entry — and it is pure, IO-free and `Result`-returning, so it works against a plain object with no package on disk:
|
|
@@ -236,6 +262,7 @@ Every failure is a `Schema.TaggedError` routed with `Effect.catchTag`. Causes ar
|
|
|
236
262
|
- `PackageJsonFile` — the IO surface: `read` and `write` over core `FileSystem` / `Path`, with the platform implementation supplied at the edge.
|
|
237
263
|
- `PackageValidator` — rule-based validation aggregating every failure, with the default rule set, a parameterized `layerRules` factory, and the publish-gate rules `noUnresolvedDepsRule` and `noLocalDepsRule`.
|
|
238
264
|
- `resolveEntryPoint` — the pure, `Result`-returning entry-point resolver over a manifest's `exports`/`main`, honoring `exports` encapsulation rather than falling through to `main`, with `EntryPointManifest` as its tolerant input shape.
|
|
265
|
+
- `LenientManifest` — the shape-lenient discovery tier below `PackageManifest`: malformed known fields degrade to absence, are preserved verbatim in `rest` and reported on `issues`, rather than failing the document; `decodeResult`/`decode` and `parseResult`/`parse` in the package's usual `Result`/`Effect` pairing.
|
|
239
266
|
- `Package.resolve` — `catalog:` and `workspace:` expansion over the `@effected/npm` contracts with pnpm's publish-time projection (alias form included), as an explicit step that `write` never performs for you.
|
|
240
267
|
- `PackageName`, `DependencySpecifier`, `Dependency`, `SpdxLicense`, `PackageManager`, `Person`, `Repository`, `Bugs`, `DevEngine` — the leaf concepts, each owning its own statics, brand and error, usable independently of `Package`. `Repository` and `Bugs` decode the `repository` and `bugs` fields from either their shorthand or object form, the same way `Person` does for `author`, `contributors` and `maintainers`; `Repository` also exposes `browseUrl` and `gitUrl` getters that normalize a shorthand or SSH form to `https://`.
|
|
241
268
|
- `Package` also types `keywords`, `maintainers` and `homepage` directly, alongside the existing `author` and `contributors`.
|
package/index.d.ts
CHANGED
|
@@ -1078,6 +1078,162 @@ declare class PackageJsonFormat {
|
|
|
1078
1078
|
static readonly modifyToString: (source: string, path: JsoncPath$1, value: unknown) => Effect.Effect<string, PackageJsonModifyError | PackageJsonSyntaxError, never>;
|
|
1079
1079
|
}
|
|
1080
1080
|
//#endregion
|
|
1081
|
+
//#region src/LenientManifest.d.ts
|
|
1082
|
+
/**
|
|
1083
|
+
* One degraded field from a lenient decode: the top-level `field` that did not
|
|
1084
|
+
* match its permissive shape, a human-readable description of the `expected`
|
|
1085
|
+
* shape, and the raw `value` found there (also preserved verbatim under
|
|
1086
|
+
* `LenientManifest.rest[field]`).
|
|
1087
|
+
*
|
|
1088
|
+
* A value, not an error — the decode still succeeds; issues exist so callers
|
|
1089
|
+
* can report what degraded.
|
|
1090
|
+
*
|
|
1091
|
+
* @public
|
|
1092
|
+
*/
|
|
1093
|
+
interface LenientFieldIssue {
|
|
1094
|
+
/** The top-level field name that degraded, e.g. `"name"`. */
|
|
1095
|
+
readonly field: string;
|
|
1096
|
+
/** A human-readable description of the permissive shape the field required. */
|
|
1097
|
+
readonly expected: string;
|
|
1098
|
+
/** The raw value found on the wire, preserved for reporting. */
|
|
1099
|
+
readonly value: unknown;
|
|
1100
|
+
}
|
|
1101
|
+
declare const LenientManifest_base: Schema.Class<LenientManifest, Schema.Struct<{
|
|
1102
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
1103
|
+
readonly version: Schema.optionalKey<Schema.String>;
|
|
1104
|
+
readonly description: Schema.optionalKey<Schema.String>;
|
|
1105
|
+
readonly private: Schema.optionalKey<Schema.Boolean>;
|
|
1106
|
+
readonly type: Schema.optionalKey<Schema.String>;
|
|
1107
|
+
readonly main: Schema.optionalKey<Schema.String>;
|
|
1108
|
+
readonly license: Schema.optionalKey<Schema.String>;
|
|
1109
|
+
readonly author: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>;
|
|
1110
|
+
readonly contributors: Schema.optionalKey<Schema.$Array<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>>;
|
|
1111
|
+
readonly maintainers: Schema.optionalKey<Schema.$Array<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>>;
|
|
1112
|
+
readonly keywords: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
1113
|
+
readonly repository: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>;
|
|
1114
|
+
readonly bugs: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>;
|
|
1115
|
+
readonly homepage: Schema.optionalKey<Schema.String>;
|
|
1116
|
+
readonly dependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
1117
|
+
readonly devDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
1118
|
+
readonly peerDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
1119
|
+
readonly optionalDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
1120
|
+
readonly peerDependenciesMeta: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
1121
|
+
readonly scripts: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
1122
|
+
readonly bin: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.String>]>>;
|
|
1123
|
+
readonly engines: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
1124
|
+
readonly exports: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>;
|
|
1125
|
+
readonly publishConfig: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
1126
|
+
readonly packageManager: Schema.optionalKey<Schema.String>;
|
|
1127
|
+
readonly devEngines: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
1128
|
+
/**
|
|
1129
|
+
* Unknown top-level keys, plus every degraded known field's raw value,
|
|
1130
|
+
* verbatim. Always present after a lenient decode (possibly empty).
|
|
1131
|
+
*/
|
|
1132
|
+
readonly rest: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
1133
|
+
/** The degradations collected by the decode — empty when nothing degraded. */
|
|
1134
|
+
readonly issues: Schema.$Array<Schema.Struct<{
|
|
1135
|
+
readonly field: Schema.String;
|
|
1136
|
+
readonly expected: Schema.String;
|
|
1137
|
+
readonly value: Schema.Unknown;
|
|
1138
|
+
}>>;
|
|
1139
|
+
}>, {}>;
|
|
1140
|
+
/**
|
|
1141
|
+
* The shape-lenient view of a package.json document, for discovery and
|
|
1142
|
+
* sniffing — probing a fetched tarball's manifest, walking a `node_modules`
|
|
1143
|
+
* tree, listing candidate packages — where the document is other people's
|
|
1144
|
+
* data and one malformed field must not fail the read.
|
|
1145
|
+
*
|
|
1146
|
+
* @remarks
|
|
1147
|
+
* **This is the discovery tier, not a validation bypass.** Every field shares
|
|
1148
|
+
* its name with the strict `Package` model, but is typed as its plain permissive JSON
|
|
1149
|
+
* shape: `name` and `version` are any string (a legacy uppercase name or a
|
|
1150
|
+
* non-semver `"1.0"` is recovered, not rejected), `license` is any string (no
|
|
1151
|
+
* SPDX check), the dependency maps and `scripts` are plain string→string
|
|
1152
|
+
* records rather than `HashMap`s. A present field that is not even that shape
|
|
1153
|
+
* **degrades to absence** rather than failing the document: the raw value is
|
|
1154
|
+
* preserved verbatim in `rest` and the degradation is reported on `issues`,
|
|
1155
|
+
* so callers can surface what was ignored. Degradation granularity is the
|
|
1156
|
+
* top-level field — one junk entry degrades its whole map, with the raw map
|
|
1157
|
+
* still in `rest`.
|
|
1158
|
+
*
|
|
1159
|
+
* Leniency is per-field, never per-syntax: text that is not valid JSON fails
|
|
1160
|
+
* {@link LenientManifest.parseResult} as a typed
|
|
1161
|
+
* {@link PackageJsonSyntaxError}, and a value that is not a JSON object fails
|
|
1162
|
+
* {@link LenientManifest.decodeResult} as a typed {@link PackageDecodeError}.
|
|
1163
|
+
*
|
|
1164
|
+
* An empty `issues` array does **not** mean the strict tiers would accept the
|
|
1165
|
+
* document — the permissive shapes check JSON shape, not npm semantics. The
|
|
1166
|
+
* upgrade path is to decode the *original* input through
|
|
1167
|
+
* `PackageManifest.decode` (presence-lenient, shape-strict) or
|
|
1168
|
+
* `Package.decode` (strict, publishable) when validation is actually
|
|
1169
|
+
* wanted. This class deliberately carries no mutation statics and no write
|
|
1170
|
+
* path; editing belongs to the strict tiers and to
|
|
1171
|
+
* `PackageJsonFormat.modifyToString` / `PackageJsonFile.modify`.
|
|
1172
|
+
*
|
|
1173
|
+
* @example
|
|
1174
|
+
* ```ts
|
|
1175
|
+
* import { LenientManifest } from "@effected/package-json";
|
|
1176
|
+
* import { Effect } from "effect";
|
|
1177
|
+
*
|
|
1178
|
+
* const program = Effect.gen(function* () {
|
|
1179
|
+
* const sniffed = yield* LenientManifest.decode({ name: "JSONStream", version: "1.0", license: 42 });
|
|
1180
|
+
* console.log(sniffed.name, sniffed.version); // "JSONStream" "1.0"
|
|
1181
|
+
* console.log(sniffed.issues); // [{ field: "license", expected: "a string", value: 42 }]
|
|
1182
|
+
* console.log(sniffed.rest?.license); // 42 — degraded, preserved verbatim
|
|
1183
|
+
* });
|
|
1184
|
+
* ```
|
|
1185
|
+
*
|
|
1186
|
+
* @public
|
|
1187
|
+
*/
|
|
1188
|
+
declare class LenientManifest extends LenientManifest_base {
|
|
1189
|
+
/**
|
|
1190
|
+
* Decode an unknown JSON value leniently, degrading malformed fields instead
|
|
1191
|
+
* of failing the document. The sync primitive backing
|
|
1192
|
+
* {@link LenientManifest.decode}.
|
|
1193
|
+
*
|
|
1194
|
+
* @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
|
|
1195
|
+
* @returns the lenient manifest, or a {@link PackageDecodeError} when
|
|
1196
|
+
* `input` is not a JSON object at all (`null`, an array or a scalar) — the
|
|
1197
|
+
* one failure leniency does not cover
|
|
1198
|
+
*/
|
|
1199
|
+
static decodeResult(input: unknown): Result.Result<LenientManifest, PackageDecodeError>;
|
|
1200
|
+
/**
|
|
1201
|
+
* Decode an unknown JSON value leniently, degrading malformed fields instead
|
|
1202
|
+
* of failing the document. The `Effect` form of
|
|
1203
|
+
* {@link LenientManifest.decodeResult}, adding the tracing span.
|
|
1204
|
+
*
|
|
1205
|
+
* @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
|
|
1206
|
+
* @returns an Effect resolving to the decoded {@link LenientManifest}
|
|
1207
|
+
* @throws (typed) `PackageDecodeError` when `input` is not a JSON object
|
|
1208
|
+
*/
|
|
1209
|
+
static readonly decode: (input: unknown) => Effect.Effect<LenientManifest, PackageDecodeError, never>;
|
|
1210
|
+
/**
|
|
1211
|
+
* Parse package.json text and decode it leniently. The sync primitive
|
|
1212
|
+
* backing {@link LenientManifest.parse}.
|
|
1213
|
+
*
|
|
1214
|
+
* @param text - the package.json source text
|
|
1215
|
+
* @returns the lenient manifest, or a {@link PackageJsonSyntaxError} when
|
|
1216
|
+
* the text is not valid JSON (`"invalid-json"`) or parses to something
|
|
1217
|
+
* other than a JSON object (`"not-an-object"`) — leniency is per-field,
|
|
1218
|
+
* never per-syntax
|
|
1219
|
+
*/
|
|
1220
|
+
static parseResult(text: string): Result.Result<LenientManifest, PackageJsonSyntaxError>;
|
|
1221
|
+
/**
|
|
1222
|
+
* Parse package.json text and decode it leniently. The `Effect` form of
|
|
1223
|
+
* {@link LenientManifest.parseResult}, adding the tracing span.
|
|
1224
|
+
*
|
|
1225
|
+
* @param text - the package.json source text
|
|
1226
|
+
* @returns an Effect resolving to the decoded {@link LenientManifest}
|
|
1227
|
+
* @throws (typed) `PackageJsonSyntaxError` when the text is not valid JSON
|
|
1228
|
+
* or is not a JSON object
|
|
1229
|
+
*/
|
|
1230
|
+
static readonly parse: (text: string) => Effect.Effect<LenientManifest, PackageJsonSyntaxError, never>;
|
|
1231
|
+
/** Whether the manifest is marked private. */
|
|
1232
|
+
get isPrivate(): boolean;
|
|
1233
|
+
/** Whether the manifest declares ESM (`"type": "module"`, exact comparison). */
|
|
1234
|
+
get isESM(): boolean;
|
|
1235
|
+
}
|
|
1236
|
+
//#endregion
|
|
1081
1237
|
//#region src/PackageManagerRange.d.ts
|
|
1082
1238
|
declare const PackageManagerRange_base: Schema.Class<PackageManagerRange, Schema.Struct<{
|
|
1083
1239
|
/** The package-manager name (e.g. `pnpm`). Any lowercase name — the same latitude as {@link PackageManager}, for the same evidence. */
|
|
@@ -1233,10 +1389,12 @@ declare const PackageManifest_base: Schema.Class<PackageManifest, Schema.Struct<
|
|
|
1233
1389
|
* typed), a present `name` must still satisfy the npm grammar, a present
|
|
1234
1390
|
* `packageManager` must still parse — though here the version position may be
|
|
1235
1391
|
* a semver range (`pnpm@^11.20.0`), decoded as {@link PackageManagerRange}.
|
|
1236
|
-
* For
|
|
1237
|
-
*
|
|
1238
|
-
* `
|
|
1239
|
-
*
|
|
1392
|
+
* For tolerance of malformed fields, use the shape-lenient `LenientManifest`
|
|
1393
|
+
* discovery tier — which degrades them to absence, preserved in `rest` and
|
|
1394
|
+
* reported on `issues` — or the decode-free {@link PackageJsonFormat} text
|
|
1395
|
+
* path (or `@effected/npm`'s shape-blind `Manifest`); here, silently carrying
|
|
1396
|
+
* a value the type claims to have validated would be a lie, and silently
|
|
1397
|
+
* dropping it would break round-trip fidelity.
|
|
1240
1398
|
*
|
|
1241
1399
|
* The model is deliberately lean — fields, the `rest` catch-all wire codec,
|
|
1242
1400
|
* {@link PackageManifest.decode} and {@link PackageManifest.toJsonString} —
|
|
@@ -1542,5 +1700,5 @@ declare class PackageValidator extends PackageValidator_base {
|
|
|
1542
1700
|
}): Layer.Layer<PackageValidator>;
|
|
1543
1701
|
}
|
|
1544
1702
|
//#endregion
|
|
1545
|
-
export { BinField, Bugs, Dependency, type DependencyKind, DependencyMapField, type DependencyProtocol, DependencySpecifier, type DependencySpecifierBrand, DevEngine, DevEngineOrArray, type DevEngines, DevEnginesSchema, type EntryPointManifest, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, JsoncEdit, type JsoncPath, Package, PackageDecodeError, type PackageFieldEdit, type PackageFormatOptions, type PackageFormatTextOptions, type PackageIndent, PackageJsonFile, type PackageJsonFileShape, PackageJsonFormat, PackageJsonModifyError, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonSyntaxError, PackageJsonWriteError, PackageManager, PackageManagerRange, PackageManifest, PackageName, type PackagePatch, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, Repository, RepositoryField, type ResolveEntryPointOptions, type RuleFailure, ScopedPackageName, SpdxLicense, StringMapField, type UnresolvedDependency, UnresolvedEntryPointError, UnscopedPackageName, type ValidationRule, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule, resolveEntryPoint };
|
|
1703
|
+
export { BinField, Bugs, Dependency, type DependencyKind, DependencyMapField, type DependencyProtocol, DependencySpecifier, type DependencySpecifierBrand, DevEngine, DevEngineOrArray, type DevEngines, DevEnginesSchema, type EntryPointManifest, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, JsoncEdit, type JsoncPath, type LenientFieldIssue, LenientManifest, Package, PackageDecodeError, type PackageFieldEdit, type PackageFormatOptions, type PackageFormatTextOptions, type PackageIndent, PackageJsonFile, type PackageJsonFileShape, PackageJsonFormat, PackageJsonModifyError, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonSyntaxError, PackageJsonWriteError, PackageManager, PackageManagerRange, PackageManifest, PackageName, type PackagePatch, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, Repository, RepositoryField, type ResolveEntryPointOptions, type RuleFailure, ScopedPackageName, SpdxLicense, StringMapField, type UnresolvedDependency, UnresolvedEntryPointError, UnscopedPackageName, type ValidationRule, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule, resolveEntryPoint };
|
|
1546
1704
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Person } from "./Person.js";
|
|
|
8
8
|
import { Bugs, Repository } from "./Repository.js";
|
|
9
9
|
import { BinField, DependencyMapField, ExportsField, Package, PackageDecodeError, PeerDependenciesMetaField, PublishConfigField, RepositoryField, StringMapField } from "./Package.js";
|
|
10
10
|
import { PackageJsonFormat, PackageJsonModifyError, PackageJsonSyntaxError } from "./PackageJsonFormat.js";
|
|
11
|
+
import { LenientManifest } from "./LenientManifest.js";
|
|
11
12
|
import { PackageManagerRange } from "./PackageManagerRange.js";
|
|
12
13
|
import { PackageManifest } from "./PackageManifest.js";
|
|
13
14
|
import { PackageJsonFile, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonWriteError } from "./PackageJsonFile.js";
|
|
@@ -15,4 +16,4 @@ import { PackageValidationError, PackageValidator, defaultRules, noLocalDepsRule
|
|
|
15
16
|
import { JsoncEdit } from "@effected/jsonc";
|
|
16
17
|
import { DependencySpecifier, InvalidDependencySpecifierError, isValidDependencySpecifier } from "@effected/npm";
|
|
17
18
|
|
|
18
|
-
export { BinField, Bugs, Dependency, DependencyMapField, DependencySpecifier, DevEngine, DevEngineOrArray, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, JsoncEdit, Package, PackageDecodeError, PackageJsonFile, PackageJsonFormat, PackageJsonModifyError, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonSyntaxError, PackageJsonWriteError, PackageManager, PackageManagerRange, PackageManifest, PackageName, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, Repository, RepositoryField, ScopedPackageName, SpdxLicense, StringMapField, UnresolvedEntryPointError, UnscopedPackageName, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule, resolveEntryPoint };
|
|
19
|
+
export { BinField, Bugs, Dependency, DependencyMapField, DependencySpecifier, DevEngine, DevEngineOrArray, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, JsoncEdit, LenientManifest, Package, PackageDecodeError, PackageJsonFile, PackageJsonFormat, PackageJsonModifyError, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonSyntaxError, PackageJsonWriteError, PackageManager, PackageManagerRange, PackageManifest, PackageName, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, Repository, RepositoryField, ScopedPackageName, SpdxLicense, StringMapField, UnresolvedEntryPointError, UnscopedPackageName, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule, resolveEntryPoint };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/package-json",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "package.json parsing, editing, validation and file IO as Effect schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"./package.json": "./package.json"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@effected/jsonc": "^0.
|
|
41
|
+
"@effected/jsonc": "^0.8.0",
|
|
42
42
|
"@effected/npm": "^0.12.0",
|
|
43
43
|
"@effected/semver": "^0.5.0",
|
|
44
44
|
"@effected/spdx": "^0.4.0"
|