jpm-pkg 1.0.3 → 1.0.5

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.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('node:path');
4
+ const fs = require('node:fs');
4
5
  const { readJSONSafe, writeJSON } = require('../utils/fs');
5
6
  const { hashString } = require('../security/integrity');
6
7
 
@@ -8,28 +9,28 @@ const LOCK_VERSION = 1;
8
9
  const LOCK_FILE = 'jpm-lock.json';
9
10
 
10
11
  /**
11
- * jpm-lock.json structure:
12
- * {
13
- * "lockVersion": 1,
14
- * "packages": {
15
- * "express@4.18.2": {
16
- * "name": "express",
17
- * "version": "4.18.2",
18
- * "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
19
- * "integrity": "sha512-...",
20
- * "dependencies": { "accepts": "^1.3.8", ... }
21
- * },
22
- * ...
23
- * }
24
- * }
12
+ * Manages the jpm-lock.json file for deterministic installations.
13
+ * The lockfile stores exact versions and integrity hashes for all dependencies.
25
14
  */
26
-
27
15
  class Lockfile {
16
+ /**
17
+ * Creates an instance of the Lockfile.
18
+ *
19
+ * @param {string} projectRoot - The absolute path to the project root directory
20
+ */
28
21
  constructor(projectRoot) {
22
+ /** @type {string} */
29
23
  this.filePath = path.join(projectRoot, LOCK_FILE);
24
+ /** @type {Object} */
30
25
  this._data = this._load();
31
26
  }
32
27
 
28
+ /**
29
+ * Loads the lockfile data from disk or returns a default structure.
30
+ *
31
+ * @returns {Object} The lockfile data
32
+ * @private
33
+ */
33
34
  _load() {
34
35
  const data = readJSONSafe(this.filePath, null);
35
36
  if (!data) return { lockVersion: LOCK_VERSION, packages: {} };
@@ -37,7 +38,11 @@ class Lockfile {
37
38
  }
38
39
 
39
40
  /**
40
- * Build/update lock file from a resolved package map
41
+ * Builds or updates the lockfile data from a resolved dependency map.
42
+ * Sorts keys for consistent output.
43
+ *
44
+ * @param {Map<string, Object>} resolvedMap - Map of resolved package metadata
45
+ * @returns {this} The current instance for chaining
41
46
  */
42
47
  update(resolvedMap) {
43
48
  const packages = {};
@@ -65,7 +70,9 @@ class Lockfile {
65
70
  }
66
71
 
67
72
  /**
68
- * Verifies if the lockfile has been tampered with.
73
+ * Verifies the cryptographic integrity of the lockfile packages.
74
+ *
75
+ * @returns {boolean} True if the integrity hash matches the current package data
69
76
  */
70
77
  verify() {
71
78
  if (!this._data.integrity || !this._data.packages) return true;
@@ -73,7 +80,14 @@ class Lockfile {
73
80
  return actual === this._data.integrity;
74
81
  }
75
82
 
76
- /** Add or update a single package entry */
83
+ /**
84
+ * Adds or updates a single package entry in the lockfile data.
85
+ *
86
+ * @param {string} name - Package name
87
+ * @param {string} version - Package version
88
+ * @param {Object} meta - Package metadata
89
+ * @returns {this} The current instance for chaining
90
+ */
77
91
  setPackage(name, version, meta) {
78
92
  const key = `${name}@${version}`;
79
93
  this._data.packages[key] = {
@@ -87,42 +101,75 @@ class Lockfile {
87
101
  return this;
88
102
  }
89
103
 
90
- /** Remove a package entry */
104
+ /**
105
+ * Removes a package entry from the lockfile data.
106
+ *
107
+ * @param {string} name - Package name
108
+ * @param {string} version - Package version
109
+ * @returns {this} The current instance for chaining
110
+ */
91
111
  removePackage(name, version) {
92
112
  const key = `${name}@${version}`;
93
113
  delete this._data.packages[key];
94
114
  return this;
95
115
  }
96
116
 
97
- /** Look up a specific package in the lock file */
117
+ /**
118
+ * Retrieves a specific package entry from the lockfile data.
119
+ *
120
+ * @param {string} name - Package name
121
+ * @param {string} version - Package version
122
+ * @returns {Object|null} The package entry or null if not found
123
+ */
98
124
  getPackage(name, version) {
99
125
  return this._data.packages[`${name}@${version}`] || null;
100
126
  }
101
127
 
102
- /** Check whether the lock file has a resolved entry for name@range */
128
+ /**
129
+ * Checks if the lockfile contains a specific package version.
130
+ *
131
+ * @param {string} name - Package name
132
+ * @param {string} version - Package version
133
+ * @returns {boolean} True if the package version exists
134
+ */
103
135
  hasPackage(name, version) {
104
136
  return Boolean(this._data.packages[`${name}@${version}`]);
105
137
  }
106
138
 
107
- /** Return all locked packages as array */
139
+ /**
140
+ * Returns an array of all package entries stored in the lockfile.
141
+ *
142
+ * @returns {Object[]} Array of package entry objects
143
+ */
108
144
  allPackages() {
109
145
  return Object.values(this._data.packages);
110
146
  }
111
147
 
112
- /** Write lock file to disk */
148
+ /**
149
+ * Writes the current lockfile data to disk.
150
+ *
151
+ * @returns {this} The current instance for chaining
152
+ */
113
153
  save() {
114
154
  writeJSON(this.filePath, this._data, 2);
115
155
  return this;
116
156
  }
117
157
 
118
- /** Raw data */
158
+ /**
159
+ * Returns the raw lockfile data structure.
160
+ * @type {Object}
161
+ */
119
162
  get data() { return this._data; }
120
163
 
121
- /** Whether a lock file exists */
164
+ /**
165
+ * Checks if the lockfile exists on the filesystem.
166
+ *
167
+ * @returns {boolean} True if the lockfile exists
168
+ */
122
169
  exists() {
123
- const fs = require('node:fs');
124
170
  return fs.existsSync(this.filePath);
125
171
  }
126
172
  }
127
173
 
128
174
  module.exports = Lockfile;
175
+
@@ -3,33 +3,69 @@
3
3
  const path = require('node:path');
4
4
  const fs = require('node:fs');
5
5
  const { readJSONSafe, writeJSON, findPackageJson } = require('../utils/fs');
6
- const logger = require('../utils/logger');
7
6
 
8
7
  const REQUIRED_FIELDS = ['name', 'version'];
9
8
 
9
+ /**
10
+ * Represents a Node.js package.json file.
11
+ * Provides methods for reading, validating, mutating, and persisting package metadata.
12
+ */
10
13
  class PackageJSON {
14
+ /**
15
+ * Creates an instance of the PackageJSON.
16
+ *
17
+ * @param {string} filePath - Absolute path to the package.json file
18
+ */
11
19
  constructor(filePath) {
20
+ /** @type {string} */
12
21
  this.filePath = filePath;
22
+ /** @type {Object} */
13
23
  this._data = this._load();
14
24
  }
15
25
 
26
+ /**
27
+ * Creates a PackageJSON instance from a directory path.
28
+ *
29
+ * @param {string} dir - Directory containing the package.json
30
+ * @returns {PackageJSON}
31
+ */
16
32
  static fromDir(dir) {
17
33
  const f = path.join(dir, 'package.json');
18
34
  return new PackageJSON(f);
19
35
  }
20
36
 
37
+ /**
38
+ * Finds and loads the nearest package.json starting from a directory.
39
+ *
40
+ * @param {string} [startDir=process.cwd()] - Directory to start the search from
41
+ * @returns {PackageJSON}
42
+ * @throws {Error} If no package.json is found in the directory tree
43
+ */
21
44
  static find(startDir = process.cwd()) {
22
45
  const f = findPackageJson(startDir);
23
46
  if (!f) throw new Error('No package.json found in this directory or any parent.');
24
47
  return new PackageJSON(f);
25
48
  }
26
49
 
50
+ /**
51
+ * Loads the package.json data from disk.
52
+ *
53
+ * @returns {Object} The package data or a default structure if not found
54
+ * @private
55
+ */
27
56
  _load() {
28
57
  const data = readJSONSafe(this.filePath, null);
29
58
  if (!data) return this._empty();
30
59
  return data;
31
60
  }
32
61
 
62
+ /**
63
+ * Returns a default package.json structure.
64
+ * Used when creating a new package or when the file is missing/invalid.
65
+ *
66
+ * @returns {Object}
67
+ * @private
68
+ */
33
69
  _empty() {
34
70
  return {
35
71
  name: path.basename(path.dirname(this.filePath || process.cwd())),
@@ -47,22 +83,57 @@ class PackageJSON {
47
83
 
48
84
  // ── Accessors ────────────────────────────────────────────────────────────────
49
85
 
86
+ /** @type {string} */
50
87
  get name() { return this._data.name; }
88
+
89
+ /** @type {string} */
51
90
  get version() { return this._data.version; }
91
+
92
+ /** @type {Object.<string, string>} */
52
93
  get dependencies() { return this._data.dependencies || {}; }
94
+
95
+ /** @type {Object.<string, string>} */
53
96
  get devDependencies() { return this._data.devDependencies || {}; }
97
+
98
+ /** @type {Object.<string, string>} */
54
99
  get peerDependencies() { return this._data.peerDependencies || {}; }
100
+
101
+ /** @type {Object.<string, string>} */
55
102
  get optionalDeps() { return this._data.optionalDependencies || {}; }
103
+
104
+ /** @type {Object.<string, string>} */
56
105
  get scripts() { return this._data.scripts || {}; }
106
+
107
+ /** @type {string[]} */
57
108
  get workspaces() { return this._data.workspaces || []; }
109
+
110
+ /** @type {string} */
58
111
  get main() { return this._data.main || 'index.js'; }
112
+
113
+ /** @type {Object.<string, string>} */
59
114
  get bin() { return this._data.bin || {}; }
115
+
116
+ /** @type {Object.<string, string>} */
60
117
  get engines() { return this._data.engines || {}; }
118
+
119
+ /** @type {Object} */
61
120
  get data() { return this._data; }
121
+
122
+ /** @type {string} */
62
123
  get dir() { return path.dirname(this.filePath); }
63
124
 
64
125
  // ── Mutations ────────────────────────────────────────────────────────────────
65
126
 
127
+ /**
128
+ * Adds a dependency to the package.json.
129
+ *
130
+ * @param {string} name - Package name
131
+ * @param {string} version - Package version
132
+ * @param {Object} [options={}] - Dependency options
133
+ * @param {boolean} [options.dev=false] - Whether to add as a devDependency
134
+ * @param {boolean} [options.exact=false] - Whether to use the exact version instead of a caret range
135
+ * @returns {this} The current instance for chaining
136
+ */
66
137
  addDependency(name, version, { dev = false, exact = false } = {}) {
67
138
  const range = exact ? version : `^${version}`;
68
139
  const key = dev ? 'devDependencies' : 'dependencies';
@@ -71,6 +142,12 @@ class PackageJSON {
71
142
  return this;
72
143
  }
73
144
 
145
+ /**
146
+ * Removes a dependency from all dependency sections.
147
+ *
148
+ * @param {string} name - Package name to remove
149
+ * @returns {this} The current instance for chaining
150
+ */
74
151
  removeDependency(name) {
75
152
  for (const key of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
76
153
  if (this._data[key]) delete this._data[key][name];
@@ -78,16 +155,36 @@ class PackageJSON {
78
155
  return this;
79
156
  }
80
157
 
158
+ /**
159
+ * Sets a top-level field in the package.json.
160
+ *
161
+ * @param {string} key - Field name
162
+ * @param {*} value - Field value
163
+ * @returns {this} The current instance for chaining
164
+ */
81
165
  setField(key, value) {
82
166
  this._data[key] = value;
83
167
  return this;
84
168
  }
85
169
 
170
+ /**
171
+ * Sets the package version.
172
+ *
173
+ * @param {string} version - New version string
174
+ * @returns {this} The current instance for chaining
175
+ */
86
176
  setVersion(version) {
87
177
  this._data.version = version;
88
178
  return this;
89
179
  }
90
180
 
181
+ /**
182
+ * Adds or updates a script in the scripts section.
183
+ *
184
+ * @param {string} name - Script name
185
+ * @param {string} command - Command to execute
186
+ * @returns {this} The current instance for chaining
187
+ */
91
188
  addScript(name, command) {
92
189
  if (!this._data.scripts) this._data.scripts = {};
93
190
  this._data.scripts[name] = command;
@@ -96,6 +193,11 @@ class PackageJSON {
96
193
 
97
194
  // ── Validation ────────────────────────────────────────────────────────────────
98
195
 
196
+ /**
197
+ * Validates the internal package data against basic npm requirements.
198
+ *
199
+ * @returns {string[]} Array of validation error messages
200
+ */
99
201
  validate() {
100
202
  const errors = [];
101
203
  for (const field of REQUIRED_FIELDS) {
@@ -112,15 +214,30 @@ class PackageJSON {
112
214
 
113
215
  // ── Persistence ──────────────────────────────────────────────────────────────
114
216
 
217
+ /**
218
+ * Writes the current package data to disk.
219
+ *
220
+ * @returns {this} The current instance for chaining
221
+ */
115
222
  save() {
116
223
  writeJSON(this.filePath, this._data, 2);
117
224
  return this;
118
225
  }
119
226
 
227
+ /**
228
+ * Checks if the package.json file exists on the filesystem.
229
+ *
230
+ * @returns {boolean}
231
+ */
120
232
  exists() {
121
233
  return fs.existsSync(this.filePath);
122
234
  }
123
235
 
236
+ /**
237
+ * Returns a combined map of all dependencies (prod, dev, and optional).
238
+ *
239
+ * @returns {Object.<string, string>}
240
+ */
124
241
  allDeps() {
125
242
  return {
126
243
  ...this.dependencies,
@@ -131,3 +248,4 @@ class PackageJSON {
131
248
  }
132
249
 
133
250
  module.exports = PackageJSON;
251
+