@effected/package-json 0.1.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/Dependency.js +76 -0
- package/DevEngines.js +42 -0
- package/LICENSE +21 -0
- package/License.js +45 -0
- package/Package.js +329 -0
- package/PackageJsonFile.js +128 -0
- package/PackageManager.js +63 -0
- package/PackageName.js +62 -0
- package/PackageValidator.js +146 -0
- package/Person.js +51 -0
- package/README.md +215 -0
- package/index.d.ts +747 -0
- package/index.js +12 -0
- package/internal/format.js +106 -0
- package/package.json +50 -0
- package/tsdoc-metadata.json +11 -0
package/Dependency.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DependencyKind, DependencySpecifier } from "@effected/npm";
|
|
2
|
+
import { Option, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/Dependency.ts
|
|
5
|
+
/**
|
|
6
|
+
* A resolved dependency entry pairing a package name with its version
|
|
7
|
+
* specifier and the `kind` of map it came from (`@effected/npm`'s
|
|
8
|
+
* `DependencyKind`). The protocol predicates delegate to `DependencySpecifier`.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
var Dependency = class extends Schema.Class("Dependency")({
|
|
13
|
+
/** The package name. */
|
|
14
|
+
name: Schema.String,
|
|
15
|
+
/** The raw version specifier. */
|
|
16
|
+
specifier: Schema.String,
|
|
17
|
+
/** Which dependency map this entry came from. */
|
|
18
|
+
kind: DependencyKind,
|
|
19
|
+
/** For `peer` dependencies, whether the peer is optional (from `peerDependenciesMeta`). */
|
|
20
|
+
isOptional: Schema.optionalKey(Schema.Boolean)
|
|
21
|
+
}) {
|
|
22
|
+
/** The classified protocol, or `None` for an empty specifier. */
|
|
23
|
+
get protocol() {
|
|
24
|
+
return this.specifier.length === 0 ? Option.none() : Option.some(DependencySpecifier.protocolOf(this.specifier));
|
|
25
|
+
}
|
|
26
|
+
/** Parse the specifier as a semver `Range`, `None` when it is not a range. */
|
|
27
|
+
get range() {
|
|
28
|
+
return DependencySpecifier.parseRange(this.specifier);
|
|
29
|
+
}
|
|
30
|
+
/** Whether the specifier points to a local path. */
|
|
31
|
+
get isLocal() {
|
|
32
|
+
return DependencySpecifier.isLocal(this.specifier);
|
|
33
|
+
}
|
|
34
|
+
/** Whether the specifier uses the `link:` protocol. */
|
|
35
|
+
get isLink() {
|
|
36
|
+
return DependencySpecifier.isLink(this.specifier);
|
|
37
|
+
}
|
|
38
|
+
/** Whether the specifier uses the `portal:` protocol. */
|
|
39
|
+
get isPortal() {
|
|
40
|
+
return DependencySpecifier.isPortal(this.specifier);
|
|
41
|
+
}
|
|
42
|
+
/** Whether the specifier uses the `catalog:` protocol. */
|
|
43
|
+
get isCatalog() {
|
|
44
|
+
return DependencySpecifier.isCatalog(this.specifier);
|
|
45
|
+
}
|
|
46
|
+
/** Whether the specifier uses the `workspace:` protocol. */
|
|
47
|
+
get isWorkspace() {
|
|
48
|
+
return DependencySpecifier.isWorkspace(this.specifier);
|
|
49
|
+
}
|
|
50
|
+
/** Whether the specifier is an unresolved `catalog:` or `workspace:` protocol. */
|
|
51
|
+
get isUnresolved() {
|
|
52
|
+
return this.isCatalog || this.isWorkspace;
|
|
53
|
+
}
|
|
54
|
+
/** Whether the specifier resolves to a git source. */
|
|
55
|
+
get isGit() {
|
|
56
|
+
return DependencySpecifier.isGit(this.specifier);
|
|
57
|
+
}
|
|
58
|
+
/** Whether the specifier is a parseable semver range. */
|
|
59
|
+
get isRange() {
|
|
60
|
+
return DependencySpecifier.isRange(this.specifier);
|
|
61
|
+
}
|
|
62
|
+
/** Whether the specifier is a dist-tag. */
|
|
63
|
+
get isTag() {
|
|
64
|
+
return DependencySpecifier.isTag(this.specifier);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Type guard narrowing any dependency-like value to
|
|
69
|
+
* {@link UnresolvedDependency}, preserving the concrete type.
|
|
70
|
+
*
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
73
|
+
const isUnresolvedDependency = (dependency) => dependency.isUnresolved === true;
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { Dependency, isUnresolvedDependency };
|
package/DevEngines.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/DevEngines.ts
|
|
4
|
+
/**
|
|
5
|
+
* A single `devEngines` constraint with a name and optional `version` / `onFail`.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
var DevEngine = class extends Schema.Class("DevEngine")({
|
|
10
|
+
/** The engine name (e.g. `node`, `pnpm`). */
|
|
11
|
+
name: Schema.String,
|
|
12
|
+
/** The optional version constraint. */
|
|
13
|
+
version: Schema.optionalKey(Schema.String),
|
|
14
|
+
/** The optional behavior when the constraint is unmet. */
|
|
15
|
+
onFail: Schema.optionalKey(Schema.Literals([
|
|
16
|
+
"warn",
|
|
17
|
+
"error",
|
|
18
|
+
"ignore"
|
|
19
|
+
]))
|
|
20
|
+
}) {};
|
|
21
|
+
/**
|
|
22
|
+
* A `devEngines` constraint slot: a single {@link DevEngine} or an array of them.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
const DevEngineOrArray = Schema.Union([DevEngine, Schema.Array(DevEngine)]);
|
|
27
|
+
/**
|
|
28
|
+
* The `devEngines` field schema, modeling runtime and package-manager
|
|
29
|
+
* constraints as optional {@link DevEngine} slots.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
const DevEnginesSchema = Schema.Struct({
|
|
34
|
+
packageManager: Schema.optionalKey(DevEngineOrArray),
|
|
35
|
+
runtime: Schema.optionalKey(DevEngineOrArray),
|
|
36
|
+
os: Schema.optionalKey(DevEngineOrArray),
|
|
37
|
+
cpu: Schema.optionalKey(DevEngineOrArray),
|
|
38
|
+
libc: Schema.optionalKey(DevEngineOrArray)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { DevEngine, DevEngineOrArray, DevEnginesSchema };
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C. Spencer Beggs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/License.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import spdxParse from "spdx-expression-parse";
|
|
3
|
+
|
|
4
|
+
//#region src/License.ts
|
|
5
|
+
/**
|
|
6
|
+
* Indicates that a string is not a valid SPDX license identifier or expression.
|
|
7
|
+
*
|
|
8
|
+
* Raised by {@link Package.setLicense} and the decode direction of
|
|
9
|
+
* `SpdxLicense`. The offending string is preserved on `input`.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
var InvalidSpdxLicenseError = class extends Schema.TaggedErrorClass()("InvalidSpdxLicenseError", {
|
|
14
|
+
/** The raw input string that failed validation. */
|
|
15
|
+
input: Schema.String }) {
|
|
16
|
+
get message() {
|
|
17
|
+
return `Invalid SPDX license "${this.input}": not a recognized identifier or expression`;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Whether a string is a valid SPDX license identifier or expression, or one of
|
|
22
|
+
* the npm special cases `UNLICENSED` / `SEE LICENSE IN <file>`.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
const isValidSpdx = (value) => {
|
|
27
|
+
if (value === "UNLICENSED") return true;
|
|
28
|
+
if (value.startsWith("SEE LICENSE IN ") && value.length > 15) return true;
|
|
29
|
+
try {
|
|
30
|
+
spdxParse(value);
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A valid SPDX license identifier, expression, `UNLICENSED`, or
|
|
38
|
+
* `SEE LICENSE IN <file>`.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
const SpdxLicense = Schema.String.pipe(Schema.check(Schema.makeFilter((value) => isValidSpdx(value) ? void 0 : "Expected a valid SPDX license expression")), Schema.brand("SpdxLicense"));
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { InvalidSpdxLicenseError, SpdxLicense, isValidSpdx };
|
package/Package.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { Dependency } from "./Dependency.js";
|
|
2
|
+
import { DevEnginesSchema } from "./DevEngines.js";
|
|
3
|
+
import { InvalidSpdxLicenseError, SpdxLicense, isValidSpdx } from "./License.js";
|
|
4
|
+
import { renderJson } from "./internal/format.js";
|
|
5
|
+
import { PackageManager } from "./PackageManager.js";
|
|
6
|
+
import { InvalidPackageNameError, PackageName } from "./PackageName.js";
|
|
7
|
+
import { Person } from "./Person.js";
|
|
8
|
+
import { CatalogResolver, WorkspaceResolver } from "@effected/npm";
|
|
9
|
+
import { Effect, Function, HashMap, Option, Pipeable, Schema, SchemaTransformation } from "effect";
|
|
10
|
+
import { SemVer } from "@effected/semver";
|
|
11
|
+
|
|
12
|
+
//#region src/Package.ts
|
|
13
|
+
const toHashMap = SchemaTransformation.transform({
|
|
14
|
+
decode: (record) => HashMap.fromIterable(Object.entries(record)),
|
|
15
|
+
encode: (map) => Object.fromEntries(HashMap.toEntries(map))
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* A string→string map field decoding a plain JSON object to a `HashMap`,
|
|
19
|
+
* defaulting to an empty map when the key is absent. Backs the four dependency
|
|
20
|
+
* maps and `scripts`. Not meant to be referenced directly.
|
|
21
|
+
*
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
const DependencyMapField = Schema.Record(Schema.String, Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed({})), Schema.decodeTo(Schema.HashMap(Schema.String, Schema.String), toHashMap));
|
|
25
|
+
/**
|
|
26
|
+
* A string→string map field decoding a plain JSON object to a `HashMap`,
|
|
27
|
+
* with no default (an absent key stays absent). Backs `engines`. Not meant to
|
|
28
|
+
* be referenced directly.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
const StringMapField = Schema.Record(Schema.String, Schema.String).pipe(Schema.decodeTo(Schema.HashMap(Schema.String, Schema.String), toHashMap));
|
|
33
|
+
/**
|
|
34
|
+
* The `bin` field: a single string path or a name→path map. Not meant to be
|
|
35
|
+
* referenced directly.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
const BinField = Schema.Union([Schema.String, StringMapField]);
|
|
40
|
+
/**
|
|
41
|
+
* The `exports` field: a single string entry point or an open object of
|
|
42
|
+
* conditional exports. Not meant to be referenced directly.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
const ExportsField = Schema.Union([Schema.String, Schema.Record(Schema.String, Schema.Unknown)]);
|
|
47
|
+
/**
|
|
48
|
+
* The `publishConfig` field: an open record preserving known npm keys
|
|
49
|
+
* (`access`, `directory`, ...) plus extensions like `targets`. Not meant to be
|
|
50
|
+
* referenced directly.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
const PublishConfigField = Schema.Record(Schema.String, Schema.Unknown);
|
|
55
|
+
/**
|
|
56
|
+
* The `peerDependenciesMeta` field: a map of package name to `{ optional? }`.
|
|
57
|
+
* Not meant to be referenced directly.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
const PeerDependenciesMetaField = Schema.Record(Schema.String, Schema.Struct({ optional: Schema.optionalKey(Schema.Boolean) }));
|
|
62
|
+
/**
|
|
63
|
+
* The `repository` field: a shorthand string or an object (with `type` / `url` /
|
|
64
|
+
* `directory` and any extensions preserved). Not meant to be referenced
|
|
65
|
+
* directly.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
const RepositoryField = Schema.Union([Schema.String, Schema.Record(Schema.String, Schema.Unknown)]);
|
|
70
|
+
/**
|
|
71
|
+
* Indicates that a JSON value could not be decoded into a valid {@link Package}.
|
|
72
|
+
*
|
|
73
|
+
* Raised by {@link Package.decode}. The underlying `SchemaError` is preserved on
|
|
74
|
+
* the structured `cause` field (never stringified), so callers keep the issue
|
|
75
|
+
* tree for diagnostics.
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
var PackageDecodeError = class extends Schema.TaggedErrorClass()("PackageDecodeError", {
|
|
80
|
+
/** The underlying `SchemaError`, preserved structurally rather than stringified. */
|
|
81
|
+
cause: Schema.Defect() }) {
|
|
82
|
+
get message() {
|
|
83
|
+
return "Failed to decode package.json";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const resolveFormatOptions = (options) => ({
|
|
87
|
+
indent: options?.indent ?? 2,
|
|
88
|
+
sort: options?.sort ?? true,
|
|
89
|
+
stripEmpty: options?.stripEmpty ?? true,
|
|
90
|
+
newline: options?.newline ?? true
|
|
91
|
+
});
|
|
92
|
+
const RawJson = Schema.Record(Schema.String, Schema.Unknown);
|
|
93
|
+
const makeWire = (Class) => {
|
|
94
|
+
const knownKeys = new Set(Object.keys(Class.fields).filter((k) => k !== "rest"));
|
|
95
|
+
return RawJson.pipe(Schema.decodeTo(Class, SchemaTransformation.transform({
|
|
96
|
+
decode: (raw) => {
|
|
97
|
+
const known = {};
|
|
98
|
+
const rest = {};
|
|
99
|
+
for (const [key, value] of Object.entries(raw)) if (knownKeys.has(key)) known[key] = value;
|
|
100
|
+
else rest[key] = value;
|
|
101
|
+
return {
|
|
102
|
+
...known,
|
|
103
|
+
rest
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
encode: (encoded) => {
|
|
107
|
+
const { rest, ...known } = encoded;
|
|
108
|
+
return {
|
|
109
|
+
...known,
|
|
110
|
+
...rest ?? {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
})));
|
|
114
|
+
};
|
|
115
|
+
const applyWorkspaceModifier = (specifier, version) => {
|
|
116
|
+
const modifier = specifier.slice(10);
|
|
117
|
+
if (modifier === "*" || modifier === "") return version;
|
|
118
|
+
if (modifier === "^") return `^${version}`;
|
|
119
|
+
if (modifier === "~") return `~${version}`;
|
|
120
|
+
return modifier;
|
|
121
|
+
};
|
|
122
|
+
const catalogName = (specifier) => {
|
|
123
|
+
const name = specifier.slice(8);
|
|
124
|
+
return name.length === 0 ? Option.none() : Option.some(name);
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* A package.json document as a rich `Schema.Class`: typed known fields, a
|
|
128
|
+
* `rest` catch-all preserving unknown top-level fields across a read/edit/write
|
|
129
|
+
* cycle, computed getters, and immutable mutation statics.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* import { Package } from "@effected/package-json";
|
|
134
|
+
* import { Effect } from "effect";
|
|
135
|
+
*
|
|
136
|
+
* const program = Effect.gen(function* () {
|
|
137
|
+
* const pkg = yield* Package.decode({ name: "my-pkg", version: "1.0.0" });
|
|
138
|
+
* const next = yield* Package.setVersion(pkg, "1.1.0");
|
|
139
|
+
* console.log(next.toJsonString());
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @public
|
|
144
|
+
*/
|
|
145
|
+
var Package = class Package extends Schema.Class("Package")({
|
|
146
|
+
name: PackageName,
|
|
147
|
+
version: SemVer.FromString,
|
|
148
|
+
description: Schema.optionalKey(Schema.String),
|
|
149
|
+
private: Schema.optionalKey(Schema.Boolean),
|
|
150
|
+
type: Schema.optionalKey(Schema.Literals(["module", "commonjs"])),
|
|
151
|
+
main: Schema.optionalKey(Schema.String),
|
|
152
|
+
license: Schema.optionalKey(SpdxLicense),
|
|
153
|
+
author: Schema.optionalKey(Person.FromValue),
|
|
154
|
+
contributors: Schema.optionalKey(Schema.Array(Person.FromValue)),
|
|
155
|
+
repository: Schema.optionalKey(RepositoryField),
|
|
156
|
+
dependencies: DependencyMapField,
|
|
157
|
+
devDependencies: DependencyMapField,
|
|
158
|
+
peerDependencies: DependencyMapField,
|
|
159
|
+
optionalDependencies: DependencyMapField,
|
|
160
|
+
peerDependenciesMeta: Schema.optionalKey(PeerDependenciesMetaField),
|
|
161
|
+
scripts: DependencyMapField,
|
|
162
|
+
bin: Schema.optionalKey(BinField),
|
|
163
|
+
engines: Schema.optionalKey(StringMapField),
|
|
164
|
+
exports: Schema.optionalKey(ExportsField),
|
|
165
|
+
publishConfig: Schema.optionalKey(PublishConfigField),
|
|
166
|
+
packageManager: Schema.optionalKey(PackageManager.FromString),
|
|
167
|
+
devEngines: Schema.optionalKey(DevEnginesSchema),
|
|
168
|
+
rest: Schema.optionalKey(Schema.Record(Schema.String, Schema.Unknown))
|
|
169
|
+
}) {
|
|
170
|
+
pipe() {
|
|
171
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The default wire codec: an open JSON object ↔ a {@link Package} instance,
|
|
175
|
+
* partitioning unknown keys into `rest` and flattening them back on encode.
|
|
176
|
+
*/
|
|
177
|
+
static schema = makeWire(Package);
|
|
178
|
+
/**
|
|
179
|
+
* Build the wire codec for a `.extend()`ed subclass, so its custom fields
|
|
180
|
+
* decode as typed members and are excluded from `rest`.
|
|
181
|
+
*
|
|
182
|
+
* @param Class - the extended `Schema.Class`, carrying its own `fields`
|
|
183
|
+
* @returns a codec between an open JSON object and `Class` instances
|
|
184
|
+
*/
|
|
185
|
+
static wireFor(Class) {
|
|
186
|
+
return makeWire(Class);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Decode an unknown JSON value into a {@link Package}, normalizing any
|
|
190
|
+
* `SchemaError` to a typed {@link PackageDecodeError} at the boundary.
|
|
191
|
+
*
|
|
192
|
+
* @param input - the parsed package.json JSON value (e.g. from `JSON.parse`)
|
|
193
|
+
* @returns an Effect resolving to the decoded `Package`
|
|
194
|
+
* @throws (typed) `PackageDecodeError` when `input` does not satisfy the schema
|
|
195
|
+
*/
|
|
196
|
+
static decode = Effect.fn("Package.decode")(function* (input) {
|
|
197
|
+
return yield* Schema.decodeUnknownEffect(Package.schema)(input).pipe(Effect.catchTag("SchemaError", (cause) => new PackageDecodeError({ cause })));
|
|
198
|
+
});
|
|
199
|
+
/** Whether the package is marked private. */
|
|
200
|
+
get isPrivate() {
|
|
201
|
+
return this.private ?? false;
|
|
202
|
+
}
|
|
203
|
+
/** Whether the package name is scoped (`@scope/name`). */
|
|
204
|
+
get isScoped() {
|
|
205
|
+
return PackageName.isScoped(this.name);
|
|
206
|
+
}
|
|
207
|
+
/** Whether the package is ESM (`"type": "module"`). */
|
|
208
|
+
get isESM() {
|
|
209
|
+
return this.type === "module";
|
|
210
|
+
}
|
|
211
|
+
/** Whether any dependency map contains `name`. */
|
|
212
|
+
hasDependency(name) {
|
|
213
|
+
return HashMap.has(this.dependencies, name) || HashMap.has(this.devDependencies, name) || HashMap.has(this.peerDependencies, name) || HashMap.has(this.optionalDependencies, name);
|
|
214
|
+
}
|
|
215
|
+
/** The `dependencies` map as {@link Dependency} instances (`kind: "prod"`). */
|
|
216
|
+
getDependencies() {
|
|
217
|
+
return HashMap.map(this.dependencies, (specifier, name) => Dependency.make({
|
|
218
|
+
name,
|
|
219
|
+
specifier,
|
|
220
|
+
kind: "prod"
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
/** The `devDependencies` map as {@link Dependency} instances (`kind: "dev"`). */
|
|
224
|
+
getDevDependencies() {
|
|
225
|
+
return HashMap.map(this.devDependencies, (specifier, name) => Dependency.make({
|
|
226
|
+
name,
|
|
227
|
+
specifier,
|
|
228
|
+
kind: "dev"
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
/** The `peerDependencies` map as {@link Dependency} instances (`kind: "peer"`), carrying `isOptional` from `peerDependenciesMeta`. */
|
|
232
|
+
getPeerDependencies() {
|
|
233
|
+
const meta = this.peerDependenciesMeta;
|
|
234
|
+
return HashMap.map(this.peerDependencies, (specifier, name) => Dependency.make({
|
|
235
|
+
name,
|
|
236
|
+
specifier,
|
|
237
|
+
kind: "peer",
|
|
238
|
+
isOptional: meta?.[name]?.optional ?? false
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
/** The `optionalDependencies` map as {@link Dependency} instances (`kind: "optional"`). */
|
|
242
|
+
getOptionalDependencies() {
|
|
243
|
+
return HashMap.map(this.optionalDependencies, (specifier, name) => Dependency.make({
|
|
244
|
+
name,
|
|
245
|
+
specifier,
|
|
246
|
+
kind: "optional"
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
/** Return a new {@link Package} with the given fields replaced. */
|
|
250
|
+
copyWith(patch) {
|
|
251
|
+
return Package.make({
|
|
252
|
+
...this,
|
|
253
|
+
...patch
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/** Set the version from a string. Fails with `InvalidVersionError`. Dual API. */
|
|
257
|
+
static setVersion = Function.dual(2, Effect.fn("Package.setVersion")(function* (pkg, version) {
|
|
258
|
+
const semver = yield* SemVer.parse(version);
|
|
259
|
+
return pkg.copyWith({ version: semver });
|
|
260
|
+
}));
|
|
261
|
+
/** Set the package name. Fails with `InvalidPackageNameError`. Dual API. */
|
|
262
|
+
static setName = Function.dual(2, Effect.fn("Package.setName")(function* (pkg, name) {
|
|
263
|
+
if (!PackageName.isValid(name)) return yield* new InvalidPackageNameError({ input: name });
|
|
264
|
+
return pkg.copyWith({ name });
|
|
265
|
+
}));
|
|
266
|
+
/** Set the license from an SPDX string. Fails with `InvalidSpdxLicenseError`. Dual API. */
|
|
267
|
+
static setLicense = Function.dual(2, Effect.fn("Package.setLicense")(function* (pkg, license) {
|
|
268
|
+
if (!isValidSpdx(license)) return yield* new InvalidSpdxLicenseError({ input: license });
|
|
269
|
+
return pkg.copyWith({ license });
|
|
270
|
+
}));
|
|
271
|
+
/** Add or replace a `dependencies` entry. Dual API. */
|
|
272
|
+
static addDependency = Function.dual(3, (pkg, name, specifier) => pkg.copyWith({ dependencies: HashMap.set(pkg.dependencies, name, specifier) }));
|
|
273
|
+
/** Remove a `dependencies` entry. Dual API. */
|
|
274
|
+
static removeDependency = Function.dual(2, (pkg, name) => pkg.copyWith({ dependencies: HashMap.remove(pkg.dependencies, name) }));
|
|
275
|
+
/** Add or replace a `devDependencies` entry. Dual API. */
|
|
276
|
+
static addDevDependency = Function.dual(3, (pkg, name, specifier) => pkg.copyWith({ devDependencies: HashMap.set(pkg.devDependencies, name, specifier) }));
|
|
277
|
+
/** Remove a `devDependencies` entry. Dual API. */
|
|
278
|
+
static removeDevDependency = Function.dual(2, (pkg, name) => pkg.copyWith({ devDependencies: HashMap.remove(pkg.devDependencies, name) }));
|
|
279
|
+
/** Add or replace a `peerDependencies` entry. Dual API. */
|
|
280
|
+
static addPeerDependency = Function.dual(3, (pkg, name, specifier) => pkg.copyWith({ peerDependencies: HashMap.set(pkg.peerDependencies, name, specifier) }));
|
|
281
|
+
/** Remove a `peerDependencies` entry. Dual API. */
|
|
282
|
+
static removePeerDependency = Function.dual(2, (pkg, name) => pkg.copyWith({ peerDependencies: HashMap.remove(pkg.peerDependencies, name) }));
|
|
283
|
+
/** Add or replace an `optionalDependencies` entry. Dual API. */
|
|
284
|
+
static addOptionalDependency = Function.dual(3, (pkg, name, specifier) => pkg.copyWith({ optionalDependencies: HashMap.set(pkg.optionalDependencies, name, specifier) }));
|
|
285
|
+
/** Remove an `optionalDependencies` entry. Dual API. */
|
|
286
|
+
static removeOptionalDependency = Function.dual(2, (pkg, name) => pkg.copyWith({ optionalDependencies: HashMap.remove(pkg.optionalDependencies, name) }));
|
|
287
|
+
/** Add or replace a `scripts` entry. Dual API. */
|
|
288
|
+
static setScript = Function.dual(3, (pkg, name, command) => pkg.copyWith({ scripts: HashMap.set(pkg.scripts, name, command) }));
|
|
289
|
+
/** Remove a `scripts` entry. Dual API. */
|
|
290
|
+
static removeScript = Function.dual(2, (pkg, name) => pkg.copyWith({ scripts: HashMap.remove(pkg.scripts, name) }));
|
|
291
|
+
/**
|
|
292
|
+
* Resolve `catalog:` and `workspace:` specifiers across all four dependency
|
|
293
|
+
* maps using the `CatalogResolver` and `WorkspaceResolver` from context.
|
|
294
|
+
* Specifiers the resolvers return `None` for are left unchanged. This is the
|
|
295
|
+
* explicit resolution step — `PackageJsonFile.write` never resolves.
|
|
296
|
+
*/
|
|
297
|
+
static resolve = Effect.fn("Package.resolve")(function* (pkg) {
|
|
298
|
+
const workspace = yield* WorkspaceResolver;
|
|
299
|
+
const catalog = yield* CatalogResolver;
|
|
300
|
+
const resolveMap = Effect.fn("Package.resolve.map")(function* (map) {
|
|
301
|
+
let next = map;
|
|
302
|
+
for (const [name, specifier] of HashMap.entries(map)) if (specifier.startsWith("workspace:")) {
|
|
303
|
+
const version = yield* workspace.versionOf(name);
|
|
304
|
+
if (Option.isSome(version)) next = HashMap.set(next, name, applyWorkspaceModifier(specifier, version.value));
|
|
305
|
+
} else if (specifier.startsWith("catalog:")) {
|
|
306
|
+
const range = yield* catalog.rangeOf(name, catalogName(specifier));
|
|
307
|
+
if (Option.isSome(range)) next = HashMap.set(next, name, range.value);
|
|
308
|
+
}
|
|
309
|
+
return next;
|
|
310
|
+
});
|
|
311
|
+
return pkg.copyWith({
|
|
312
|
+
dependencies: yield* resolveMap(pkg.dependencies),
|
|
313
|
+
devDependencies: yield* resolveMap(pkg.devDependencies),
|
|
314
|
+
peerDependencies: yield* resolveMap(pkg.peerDependencies),
|
|
315
|
+
optionalDependencies: yield* resolveMap(pkg.optionalDependencies)
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
/**
|
|
319
|
+
* Serialize to a formatted package.json string: encode through the wire
|
|
320
|
+
* codec (flattening `rest`), then apply the canonical key order, dependency
|
|
321
|
+
* sorting and empty-map stripping unless the options opt out. Pure.
|
|
322
|
+
*/
|
|
323
|
+
toJsonString(options) {
|
|
324
|
+
return renderJson(Schema.encodeUnknownSync(Package.schema)(this), resolveFormatOptions(options));
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
//#endregion
|
|
329
|
+
export { BinField, DependencyMapField, ExportsField, Package, PackageDecodeError, PeerDependenciesMetaField, PublishConfigField, RepositoryField, StringMapField };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Package } from "./Package.js";
|
|
2
|
+
import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/PackageJsonFile.ts
|
|
5
|
+
/**
|
|
6
|
+
* Indicates that a package.json file could not be read from the filesystem
|
|
7
|
+
* (a filesystem error other than not-found).
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
var PackageJsonReadError = class extends Schema.TaggedErrorClass()("PackageJsonReadError", {
|
|
12
|
+
/** The path that could not be read. */
|
|
13
|
+
path: Schema.String,
|
|
14
|
+
/** The underlying failure, preserved structurally. */
|
|
15
|
+
cause: Schema.Defect()
|
|
16
|
+
}) {
|
|
17
|
+
get message() {
|
|
18
|
+
return `Failed to read package.json from "${this.path}"`;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Indicates that no package.json file exists at the expected path. Carries its
|
|
23
|
+
* own tag for `catchTag` routing.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
var PackageJsonNotFoundError = class extends Schema.TaggedErrorClass()("PackageJsonNotFoundError", {
|
|
28
|
+
/** The path where package.json was expected. */
|
|
29
|
+
path: Schema.String }) {
|
|
30
|
+
get message() {
|
|
31
|
+
return `package.json not found at "${this.path}"`;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Indicates that a package.json file's contents are not valid JSON.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
var PackageJsonParseError = class extends Schema.TaggedErrorClass()("PackageJsonParseError", {
|
|
40
|
+
/** The path whose contents failed to parse as JSON. */
|
|
41
|
+
path: Schema.String,
|
|
42
|
+
/** The underlying `SyntaxError`, preserved structurally. */
|
|
43
|
+
cause: Schema.Defect()
|
|
44
|
+
}) {
|
|
45
|
+
get message() {
|
|
46
|
+
return `Failed to parse package.json at "${this.path}"`;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Indicates that a package.json file could not be written to the filesystem.
|
|
51
|
+
* Narrowed to the filesystem-write failure only — never a resolution or encode
|
|
52
|
+
* error.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
var PackageJsonWriteError = class extends Schema.TaggedErrorClass()("PackageJsonWriteError", {
|
|
57
|
+
/** The path that could not be written. */
|
|
58
|
+
path: Schema.String,
|
|
59
|
+
/** The underlying filesystem failure, preserved structurally. Narrowed to the write failure only. */
|
|
60
|
+
cause: Schema.Defect()
|
|
61
|
+
}) {
|
|
62
|
+
get message() {
|
|
63
|
+
return `Failed to write package.json to "${this.path}"`;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Reads and writes package.json over core `FileSystem` / `Path`. The layer
|
|
68
|
+
* requires those services; provide `@effect/platform-node`'s `NodeFileSystem` /
|
|
69
|
+
* `NodePath` (or a bun equivalent) at the application boundary.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { PackageJsonFile } from "@effected/package-json";
|
|
74
|
+
* import { NodeFileSystem, NodePath } from "@effect/platform-node";
|
|
75
|
+
* import { Effect } from "effect";
|
|
76
|
+
*
|
|
77
|
+
* const program = Effect.gen(function* () {
|
|
78
|
+
* const files = yield* PackageJsonFile;
|
|
79
|
+
* const pkg = yield* files.read("./package.json");
|
|
80
|
+
* console.log(pkg.name);
|
|
81
|
+
* }).pipe(Effect.provide(PackageJsonFile.layer), Effect.provide(NodeFileSystem.layer), Effect.provide(NodePath.layer));
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
var PackageJsonFile = class PackageJsonFile extends Context.Service()("@effected/package-json/PackageJsonFile") {
|
|
87
|
+
/** Build the service implementation from `FileSystem` / `Path` in context; use {@link PackageJsonFile.layer} to provide it. */
|
|
88
|
+
static make = Effect.gen(function* () {
|
|
89
|
+
const fs = yield* FileSystem.FileSystem;
|
|
90
|
+
const path = yield* Path.Path;
|
|
91
|
+
return {
|
|
92
|
+
read: Effect.fn("PackageJsonFile.read")(function* (target) {
|
|
93
|
+
const content = yield* fs.readFileString(target).pipe(Effect.mapError((cause) => cause.reason._tag === "NotFound" ? new PackageJsonNotFoundError({ path: target }) : new PackageJsonReadError({
|
|
94
|
+
path: target,
|
|
95
|
+
cause
|
|
96
|
+
})));
|
|
97
|
+
const json = yield* Effect.try({
|
|
98
|
+
try: () => JSON.parse(content),
|
|
99
|
+
catch: (cause) => new PackageJsonParseError({
|
|
100
|
+
path: target,
|
|
101
|
+
cause
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
return yield* Package.decode(json);
|
|
105
|
+
}),
|
|
106
|
+
write: Effect.fn("PackageJsonFile.write")(function* (target, pkg, options) {
|
|
107
|
+
const json = pkg.toJsonString(options);
|
|
108
|
+
const directory = path.dirname(target);
|
|
109
|
+
yield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.mapError((cause) => new PackageJsonWriteError({
|
|
110
|
+
path: target,
|
|
111
|
+
cause
|
|
112
|
+
})));
|
|
113
|
+
yield* fs.writeFileString(target, json).pipe(Effect.mapError((cause) => new PackageJsonWriteError({
|
|
114
|
+
path: target,
|
|
115
|
+
cause
|
|
116
|
+
})));
|
|
117
|
+
})
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* The live layer. Requires core `FileSystem` / `Path`, provided by the
|
|
122
|
+
* consumer's platform implementation at the edge.
|
|
123
|
+
*/
|
|
124
|
+
static layer = Layer.effect(PackageJsonFile, PackageJsonFile.make);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
export { PackageJsonFile, PackageJsonNotFoundError, PackageJsonParseError, PackageJsonReadError, PackageJsonWriteError };
|