@effected/package-json 0.2.0 → 0.3.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/Package.js +2 -2
- package/PackageJsonFile.js +9 -1
- package/README.md +3 -1
- package/index.d.ts +26 -4
- package/internal/format.js +138 -19
- package/package.json +6 -5
- package/tsdoc-metadata.json +1 -1
package/Package.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Dependency } from "./Dependency.js";
|
|
2
2
|
import { DevEnginesSchema } from "./DevEngines.js";
|
|
3
3
|
import { InvalidSpdxLicenseError, SpdxLicense, isValidSpdx } from "./License.js";
|
|
4
|
-
import { renderJson } from "./internal/format.js";
|
|
4
|
+
import { renderJson, resolveIndent } from "./internal/format.js";
|
|
5
5
|
import { PackageManager } from "./PackageManager.js";
|
|
6
6
|
import { InvalidPackageNameError, PackageName } from "./PackageName.js";
|
|
7
7
|
import { Person } from "./Person.js";
|
|
@@ -84,7 +84,7 @@ cause: Schema.Defect() }) {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
const resolveFormatOptions = (options) => ({
|
|
87
|
-
indent: options?.indent
|
|
87
|
+
indent: resolveIndent(options?.indent, options?.sourceText),
|
|
88
88
|
sort: options?.sort ?? true,
|
|
89
89
|
stripEmpty: options?.stripEmpty ?? true,
|
|
90
90
|
newline: options?.newline ?? true
|
package/PackageJsonFile.js
CHANGED
|
@@ -104,7 +104,15 @@ var PackageJsonFile = class PackageJsonFile extends Context.Service()("@effected
|
|
|
104
104
|
return yield* Package.decode(json);
|
|
105
105
|
}),
|
|
106
106
|
write: Effect.fn("PackageJsonFile.write")(function* (target, pkg, options) {
|
|
107
|
-
|
|
107
|
+
let effective = options;
|
|
108
|
+
if (options?.indent === "preserve" && options.sourceText === void 0) {
|
|
109
|
+
const existing = yield* fs.readFileString(target).pipe(Effect.catch(() => Effect.succeed(void 0)));
|
|
110
|
+
if (existing !== void 0) effective = {
|
|
111
|
+
...options,
|
|
112
|
+
sourceText: existing
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const json = pkg.toJsonString(effective);
|
|
108
116
|
const directory = path.dirname(target);
|
|
109
117
|
yield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.mapError((cause) => new PackageJsonWriteError({
|
|
110
118
|
path: target,
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@effected/package-json)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
8
|
package.json parsing, editing, validation and file IO as Effect schemas. `Package` is a `Schema.Class` with the manifest's known fields typed — `name` is a branded npm name, `version` is a real `SemVer`, `packageManager` decodes into `{ name, version, integrity }` — and a `rest` catch-all that carries every unknown top-level key through a read, edit and write cycle without losing it. Editing is immutable and dual-signature, validation is a rule set you can replace, and `catalog:` / `workspace:` specifiers expand through the `@effected/npm` resolver contracts as an explicit step you opt into.
|
|
9
9
|
|
|
@@ -37,6 +37,8 @@ pnpm add @effected/package-json effect @effect/platform-node
|
|
|
37
37
|
|
|
38
38
|
Requires Node.js >=24.11.0.
|
|
39
39
|
|
|
40
|
+
All `@effected/*` packages are ESM-only: the exports maps publish only `import` conditions, so `require()` — including tools that resolve in CJS mode — fails with Node's `ERR_PACKAGE_PATH_NOT_EXPORTED` rather than loading a CJS build that does not exist. Import from an ES module.
|
|
41
|
+
|
|
40
42
|
`effect` v4 is the only peer dependency. `@effected/semver` and `@effected/npm` come along as ordinary dependencies — they back the `version` field and the resolver contracts — and `spdx-expression-parse` is the one external package in the tree, used to validate SPDX license expressions.
|
|
41
43
|
|
|
42
44
|
Reading and writing files needs a `FileSystem` and a `Path` implementation, provided once at the edge, from `@effect/platform-node` on Node. Everything except `PackageJsonFile` is pure and needs no platform layer at all.
|
package/index.d.ts
CHANGED
|
@@ -327,14 +327,31 @@ declare const PackageDecodeError_base: Schema.Class<PackageDecodeError, Schema.T
|
|
|
327
327
|
declare class PackageDecodeError extends PackageDecodeError_base {
|
|
328
328
|
get message(): string;
|
|
329
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Indentation for serialized package.json output: a spaces count, `"tab"` for
|
|
332
|
+
* real tab indentation, or `"preserve"` to reuse the indentation detected from
|
|
333
|
+
* the original source text (falling back to the two-space default when no
|
|
334
|
+
* source text is available).
|
|
335
|
+
*
|
|
336
|
+
* @public
|
|
337
|
+
*/
|
|
338
|
+
type PackageIndent = number | "tab" | "preserve";
|
|
330
339
|
/**
|
|
331
340
|
* Options for {@link Package.toJsonString} and `PackageJsonFile.write`.
|
|
332
341
|
*
|
|
333
342
|
* @public
|
|
334
343
|
*/
|
|
335
344
|
interface PackageFormatOptions {
|
|
336
|
-
/** Indentation
|
|
337
|
-
readonly indent?:
|
|
345
|
+
/** Indentation: a spaces count, `"tab"`, or `"preserve"` (default `2`). */
|
|
346
|
+
readonly indent?: PackageIndent;
|
|
347
|
+
/**
|
|
348
|
+
* The original source text backing `indent: "preserve"`: its indentation
|
|
349
|
+
* (tab vs N spaces, detected from the first indented line) is reused.
|
|
350
|
+
* Ignored for other `indent` values. When absent, `PackageJsonFile.write`
|
|
351
|
+
* supplies the existing file's text automatically; the pure
|
|
352
|
+
* {@link Package.toJsonString} falls back to the default indentation.
|
|
353
|
+
*/
|
|
354
|
+
readonly sourceText?: string;
|
|
338
355
|
/** Order top-level keys canonically and alphabetize dependency maps (default `true`). */
|
|
339
356
|
readonly sort?: boolean;
|
|
340
357
|
/** Strip empty dependency-map keys (default `true`). */
|
|
@@ -622,7 +639,12 @@ interface PackageJsonFileShape {
|
|
|
622
639
|
* (invalid JSON) or `PackageDecodeError` (schema decode).
|
|
623
640
|
*/
|
|
624
641
|
readonly read: (path: string) => Effect.Effect<Package, PackageJsonReadError | PackageJsonNotFoundError | PackageJsonParseError | PackageDecodeError>;
|
|
625
|
-
/**
|
|
642
|
+
/**
|
|
643
|
+
* Serialize and write a package.json file. Fails with
|
|
644
|
+
* `PackageJsonWriteError`. With `indent: "preserve"` and no explicit
|
|
645
|
+
* `sourceText`, the existing file at `path` (when readable) supplies the
|
|
646
|
+
* source text whose indentation is preserved.
|
|
647
|
+
*/
|
|
626
648
|
readonly write: (path: string, pkg: Package, options?: PackageFormatOptions) => Effect.Effect<void, PackageJsonWriteError>;
|
|
627
649
|
}
|
|
628
650
|
declare const PackageJsonFile_base: Context.ServiceClass<PackageJsonFile, "@effected/package-json/PackageJsonFile", PackageJsonFileShape>;
|
|
@@ -755,5 +777,5 @@ declare class PackageValidator extends PackageValidator_base {
|
|
|
755
777
|
}): Layer.Layer<PackageValidator>;
|
|
756
778
|
}
|
|
757
779
|
//#endregion
|
|
758
|
-
export { BinField, Dependency, type DependencyKind, DependencyMapField, type DependencyProtocol, DependencySpecifier, type DependencySpecifierBrand, DevEngine, DevEngineOrArray, type DevEngines, DevEnginesSchema, ExportsField, InvalidDependencySpecifierError, InvalidPackageNameError, InvalidSpdxLicenseError, Package, PackageDecodeError, type PackageFormatOptions, 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 };
|
|
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 };
|
|
759
781
|
//# sourceMappingURL=index.d.ts.map
|
package/internal/format.js
CHANGED
|
@@ -2,94 +2,213 @@
|
|
|
2
2
|
const KEY_INDEX = new Map([
|
|
3
3
|
"$schema",
|
|
4
4
|
"name",
|
|
5
|
+
"displayName",
|
|
5
6
|
"version",
|
|
7
|
+
"stableVersion",
|
|
6
8
|
"private",
|
|
7
9
|
"description",
|
|
10
|
+
"categories",
|
|
8
11
|
"keywords",
|
|
9
12
|
"homepage",
|
|
10
13
|
"bugs",
|
|
11
14
|
"repository",
|
|
12
15
|
"funding",
|
|
13
16
|
"license",
|
|
17
|
+
"qna",
|
|
14
18
|
"author",
|
|
19
|
+
"maintainers",
|
|
15
20
|
"contributors",
|
|
21
|
+
"publisher",
|
|
22
|
+
"sideEffects",
|
|
16
23
|
"type",
|
|
17
24
|
"imports",
|
|
18
25
|
"exports",
|
|
19
26
|
"main",
|
|
27
|
+
"svelte",
|
|
28
|
+
"umd:main",
|
|
29
|
+
"jsdelivr",
|
|
30
|
+
"unpkg",
|
|
20
31
|
"module",
|
|
32
|
+
"source",
|
|
33
|
+
"jsnext:main",
|
|
21
34
|
"browser",
|
|
35
|
+
"react-native",
|
|
36
|
+
"types",
|
|
37
|
+
"typesVersions",
|
|
38
|
+
"typings",
|
|
39
|
+
"style",
|
|
40
|
+
"example",
|
|
41
|
+
"examplestyle",
|
|
42
|
+
"assets",
|
|
22
43
|
"bin",
|
|
23
44
|
"man",
|
|
24
|
-
"files",
|
|
25
45
|
"directories",
|
|
46
|
+
"files",
|
|
26
47
|
"workspaces",
|
|
48
|
+
"binary",
|
|
27
49
|
"scripts",
|
|
50
|
+
"betterScripts",
|
|
51
|
+
"wireit",
|
|
52
|
+
"l10n",
|
|
53
|
+
"contributes",
|
|
54
|
+
"activationEvents",
|
|
55
|
+
"husky",
|
|
56
|
+
"simple-git-hooks",
|
|
57
|
+
"pre-commit",
|
|
58
|
+
"commitlint",
|
|
59
|
+
"lint-staged",
|
|
60
|
+
"nano-staged",
|
|
28
61
|
"config",
|
|
62
|
+
"nodemonConfig",
|
|
63
|
+
"browserify",
|
|
64
|
+
"babel",
|
|
65
|
+
"browserslist",
|
|
66
|
+
"xo",
|
|
67
|
+
"prettier",
|
|
68
|
+
"eslintConfig",
|
|
69
|
+
"eslintIgnore",
|
|
70
|
+
"npmpkgjsonlint",
|
|
71
|
+
"npmPackageJsonLintConfig",
|
|
72
|
+
"npmpackagejsonlint",
|
|
73
|
+
"release",
|
|
74
|
+
"remarkConfig",
|
|
75
|
+
"stylelint",
|
|
76
|
+
"ava",
|
|
77
|
+
"jest",
|
|
78
|
+
"jest-junit",
|
|
79
|
+
"jest-stare",
|
|
80
|
+
"mocha",
|
|
81
|
+
"nyc",
|
|
82
|
+
"c8",
|
|
83
|
+
"tap",
|
|
84
|
+
"oclif",
|
|
85
|
+
"resolutions",
|
|
86
|
+
"overrides",
|
|
29
87
|
"dependencies",
|
|
30
88
|
"devDependencies",
|
|
89
|
+
"dependenciesMeta",
|
|
31
90
|
"peerDependencies",
|
|
32
91
|
"peerDependenciesMeta",
|
|
33
92
|
"optionalDependencies",
|
|
93
|
+
"bundledDependencies",
|
|
34
94
|
"bundleDependencies",
|
|
35
|
-
"
|
|
95
|
+
"extensionPack",
|
|
96
|
+
"extensionDependencies",
|
|
97
|
+
"flat",
|
|
98
|
+
"packageManager",
|
|
36
99
|
"engines",
|
|
100
|
+
"engineStrict",
|
|
37
101
|
"devEngines",
|
|
102
|
+
"volta",
|
|
103
|
+
"languageName",
|
|
38
104
|
"os",
|
|
39
105
|
"cpu",
|
|
106
|
+
"preferGlobal",
|
|
40
107
|
"publishConfig",
|
|
41
|
-
"
|
|
108
|
+
"icon",
|
|
109
|
+
"badges",
|
|
110
|
+
"galleryBanner",
|
|
111
|
+
"preview",
|
|
112
|
+
"markdown",
|
|
113
|
+
"pnpm"
|
|
42
114
|
].map((k, i) => [k, i]));
|
|
43
115
|
/**
|
|
44
|
-
* Deterministic, locale-independent string comparison by code
|
|
116
|
+
* Deterministic, locale-independent string comparison by code unit. A bare
|
|
45
117
|
* `localeCompare` sorts differently across ICU builds/locales; package.json key
|
|
46
118
|
* order must be stable everywhere.
|
|
47
119
|
*/
|
|
48
120
|
const byCodePoint = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
49
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Top-level map fields whose entries are alphabetized when sorting. The
|
|
123
|
+
* dependency maps for canonical presentation; `scripts` / `engines` / `bin`
|
|
124
|
+
* additionally because the `Package` model carries them as `HashMap`s, whose
|
|
125
|
+
* encode order is hash order — source order is already gone, so a deterministic
|
|
126
|
+
* alphabetical order is strictly better. `sort-package-json` sorts `engines`
|
|
127
|
+
* and `bin` identically; its `scripts` sort is a grouped sort that agrees with
|
|
128
|
+
* plain code-unit order except for `pre*`/`post*` script pairing.
|
|
129
|
+
*/
|
|
130
|
+
const SORTED_MAP_KEYS = /* @__PURE__ */ new Set([
|
|
50
131
|
"dependencies",
|
|
51
132
|
"devDependencies",
|
|
52
133
|
"peerDependencies",
|
|
53
134
|
"optionalDependencies",
|
|
54
|
-
"bundleDependencies"
|
|
135
|
+
"bundleDependencies",
|
|
136
|
+
"scripts",
|
|
137
|
+
"engines",
|
|
138
|
+
"bin"
|
|
55
139
|
]);
|
|
56
|
-
/** Alphabetize the entries of a plain-object
|
|
57
|
-
const
|
|
140
|
+
/** Alphabetize the entries of a plain-object map field. */
|
|
141
|
+
const sortMapEntries = (value) => {
|
|
58
142
|
const result = {};
|
|
59
143
|
for (const key of Object.keys(value).sort(byCodePoint)) result[key] = value[key];
|
|
60
144
|
return result;
|
|
61
145
|
};
|
|
62
146
|
const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
63
147
|
/**
|
|
64
|
-
* Order top-level keys canonically (known keys by {@link KEY_ORDER},
|
|
65
|
-
* alphabetically
|
|
148
|
+
* Order top-level keys canonically (known keys by {@link KEY_ORDER}, then
|
|
149
|
+
* unknown public keys alphabetically, then unknown `_`-prefixed keys
|
|
150
|
+
* alphabetically) and alphabetize the {@link SORTED_MAP_KEYS} map entries.
|
|
66
151
|
*/
|
|
67
152
|
const sortKeys = (obj) => {
|
|
68
153
|
const known = [];
|
|
69
|
-
const
|
|
154
|
+
const restPublic = [];
|
|
155
|
+
const restPrivate = [];
|
|
70
156
|
for (const key of Object.keys(obj)) if (KEY_INDEX.has(key)) known.push([key, obj[key]]);
|
|
71
|
-
else
|
|
157
|
+
else if (key.startsWith("_")) restPrivate.push([key, obj[key]]);
|
|
158
|
+
else restPublic.push([key, obj[key]]);
|
|
72
159
|
known.sort((a, b) => KEY_INDEX.get(a[0]) - KEY_INDEX.get(b[0]));
|
|
73
|
-
|
|
160
|
+
restPublic.sort((a, b) => byCodePoint(a[0], b[0]));
|
|
161
|
+
restPrivate.sort((a, b) => byCodePoint(a[0], b[0]));
|
|
74
162
|
const result = {};
|
|
75
|
-
for (const [key, value] of [
|
|
163
|
+
for (const [key, value] of [
|
|
164
|
+
...known,
|
|
165
|
+
...restPublic,
|
|
166
|
+
...restPrivate
|
|
167
|
+
]) result[key] = SORTED_MAP_KEYS.has(key) && isPlainObject(value) ? sortMapEntries(value) : value;
|
|
76
168
|
return result;
|
|
77
169
|
};
|
|
78
|
-
const
|
|
170
|
+
const STRIP_EMPTY_KEYS = [
|
|
79
171
|
"dependencies",
|
|
80
172
|
"devDependencies",
|
|
81
173
|
"peerDependencies",
|
|
82
|
-
"optionalDependencies"
|
|
174
|
+
"optionalDependencies",
|
|
175
|
+
"scripts"
|
|
83
176
|
];
|
|
84
|
-
/** Remove
|
|
177
|
+
/** Remove map keys whose value is an empty object. */
|
|
85
178
|
const stripEmptyDependencyMaps = (raw) => {
|
|
86
179
|
const result = { ...raw };
|
|
87
|
-
for (const key of
|
|
180
|
+
for (const key of STRIP_EMPTY_KEYS) {
|
|
88
181
|
const value = result[key];
|
|
89
182
|
if (isPlainObject(value) && Object.keys(value).length === 0) delete result[key];
|
|
90
183
|
}
|
|
91
184
|
return result;
|
|
92
185
|
};
|
|
186
|
+
const DEFAULT_INDENT = 2;
|
|
187
|
+
/**
|
|
188
|
+
* Detect the indentation of a JSON source text from its first indented line:
|
|
189
|
+
* `"\t"` for tab indentation, otherwise the leading run of spaces. Returns
|
|
190
|
+
* `undefined` when no line is indented.
|
|
191
|
+
*/
|
|
192
|
+
const detectIndent = (source) => {
|
|
193
|
+
for (const line of source.split("\n")) {
|
|
194
|
+
const match = /^(\t+| +)\S/.exec(line);
|
|
195
|
+
if (match !== null) {
|
|
196
|
+
const indent = match[1];
|
|
197
|
+
return indent.startsWith(" ") ? " " : indent;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Resolve a `PackageFormatOptions.indent` value to the `JSON.stringify` indent
|
|
203
|
+
* argument: `"tab"` becomes a real tab, `"preserve"` reuses the indentation
|
|
204
|
+
* detected from `sourceText` (falling back to the two-space default when no
|
|
205
|
+
* source text or no indented line is available), and a number passes through.
|
|
206
|
+
*/
|
|
207
|
+
const resolveIndent = (indent, sourceText) => {
|
|
208
|
+
if (indent === "tab") return " ";
|
|
209
|
+
if (indent === "preserve") return sourceText === void 0 ? DEFAULT_INDENT : detectIndent(sourceText) ?? DEFAULT_INDENT;
|
|
210
|
+
return indent ?? DEFAULT_INDENT;
|
|
211
|
+
};
|
|
93
212
|
/**
|
|
94
213
|
* Render an already-encoded package.json record to a JSON string, applying the
|
|
95
214
|
* empty-map strip, canonical key ordering and a trailing newline unless the
|
|
@@ -103,4 +222,4 @@ const renderJson = (raw, options) => {
|
|
|
103
222
|
};
|
|
104
223
|
|
|
105
224
|
//#endregion
|
|
106
|
-
export { renderJson, sortKeys, stripEmptyDependencyMaps };
|
|
225
|
+
export { detectIndent, renderJson, resolveIndent, sortKeys, stripEmptyDependencyMaps };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/package-json",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "package.json parsing, editing, validation and file IO as Effect schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,17 +32,18 @@
|
|
|
32
32
|
"exports": {
|
|
33
33
|
".": {
|
|
34
34
|
"types": "./index.d.ts",
|
|
35
|
-
"import": "./index.js"
|
|
35
|
+
"import": "./index.js",
|
|
36
|
+
"default": "./index.js"
|
|
36
37
|
},
|
|
37
38
|
"./package.json": "./package.json"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"@effected/npm": "0.2.
|
|
41
|
-
"@effected/semver": "0.1.
|
|
41
|
+
"@effected/npm": "0.2.1",
|
|
42
|
+
"@effected/semver": "0.1.1",
|
|
42
43
|
"spdx-expression-parse": "^4.0.0"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
|
-
"effect": "4.0.0-beta.
|
|
46
|
+
"effect": "4.0.0-beta.99"
|
|
46
47
|
},
|
|
47
48
|
"engines": {
|
|
48
49
|
"node": ">=24.11.0"
|