@effected/package-json 0.3.1 → 0.4.1
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/PackageJsonFormat.js +138 -0
- package/Person.js +106 -10
- package/index.d.ts +190 -7
- package/index.js +2 -1
- package/package.json +3 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { renderJson, resolveIndent, sortKeys } from "./internal/format.js";
|
|
2
|
+
import { Result, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/PackageJsonFormat.ts
|
|
5
|
+
/**
|
|
6
|
+
* Indicates that a text input could not be treated as a package.json document:
|
|
7
|
+
* either it is not valid JSON (`"invalid-json"`, carrying the underlying
|
|
8
|
+
* `SyntaxError` on `cause`) or it parsed to something other than a JSON object
|
|
9
|
+
* (`"not-an-object"` — an array, a scalar or `null`).
|
|
10
|
+
*
|
|
11
|
+
* Raised by {@link PackageJsonFormat.formatToString}. This is a *syntactic*
|
|
12
|
+
* failure only; it says nothing about whether the document is a valid package
|
|
13
|
+
* manifest, which the decode-free path deliberately does not check.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
var PackageJsonSyntaxError = class extends Schema.TaggedErrorClass()("PackageJsonSyntaxError", {
|
|
18
|
+
/** Which syntactic precondition failed. */
|
|
19
|
+
reason: Schema.Literals(["invalid-json", "not-an-object"]),
|
|
20
|
+
/** The underlying `SyntaxError` for `"invalid-json"`, preserved structurally. */
|
|
21
|
+
cause: Schema.optionalKey(Schema.Defect())
|
|
22
|
+
}) {
|
|
23
|
+
get message() {
|
|
24
|
+
return this.reason === "invalid-json" ? "package.json text is not valid JSON" : "package.json text is not a JSON object";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
/**
|
|
29
|
+
* Decode-free canonical sort and format statics. Not instantiable.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The guarantee both statics make is that they are **source-preserving**:
|
|
33
|
+
* neither decodes into a `Package`, so neither can normalize a field encoding.
|
|
34
|
+
* String-form `author` shorthand, unknown fields, unusual value shapes and
|
|
35
|
+
* empty maps all survive untouched, because they are never looked at. Key
|
|
36
|
+
* order, indentation and the trailing newline are the only things that change.
|
|
37
|
+
*
|
|
38
|
+
* That is what makes this usable as a lint-hook handler where the strict path
|
|
39
|
+
* is not: any syntactically valid JSON object formats, including the
|
|
40
|
+
* version-less workspace roots and `{"private": true}` manifests that
|
|
41
|
+
* `Package.decode` rejects. Reach for `Package.decode` +
|
|
42
|
+
* `Package.toJsonString` instead when the job needs the validated model and an
|
|
43
|
+
* invalid manifest should fail loudly.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
var PackageJsonFormat = class {
|
|
48
|
+
constructor() {}
|
|
49
|
+
/**
|
|
50
|
+
* Order a package.json object's keys canonically **without decoding it into
|
|
51
|
+
* a `Package`**: known top-level keys in `sort-package-json`'s order, then
|
|
52
|
+
* unknown public keys alphabetically, then `_`-prefixed keys, with the
|
|
53
|
+
* dependency maps and `scripts` / `engines` / `bin` alphabetized.
|
|
54
|
+
*
|
|
55
|
+
* Value in, value out — for hosts that already hold parsed JSON and never
|
|
56
|
+
* want a string. {@link PackageJsonFormat.formatToString} is the same
|
|
57
|
+
* ordering for hosts holding file text. Pure and total.
|
|
58
|
+
*
|
|
59
|
+
* Returns a new object; nested values are shared by reference rather than
|
|
60
|
+
* cloned, except the maps whose own keys are reordered. A value that is not
|
|
61
|
+
* a JSON object (an array, a scalar, `null`) is returned unchanged rather
|
|
62
|
+
* than mangled, so a mistyped `Json` union cannot silently lose data.
|
|
63
|
+
*
|
|
64
|
+
* Reordering keys is the whole of it — **no key is ever added or removed**,
|
|
65
|
+
* which is what lets the return type be the input type `T` and makes this a
|
|
66
|
+
* drop-in. Use {@link PackageJsonFormat.formatToString} with `stripEmpty`
|
|
67
|
+
* when removing empty maps is wanted; it returns a string and so carries no
|
|
68
|
+
* such obligation.
|
|
69
|
+
*
|
|
70
|
+
* @param value - the parsed package.json object
|
|
71
|
+
* @returns a new object with canonically ordered keys
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { PackageJsonFormat } from "@effected/package-json";
|
|
76
|
+
*
|
|
77
|
+
* const sorted = PackageJsonFormat.sortValue({ version: "1.0.0", name: "p" });
|
|
78
|
+
* // => { name: "p", version: "1.0.0" }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
static sortValue(value) {
|
|
82
|
+
if (!isJsonObject(value)) return value;
|
|
83
|
+
return sortKeys(value);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Sort and format package.json text **without decoding it into a
|
|
87
|
+
* `Package`**. Text in, text out — for hosts that hold file contents and
|
|
88
|
+
* cannot afford a decode. {@link PackageJsonFormat.sortValue} is the same
|
|
89
|
+
* ordering for hosts that already hold parsed JSON.
|
|
90
|
+
*
|
|
91
|
+
* Any syntactically valid JSON object formats, whatever it contains: a
|
|
92
|
+
* version-less root, `{"private": true}`, a malformed `packageManager`
|
|
93
|
+
* integrity. Nothing is decoded, so nothing is normalized — string-form
|
|
94
|
+
* `author` shorthand, unknown fields, unusual value shapes and empty maps
|
|
95
|
+
* all survive byte-for-byte. Only key order, indentation and the trailing
|
|
96
|
+
* newline change.
|
|
97
|
+
*
|
|
98
|
+
* Pure and synchronous: it returns a `Result` rather than an `Effect`, so
|
|
99
|
+
* synchronous hosts can call it directly. Lift it with `Effect.fromResult`.
|
|
100
|
+
*
|
|
101
|
+
* @param source - the package.json file contents
|
|
102
|
+
* @param options - formatting options; see {@link PackageFormatTextOptions}
|
|
103
|
+
* @returns the formatted text, or a {@link PackageJsonSyntaxError}
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { PackageJsonFormat } from "@effected/package-json";
|
|
108
|
+
* import { Effect, Result } from "effect";
|
|
109
|
+
*
|
|
110
|
+
* const formatted = PackageJsonFormat.formatToString('{"private": true}');
|
|
111
|
+
* if (Result.isSuccess(formatted)) console.log(formatted.success);
|
|
112
|
+
*
|
|
113
|
+
* // In an Effect program:
|
|
114
|
+
* const program = Effect.fromResult(PackageJsonFormat.formatToString('{"private": true}'));
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
static formatToString(source, options) {
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(source);
|
|
121
|
+
} catch (cause) {
|
|
122
|
+
return Result.fail(new PackageJsonSyntaxError({
|
|
123
|
+
reason: "invalid-json",
|
|
124
|
+
cause
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
if (!isJsonObject(parsed)) return Result.fail(new PackageJsonSyntaxError({ reason: "not-an-object" }));
|
|
128
|
+
return Result.succeed(renderJson(parsed, {
|
|
129
|
+
indent: resolveIndent(options?.indent ?? "preserve", source),
|
|
130
|
+
sort: options?.sort ?? true,
|
|
131
|
+
stripEmpty: options?.stripEmpty ?? false,
|
|
132
|
+
newline: options?.newline ?? true
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { PackageJsonFormat, PackageJsonSyntaxError };
|
package/Person.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Schema, SchemaTransformation } from "effect";
|
|
1
|
+
import { Effect, Option, Schema, SchemaTransformation } from "effect";
|
|
2
2
|
|
|
3
3
|
//#region src/Person.ts
|
|
4
4
|
const parsePersonString = (input) => {
|
|
@@ -14,8 +14,56 @@ const parsePersonString = (input) => {
|
|
|
14
14
|
...urlMatch !== null ? { url: urlMatch[1] } : {}
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
|
+
const serializePerson = (person) => {
|
|
18
|
+
let result = person.name;
|
|
19
|
+
if (person.email !== void 0) result += ` <${person.email}>`;
|
|
20
|
+
if (person.url !== void 0) result += ` (${person.url})`;
|
|
21
|
+
return result;
|
|
22
|
+
};
|
|
23
|
+
const wireForms = /* @__PURE__ */ new WeakMap();
|
|
24
|
+
const rememberWire = (person, wire) => {
|
|
25
|
+
wireForms.set(person, wire);
|
|
26
|
+
return person;
|
|
27
|
+
};
|
|
28
|
+
const KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
29
|
+
"name",
|
|
30
|
+
"email",
|
|
31
|
+
"url"
|
|
32
|
+
]);
|
|
33
|
+
const sameRest = (a, b) => JSON.stringify(a ?? {}) === JSON.stringify(b ?? {});
|
|
34
|
+
const isFaithful = (wire, person) => {
|
|
35
|
+
if (typeof wire === "string") {
|
|
36
|
+
const parsed = parsePersonString(wire);
|
|
37
|
+
return parsed.name === person.name && parsed.email === person.email && parsed.url === person.url;
|
|
38
|
+
}
|
|
39
|
+
const rest = restOf(wire);
|
|
40
|
+
return wire.name === person.name && wire.email === person.email && wire.url === person.url && sameRest(rest, person.rest);
|
|
41
|
+
};
|
|
42
|
+
const PersonFields = Schema.Struct({
|
|
43
|
+
name: Schema.String,
|
|
44
|
+
email: Schema.optionalKey(Schema.String),
|
|
45
|
+
url: Schema.optionalKey(Schema.String)
|
|
46
|
+
});
|
|
47
|
+
const decodePersonFields = Schema.decodeUnknownEffect(PersonFields);
|
|
48
|
+
const restOf = (raw) => {
|
|
49
|
+
const rest = {};
|
|
50
|
+
for (const [key, value] of Object.entries(raw)) if (!KNOWN_KEYS.has(key)) rest[key] = value;
|
|
51
|
+
return rest;
|
|
52
|
+
};
|
|
53
|
+
const encodePersonObject = (person) => {
|
|
54
|
+
const wire = wireForms.get(person);
|
|
55
|
+
if (wire !== void 0 && typeof wire !== "string" && isFaithful(wire, person)) return wire;
|
|
56
|
+
const known = { name: person.name };
|
|
57
|
+
if (person.email !== void 0) known.email = person.email;
|
|
58
|
+
if (person.url !== void 0) known.url = person.url;
|
|
59
|
+
return {
|
|
60
|
+
...person.rest ?? {},
|
|
61
|
+
...known
|
|
62
|
+
};
|
|
63
|
+
};
|
|
17
64
|
/**
|
|
18
|
-
* A structured person object with `name
|
|
65
|
+
* A structured person object with `name`, optional `email` / `url`, and a
|
|
66
|
+
* `rest` catch-all preserving any additional keys across a read/write cycle.
|
|
19
67
|
*
|
|
20
68
|
* @public
|
|
21
69
|
*/
|
|
@@ -25,26 +73,74 @@ var Person = class Person extends Schema.Class("Person")({
|
|
|
25
73
|
/** The optional email address. */
|
|
26
74
|
email: Schema.optionalKey(Schema.String),
|
|
27
75
|
/** The optional homepage URL. */
|
|
28
|
-
url: Schema.optionalKey(Schema.String)
|
|
76
|
+
url: Schema.optionalKey(Schema.String),
|
|
77
|
+
/** Any additional keys, preserved verbatim and flattened back on encode. */
|
|
78
|
+
rest: Schema.optionalKey(Schema.Record(Schema.String, Schema.Unknown))
|
|
29
79
|
}) {
|
|
80
|
+
/**
|
|
81
|
+
* The object wire codec: an open JSON object ↔ a {@link Person}, partitioning
|
|
82
|
+
* unknown keys into `rest` and flattening them back on encode so the on-disk
|
|
83
|
+
* shape never carries a literal `rest` key.
|
|
84
|
+
*/
|
|
85
|
+
static schema = Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.decodeTo(Schema.instanceOf(Person), SchemaTransformation.transformOrFail({
|
|
86
|
+
decode: (raw) => decodePersonFields(raw).pipe(Effect.mapError((error) => error.issue), Effect.map((fields) => {
|
|
87
|
+
const rest = restOf(raw);
|
|
88
|
+
return rememberWire(Person.make({
|
|
89
|
+
...fields,
|
|
90
|
+
...Object.keys(rest).length > 0 ? { rest } : {}
|
|
91
|
+
}), raw);
|
|
92
|
+
})),
|
|
93
|
+
encode: (person) => Effect.succeed(encodePersonObject(person))
|
|
94
|
+
})));
|
|
30
95
|
/**
|
|
31
96
|
* Schema transformation between the `"Name <email> (url)"` shorthand string
|
|
32
|
-
* and a {@link Person}.
|
|
97
|
+
* and a {@link Person}. Decoding remembers the input text so that encoding
|
|
98
|
+
* reproduces it verbatim; see {@link Person.wireStringOf}.
|
|
33
99
|
*/
|
|
34
100
|
static FromString = Schema.String.pipe(Schema.decodeTo(Schema.instanceOf(Person), SchemaTransformation.transform({
|
|
35
|
-
decode: (input) => parsePersonString(input),
|
|
101
|
+
decode: (input) => rememberWire(parsePersonString(input), input),
|
|
36
102
|
encode: (person) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (person.url !== void 0) result += ` (${person.url})`;
|
|
40
|
-
return result;
|
|
103
|
+
const wire = wireForms.get(person);
|
|
104
|
+
return typeof wire === "string" && isFaithful(wire, person) ? wire : serializePerson(person);
|
|
41
105
|
}
|
|
42
106
|
})));
|
|
43
107
|
/**
|
|
44
108
|
* The `author` / `contributors` value: either the shorthand string or the
|
|
45
109
|
* structured object, always decoded to a {@link Person}.
|
|
110
|
+
*
|
|
111
|
+
* The wire form is preserved across a round trip — a person read from the
|
|
112
|
+
* shorthand string encodes back to that string, byte for byte, and one read
|
|
113
|
+
* from an object encodes back to an object with its unknown keys intact.
|
|
114
|
+
* Formatting a manifest therefore never rewrites one legal encoding into the
|
|
115
|
+
* other.
|
|
116
|
+
*
|
|
117
|
+
* Provenance belongs to the instance, so a person that is *rebuilt* (rather
|
|
118
|
+
* than carried through unchanged) has none and encodes in the canonical
|
|
119
|
+
* object form. Editing an unrelated field of the surrounding `Package`
|
|
120
|
+
* carries the same person instance through and preserves its encoding.
|
|
121
|
+
*/
|
|
122
|
+
static FromValue = Schema.Union([Person.schema, Schema.String]).pipe(Schema.decodeTo(Schema.instanceOf(Person), SchemaTransformation.transform({
|
|
123
|
+
decode: (input) => typeof input === "string" ? rememberWire(parsePersonString(input), input) : input,
|
|
124
|
+
encode: (person) => {
|
|
125
|
+
const wire = wireForms.get(person);
|
|
126
|
+
return typeof wire === "string" && isFaithful(wire, person) ? wire : person;
|
|
127
|
+
}
|
|
128
|
+
})));
|
|
129
|
+
/**
|
|
130
|
+
* The shorthand text this person was decoded from, when it was decoded from
|
|
131
|
+
* the string form and still matches its fields; `None` for a person built
|
|
132
|
+
* from an object or by hand.
|
|
133
|
+
*
|
|
134
|
+
* Exposed so callers can tell which encoding a manifest used without
|
|
135
|
+
* re-reading the file.
|
|
136
|
+
*
|
|
137
|
+
* @param person - the person to inspect
|
|
138
|
+
* @returns the original shorthand text, or `None`
|
|
46
139
|
*/
|
|
47
|
-
static
|
|
140
|
+
static wireStringOf(person) {
|
|
141
|
+
const wire = wireForms.get(person);
|
|
142
|
+
return typeof wire === "string" && isFaithful(wire, person) ? Option.some(wire) : Option.none();
|
|
143
|
+
}
|
|
48
144
|
};
|
|
49
145
|
|
|
50
146
|
//#endregion
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CatalogResolver, DependencyKind, DependencyProtocol, DependencyProtocol as DependencyProtocol$1, DependencySpecifier, DependencySpecifierBrand, InvalidDependencySpecifierError, WorkspaceResolver, isValidDependencySpecifier } from "@effected/npm";
|
|
2
2
|
import { InvalidVersionError, Range, SemVer } from "@effected/semver";
|
|
3
|
-
import { Brand, Context, Effect, FileSystem, HashMap, Layer, Option, Path, Schema } from "effect";
|
|
3
|
+
import { Brand, Context, Effect, FileSystem, HashMap, Layer, Option, Path, Result, Schema } from "effect";
|
|
4
4
|
//#region src/Dependency.d.ts
|
|
5
5
|
declare const Dependency_base: Schema.Class<Dependency, Schema.Struct<{
|
|
6
6
|
/** The package name. */
|
|
@@ -236,23 +236,60 @@ declare const Person_base: Schema.Class<Person, Schema.Struct<{
|
|
|
236
236
|
readonly email: Schema.optionalKey<Schema.String>;
|
|
237
237
|
/** The optional homepage URL. */
|
|
238
238
|
readonly url: Schema.optionalKey<Schema.String>;
|
|
239
|
+
/** Any additional keys, preserved verbatim and flattened back on encode. */
|
|
240
|
+
readonly rest: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
239
241
|
}>, {}>;
|
|
240
242
|
/**
|
|
241
|
-
* A structured person object with `name
|
|
243
|
+
* A structured person object with `name`, optional `email` / `url`, and a
|
|
244
|
+
* `rest` catch-all preserving any additional keys across a read/write cycle.
|
|
242
245
|
*
|
|
243
246
|
* @public
|
|
244
247
|
*/
|
|
245
248
|
declare class Person extends Person_base {
|
|
249
|
+
/**
|
|
250
|
+
* The object wire codec: an open JSON object ↔ a {@link Person}, partitioning
|
|
251
|
+
* unknown keys into `rest` and flattening them back on encode so the on-disk
|
|
252
|
+
* shape never carries a literal `rest` key.
|
|
253
|
+
*/
|
|
254
|
+
static readonly schema: Schema.Codec<Person, {
|
|
255
|
+
readonly [k: string]: unknown;
|
|
256
|
+
}>;
|
|
246
257
|
/**
|
|
247
258
|
* Schema transformation between the `"Name <email> (url)"` shorthand string
|
|
248
|
-
* and a {@link Person}.
|
|
259
|
+
* and a {@link Person}. Decoding remembers the input text so that encoding
|
|
260
|
+
* reproduces it verbatim; see {@link Person.wireStringOf}.
|
|
249
261
|
*/
|
|
250
262
|
static readonly FromString: Schema.Codec<Person, string>;
|
|
251
263
|
/**
|
|
252
264
|
* The `author` / `contributors` value: either the shorthand string or the
|
|
253
265
|
* structured object, always decoded to a {@link Person}.
|
|
266
|
+
*
|
|
267
|
+
* The wire form is preserved across a round trip — a person read from the
|
|
268
|
+
* shorthand string encodes back to that string, byte for byte, and one read
|
|
269
|
+
* from an object encodes back to an object with its unknown keys intact.
|
|
270
|
+
* Formatting a manifest therefore never rewrites one legal encoding into the
|
|
271
|
+
* other.
|
|
272
|
+
*
|
|
273
|
+
* Provenance belongs to the instance, so a person that is *rebuilt* (rather
|
|
274
|
+
* than carried through unchanged) has none and encodes in the canonical
|
|
275
|
+
* object form. Editing an unrelated field of the surrounding `Package`
|
|
276
|
+
* carries the same person instance through and preserves its encoding.
|
|
277
|
+
*/
|
|
278
|
+
static readonly FromValue: Schema.Codec<Person, string | {
|
|
279
|
+
readonly [k: string]: unknown;
|
|
280
|
+
}>;
|
|
281
|
+
/**
|
|
282
|
+
* The shorthand text this person was decoded from, when it was decoded from
|
|
283
|
+
* the string form and still matches its fields; `None` for a person built
|
|
284
|
+
* from an object or by hand.
|
|
285
|
+
*
|
|
286
|
+
* Exposed so callers can tell which encoding a manifest used without
|
|
287
|
+
* re-reading the file.
|
|
288
|
+
*
|
|
289
|
+
* @param person - the person to inspect
|
|
290
|
+
* @returns the original shorthand text, or `None`
|
|
254
291
|
*/
|
|
255
|
-
static
|
|
292
|
+
static wireStringOf(person: Person): Option.Option<string>;
|
|
256
293
|
}
|
|
257
294
|
//#endregion
|
|
258
295
|
//#region src/Package.d.ts
|
|
@@ -379,8 +416,12 @@ declare const Package_base: Schema.Class<Package, Schema.Struct<{
|
|
|
379
416
|
readonly type: Schema.optionalKey<Schema.Literals<readonly ["module", "commonjs"]>>;
|
|
380
417
|
readonly main: Schema.optionalKey<Schema.String>;
|
|
381
418
|
readonly license: Schema.optionalKey<Schema.brand<Schema.String, "SpdxLicense">>;
|
|
382
|
-
readonly author: Schema.optionalKey<Schema.
|
|
383
|
-
|
|
419
|
+
readonly author: Schema.optionalKey<Schema.Codec<Person, string | {
|
|
420
|
+
readonly [k: string]: unknown;
|
|
421
|
+
}, never, never>>;
|
|
422
|
+
readonly contributors: Schema.optionalKey<Schema.$Array<Schema.Codec<Person, string | {
|
|
423
|
+
readonly [k: string]: unknown;
|
|
424
|
+
}, never, never>>>;
|
|
384
425
|
readonly repository: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>]>>;
|
|
385
426
|
readonly dependencies: Schema.decodeTo<Schema.HashMap<Schema.String, Schema.String>, Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>, never, never>;
|
|
386
427
|
readonly devDependencies: Schema.decodeTo<Schema.HashMap<Schema.String, Schema.String>, Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>, never, never>;
|
|
@@ -678,6 +719,148 @@ declare class PackageJsonFile extends PackageJsonFile_base {
|
|
|
678
719
|
static readonly layer: Layer.Layer<PackageJsonFile, never, FileSystem.FileSystem | Path.Path>;
|
|
679
720
|
}
|
|
680
721
|
//#endregion
|
|
722
|
+
//#region src/PackageJsonFormat.d.ts
|
|
723
|
+
declare const PackageJsonSyntaxError_base: Schema.Class<PackageJsonSyntaxError, Schema.TaggedStruct<"PackageJsonSyntaxError", {
|
|
724
|
+
/** Which syntactic precondition failed. */
|
|
725
|
+
readonly reason: Schema.Literals<readonly ["invalid-json", "not-an-object"]>;
|
|
726
|
+
/** The underlying `SyntaxError` for `"invalid-json"`, preserved structurally. */
|
|
727
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
728
|
+
}>, import("effect/Cause").YieldableError>;
|
|
729
|
+
/**
|
|
730
|
+
* Indicates that a text input could not be treated as a package.json document:
|
|
731
|
+
* either it is not valid JSON (`"invalid-json"`, carrying the underlying
|
|
732
|
+
* `SyntaxError` on `cause`) or it parsed to something other than a JSON object
|
|
733
|
+
* (`"not-an-object"` — an array, a scalar or `null`).
|
|
734
|
+
*
|
|
735
|
+
* Raised by {@link PackageJsonFormat.formatToString}. This is a *syntactic*
|
|
736
|
+
* failure only; it says nothing about whether the document is a valid package
|
|
737
|
+
* manifest, which the decode-free path deliberately does not check.
|
|
738
|
+
*
|
|
739
|
+
* @public
|
|
740
|
+
*/
|
|
741
|
+
declare class PackageJsonSyntaxError extends PackageJsonSyntaxError_base {
|
|
742
|
+
get message(): string;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Options for {@link PackageJsonFormat.formatToString}.
|
|
746
|
+
*
|
|
747
|
+
* Deliberately not `PackageFormatOptions`: there is no `sourceText` member,
|
|
748
|
+
* because the text being formatted *is* the source text, and the defaults for
|
|
749
|
+
* `indent` and `stripEmpty` differ — see each member.
|
|
750
|
+
*
|
|
751
|
+
* @public
|
|
752
|
+
*/
|
|
753
|
+
interface PackageFormatTextOptions {
|
|
754
|
+
/**
|
|
755
|
+
* Indentation: a spaces count, `"tab"`, or `"preserve"`. Defaults to
|
|
756
|
+
* `"preserve"` — unlike `Package.toJsonString`, this path always has the
|
|
757
|
+
* original text in hand, and reformatting a file in place should not
|
|
758
|
+
* silently restyle its indentation.
|
|
759
|
+
*/
|
|
760
|
+
readonly indent?: number | "tab" | "preserve";
|
|
761
|
+
/** Order top-level keys canonically and alphabetize dependency maps (default `true`). */
|
|
762
|
+
readonly sort?: boolean;
|
|
763
|
+
/**
|
|
764
|
+
* Strip dependency-map keys whose value is an empty object (default
|
|
765
|
+
* `false`). The strict path defaults this on because the model materializes
|
|
766
|
+
* absent maps as empty ones; here an empty map is a key the author actually
|
|
767
|
+
* wrote, and removing it would be a silent edit rather than a format.
|
|
768
|
+
*/
|
|
769
|
+
readonly stripEmpty?: boolean;
|
|
770
|
+
/** Append a trailing newline (default `true`). */
|
|
771
|
+
readonly newline?: boolean;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Decode-free canonical sort and format statics. Not instantiable.
|
|
775
|
+
*
|
|
776
|
+
* @remarks
|
|
777
|
+
* The guarantee both statics make is that they are **source-preserving**:
|
|
778
|
+
* neither decodes into a `Package`, so neither can normalize a field encoding.
|
|
779
|
+
* String-form `author` shorthand, unknown fields, unusual value shapes and
|
|
780
|
+
* empty maps all survive untouched, because they are never looked at. Key
|
|
781
|
+
* order, indentation and the trailing newline are the only things that change.
|
|
782
|
+
*
|
|
783
|
+
* That is what makes this usable as a lint-hook handler where the strict path
|
|
784
|
+
* is not: any syntactically valid JSON object formats, including the
|
|
785
|
+
* version-less workspace roots and `{"private": true}` manifests that
|
|
786
|
+
* `Package.decode` rejects. Reach for `Package.decode` +
|
|
787
|
+
* `Package.toJsonString` instead when the job needs the validated model and an
|
|
788
|
+
* invalid manifest should fail loudly.
|
|
789
|
+
*
|
|
790
|
+
* @public
|
|
791
|
+
*/
|
|
792
|
+
declare class PackageJsonFormat {
|
|
793
|
+
private constructor();
|
|
794
|
+
/**
|
|
795
|
+
* Order a package.json object's keys canonically **without decoding it into
|
|
796
|
+
* a `Package`**: known top-level keys in `sort-package-json`'s order, then
|
|
797
|
+
* unknown public keys alphabetically, then `_`-prefixed keys, with the
|
|
798
|
+
* dependency maps and `scripts` / `engines` / `bin` alphabetized.
|
|
799
|
+
*
|
|
800
|
+
* Value in, value out — for hosts that already hold parsed JSON and never
|
|
801
|
+
* want a string. {@link PackageJsonFormat.formatToString} is the same
|
|
802
|
+
* ordering for hosts holding file text. Pure and total.
|
|
803
|
+
*
|
|
804
|
+
* Returns a new object; nested values are shared by reference rather than
|
|
805
|
+
* cloned, except the maps whose own keys are reordered. A value that is not
|
|
806
|
+
* a JSON object (an array, a scalar, `null`) is returned unchanged rather
|
|
807
|
+
* than mangled, so a mistyped `Json` union cannot silently lose data.
|
|
808
|
+
*
|
|
809
|
+
* Reordering keys is the whole of it — **no key is ever added or removed**,
|
|
810
|
+
* which is what lets the return type be the input type `T` and makes this a
|
|
811
|
+
* drop-in. Use {@link PackageJsonFormat.formatToString} with `stripEmpty`
|
|
812
|
+
* when removing empty maps is wanted; it returns a string and so carries no
|
|
813
|
+
* such obligation.
|
|
814
|
+
*
|
|
815
|
+
* @param value - the parsed package.json object
|
|
816
|
+
* @returns a new object with canonically ordered keys
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```ts
|
|
820
|
+
* import { PackageJsonFormat } from "@effected/package-json";
|
|
821
|
+
*
|
|
822
|
+
* const sorted = PackageJsonFormat.sortValue({ version: "1.0.0", name: "p" });
|
|
823
|
+
* // => { name: "p", version: "1.0.0" }
|
|
824
|
+
* ```
|
|
825
|
+
*/
|
|
826
|
+
static sortValue<T extends {
|
|
827
|
+
readonly [k: string]: unknown;
|
|
828
|
+
}>(value: T): T;
|
|
829
|
+
/**
|
|
830
|
+
* Sort and format package.json text **without decoding it into a
|
|
831
|
+
* `Package`**. Text in, text out — for hosts that hold file contents and
|
|
832
|
+
* cannot afford a decode. {@link PackageJsonFormat.sortValue} is the same
|
|
833
|
+
* ordering for hosts that already hold parsed JSON.
|
|
834
|
+
*
|
|
835
|
+
* Any syntactically valid JSON object formats, whatever it contains: a
|
|
836
|
+
* version-less root, `{"private": true}`, a malformed `packageManager`
|
|
837
|
+
* integrity. Nothing is decoded, so nothing is normalized — string-form
|
|
838
|
+
* `author` shorthand, unknown fields, unusual value shapes and empty maps
|
|
839
|
+
* all survive byte-for-byte. Only key order, indentation and the trailing
|
|
840
|
+
* newline change.
|
|
841
|
+
*
|
|
842
|
+
* Pure and synchronous: it returns a `Result` rather than an `Effect`, so
|
|
843
|
+
* synchronous hosts can call it directly. Lift it with `Effect.fromResult`.
|
|
844
|
+
*
|
|
845
|
+
* @param source - the package.json file contents
|
|
846
|
+
* @param options - formatting options; see {@link PackageFormatTextOptions}
|
|
847
|
+
* @returns the formatted text, or a {@link PackageJsonSyntaxError}
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* ```ts
|
|
851
|
+
* import { PackageJsonFormat } from "@effected/package-json";
|
|
852
|
+
* import { Effect, Result } from "effect";
|
|
853
|
+
*
|
|
854
|
+
* const formatted = PackageJsonFormat.formatToString('{"private": true}');
|
|
855
|
+
* if (Result.isSuccess(formatted)) console.log(formatted.success);
|
|
856
|
+
*
|
|
857
|
+
* // In an Effect program:
|
|
858
|
+
* const program = Effect.fromResult(PackageJsonFormat.formatToString('{"private": true}'));
|
|
859
|
+
* ```
|
|
860
|
+
*/
|
|
861
|
+
static formatToString(source: string, options?: PackageFormatTextOptions): Result.Result<string, PackageJsonSyntaxError>;
|
|
862
|
+
}
|
|
863
|
+
//#endregion
|
|
681
864
|
//#region src/PackageValidator.d.ts
|
|
682
865
|
/**
|
|
683
866
|
* A single validation-rule failure.
|
|
@@ -777,5 +960,5 @@ declare class PackageValidator extends PackageValidator_base {
|
|
|
777
960
|
}): Layer.Layer<PackageValidator>;
|
|
778
961
|
}
|
|
779
962
|
//#endregion
|
|
780
|
-
export { BinField, Dependency, type DependencyKind, DependencyMapField, type DependencyProtocol, DependencySpecifier, type DependencySpecifierBrand, DevEngine, DevEngineOrArray, type DevEngines, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, Package, PackageDecodeError, type PackageFormatOptions, type PackageIndent, PackageJsonFile, type PackageJsonFileShape, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonWriteError, PackageManager, PackageName, type PackagePatch, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, RepositoryField, type RuleFailure, ScopedPackageName, SpdxLicense, StringMapField, type UnresolvedDependency, UnscopedPackageName, type ValidationRule, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule };
|
|
963
|
+
export { BinField, Dependency, type DependencyKind, DependencyMapField, type DependencyProtocol, DependencySpecifier, type DependencySpecifierBrand, DevEngine, DevEngineOrArray, type DevEngines, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, Package, PackageDecodeError, type PackageFormatOptions, type PackageFormatTextOptions, type PackageIndent, PackageJsonFile, type PackageJsonFileShape, PackageJsonFormat, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonSyntaxError, PackageJsonWriteError, PackageManager, PackageName, type PackagePatch, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, RepositoryField, type RuleFailure, ScopedPackageName, SpdxLicense, StringMapField, type UnresolvedDependency, UnscopedPackageName, type ValidationRule, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule };
|
|
781
964
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -6,7 +6,8 @@ import { InvalidPackageNameError, PackageName, ScopedPackageName, UnscopedPackag
|
|
|
6
6
|
import { Person } from "./Person.js";
|
|
7
7
|
import { BinField, DependencyMapField, ExportsField, Package, PackageDecodeError, PeerDependenciesMetaField, PublishConfigField, RepositoryField, StringMapField } from "./Package.js";
|
|
8
8
|
import { PackageJsonFile, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonWriteError } from "./PackageJsonFile.js";
|
|
9
|
+
import { PackageJsonFormat, PackageJsonSyntaxError } from "./PackageJsonFormat.js";
|
|
9
10
|
import { PackageValidationError, PackageValidator, defaultRules, noLocalDepsRule, noUnresolvedDepsRule } from "./PackageValidator.js";
|
|
10
11
|
import { DependencySpecifier, InvalidDependencySpecifierError, isValidDependencySpecifier } from "@effected/npm";
|
|
11
12
|
|
|
12
|
-
export { BinField, Dependency, DependencyMapField, DependencySpecifier, DevEngine, DevEngineOrArray, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, Package, PackageDecodeError, PackageJsonFile, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonWriteError, PackageManager, PackageName, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, RepositoryField, ScopedPackageName, SpdxLicense, StringMapField, UnscopedPackageName, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule };
|
|
13
|
+
export { BinField, Dependency, DependencyMapField, DependencySpecifier, DevEngine, DevEngineOrArray, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, Package, PackageDecodeError, PackageJsonFile, PackageJsonFormat, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonSyntaxError, PackageJsonWriteError, PackageManager, PackageName, PackageValidationError, PackageValidator, PeerDependenciesMetaField, Person, PublishConfigField, RepositoryField, ScopedPackageName, SpdxLicense, StringMapField, UnscopedPackageName, defaultRules, isUnresolvedDependency, isValidDependencySpecifier, isValidSpdx, noLocalDepsRule, noUnresolvedDepsRule };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/package-json",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "package.json parsing, editing, validation and file IO as Effect schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"./package.json": "./package.json"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@effected/npm": "0.2.
|
|
42
|
-
"@effected/semver": "0.
|
|
41
|
+
"@effected/npm": "~0.2.3",
|
|
42
|
+
"@effected/semver": "~0.2.0",
|
|
43
43
|
"spdx-expression-parse": "^4.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|