@patchstack/connect 0.2.7 → 0.2.9

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/dist/index.cjs CHANGED
@@ -47,8 +47,8 @@ __export(src_exports, {
47
47
  module.exports = __toCommonJS(src_exports);
48
48
 
49
49
  // src/parsers/index.ts
50
- var import_promises5 = require("fs/promises");
51
- var import_node_path4 = __toESM(require("path"), 1);
50
+ var import_promises6 = require("fs/promises");
51
+ var import_node_path5 = __toESM(require("path"), 1);
52
52
 
53
53
  // src/types.ts
54
54
  var PatchstackError = class extends Error {
@@ -62,13 +62,52 @@ var PatchstackError = class extends Error {
62
62
  cause;
63
63
  };
64
64
 
65
- // src/parsers/npm.ts
65
+ // src/parsers/consistency.ts
66
66
  var import_promises = require("fs/promises");
67
67
  var import_node_path = __toESM(require("path"), 1);
68
+ async function readDeclaredDependencyNames(cwd) {
69
+ let raw;
70
+ try {
71
+ raw = await (0, import_promises.readFile)(import_node_path.default.join(cwd, "package.json"), "utf8");
72
+ } catch {
73
+ return [];
74
+ }
75
+ let parsed;
76
+ try {
77
+ parsed = JSON.parse(raw);
78
+ } catch {
79
+ return [];
80
+ }
81
+ if (typeof parsed !== "object" || parsed === null) {
82
+ return [];
83
+ }
84
+ const names = /* @__PURE__ */ new Set();
85
+ for (const group of ["dependencies", "devDependencies"]) {
86
+ const deps = parsed[group];
87
+ if (typeof deps !== "object" || deps === null) {
88
+ continue;
89
+ }
90
+ for (const [name, spec] of Object.entries(deps)) {
91
+ if (typeof spec === "string" && /^(file|link|workspace|portal):/.test(spec)) {
92
+ continue;
93
+ }
94
+ names.add(name);
95
+ }
96
+ }
97
+ return [...names];
98
+ }
99
+ function missingDependencies(declared, packages) {
100
+ const present = new Set(packages.map((entry) => entry.name));
101
+ return declared.filter((name) => !present.has(name));
102
+ }
103
+
104
+ // src/parsers/npm.ts
105
+ var import_promises2 = require("fs/promises");
106
+ var import_node_path2 = __toESM(require("path"), 1);
68
107
  async function parseNpmLockfile(lockfilePath) {
69
108
  let raw;
70
109
  try {
71
- raw = await (0, import_promises.readFile)(lockfilePath, "utf8");
110
+ raw = await (0, import_promises2.readFile)(lockfilePath, "utf8");
72
111
  } catch (cause) {
73
112
  throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
74
113
  }
@@ -126,7 +165,7 @@ function extractFromV1(deps, acc = [], depth = 0) {
126
165
  return acc;
127
166
  }
128
167
  function extractNameFromPath(pkgPath) {
129
- const segments = pkgPath.split("node_modules" + import_node_path.default.sep === pkgPath ? import_node_path.default.sep : "/");
168
+ const segments = pkgPath.split("node_modules" + import_node_path2.default.sep === pkgPath ? import_node_path2.default.sep : "/");
130
169
  const parts = pkgPath.split("/");
131
170
  const nmIndex = parts.lastIndexOf("node_modules");
132
171
  if (nmIndex === -1 || nmIndex >= parts.length - 1) {
@@ -149,12 +188,12 @@ function isDirectV2(pkgPath) {
149
188
  }
150
189
 
151
190
  // src/parsers/node_modules.ts
152
- var import_promises2 = require("fs/promises");
153
- var import_node_path2 = __toESM(require("path"), 1);
191
+ var import_promises3 = require("fs/promises");
192
+ var import_node_path3 = __toESM(require("path"), 1);
154
193
  async function walkNodeModules(cwd) {
155
- const root = import_node_path2.default.join(cwd, "node_modules");
194
+ const root = import_node_path3.default.join(cwd, "node_modules");
156
195
  try {
157
- const info = await (0, import_promises2.stat)(root);
196
+ const info = await (0, import_promises3.stat)(root);
158
197
  if (!info.isDirectory()) {
159
198
  throw new PatchstackError(
160
199
  `${root} exists but is not a directory.`,
@@ -178,7 +217,7 @@ async function walkNodeModules(cwd) {
178
217
  async function walk(dir, acc, depth) {
179
218
  let names;
180
219
  try {
181
- names = await (0, import_promises2.readdir)(dir);
220
+ names = await (0, import_promises3.readdir)(dir);
182
221
  } catch {
183
222
  return;
184
223
  }
@@ -186,14 +225,14 @@ async function walk(dir, acc, depth) {
186
225
  if (name.startsWith(".")) {
187
226
  continue;
188
227
  }
189
- const fullPath = import_node_path2.default.join(dir, name);
228
+ const fullPath = import_node_path3.default.join(dir, name);
190
229
  if (!await isPlainDirectory(fullPath)) {
191
230
  continue;
192
231
  }
193
232
  if (name.startsWith("@")) {
194
233
  let subNames;
195
234
  try {
196
- subNames = await (0, import_promises2.readdir)(fullPath);
235
+ subNames = await (0, import_promises3.readdir)(fullPath);
197
236
  } catch {
198
237
  continue;
199
238
  }
@@ -201,7 +240,7 @@ async function walk(dir, acc, depth) {
201
240
  if (sub.startsWith(".")) {
202
241
  continue;
203
242
  }
204
- const scopedDir = import_node_path2.default.join(fullPath, sub);
243
+ const scopedDir = import_node_path3.default.join(fullPath, sub);
205
244
  if (!await isPlainDirectory(scopedDir)) {
206
245
  continue;
207
246
  }
@@ -217,7 +256,7 @@ async function walk(dir, acc, depth) {
217
256
  async function readPackage(pkgDir, depth, acc) {
218
257
  let raw;
219
258
  try {
220
- raw = await (0, import_promises2.readFile)(import_node_path2.default.join(pkgDir, "package.json"), "utf8");
259
+ raw = await (0, import_promises3.readFile)(import_node_path3.default.join(pkgDir, "package.json"), "utf8");
221
260
  } catch {
222
261
  return;
223
262
  }
@@ -240,7 +279,7 @@ async function readPackage(pkgDir, depth, acc) {
240
279
  });
241
280
  }
242
281
  async function walkNested(pkgDir, acc, depth) {
243
- const nested = import_node_path2.default.join(pkgDir, "node_modules");
282
+ const nested = import_node_path3.default.join(pkgDir, "node_modules");
244
283
  if (!await isPlainDirectory(nested)) {
245
284
  return;
246
285
  }
@@ -248,7 +287,7 @@ async function walkNested(pkgDir, acc, depth) {
248
287
  }
249
288
  async function isPlainDirectory(dir) {
250
289
  try {
251
- const info = await (0, import_promises2.lstat)(dir);
290
+ const info = await (0, import_promises3.lstat)(dir);
252
291
  return info.isDirectory() && !info.isSymbolicLink();
253
292
  } catch {
254
293
  return false;
@@ -256,11 +295,11 @@ async function isPlainDirectory(dir) {
256
295
  }
257
296
 
258
297
  // src/parsers/pnpm.ts
259
- var import_promises3 = require("fs/promises");
298
+ var import_promises4 = require("fs/promises");
260
299
  async function parsePnpmLockfile(lockfilePath) {
261
300
  let raw;
262
301
  try {
263
- raw = await (0, import_promises3.readFile)(lockfilePath, "utf8");
302
+ raw = await (0, import_promises4.readFile)(lockfilePath, "utf8");
264
303
  } catch (cause) {
265
304
  throw new PatchstackError(
266
305
  `Could not read lockfile at ${lockfilePath}`,
@@ -508,12 +547,12 @@ function extractLeafName(trimmed) {
508
547
  }
509
548
 
510
549
  // src/parsers/yarn.ts
511
- var import_promises4 = require("fs/promises");
512
- var import_node_path3 = __toESM(require("path"), 1);
550
+ var import_promises5 = require("fs/promises");
551
+ var import_node_path4 = __toESM(require("path"), 1);
513
552
  async function parseYarnLockfile(lockfilePath) {
514
553
  let raw;
515
554
  try {
516
- raw = await (0, import_promises4.readFile)(lockfilePath, "utf8");
555
+ raw = await (0, import_promises5.readFile)(lockfilePath, "utf8");
517
556
  } catch (cause) {
518
557
  throw new PatchstackError(
519
558
  `Could not read lockfile at ${lockfilePath}`,
@@ -528,7 +567,7 @@ async function parseYarnLockfile(lockfilePath) {
528
567
  "LOCKFILE_PARSE_ERROR"
529
568
  );
530
569
  }
531
- const directNames = await readDirectDepNames(import_node_path3.default.dirname(lockfilePath));
570
+ const directNames = await readDirectDepNames(import_node_path4.default.dirname(lockfilePath));
532
571
  const entries = [];
533
572
  const seen = /* @__PURE__ */ new Set();
534
573
  for (const block of blocks) {
@@ -672,7 +711,7 @@ async function readDirectDepNames(cwd) {
672
711
  const names = /* @__PURE__ */ new Set();
673
712
  let raw;
674
713
  try {
675
- raw = await (0, import_promises4.readFile)(import_node_path3.default.join(cwd, "package.json"), "utf8");
714
+ raw = await (0, import_promises5.readFile)(import_node_path4.default.join(cwd, "package.json"), "utf8");
676
715
  } catch {
677
716
  return names;
678
717
  }
@@ -709,7 +748,7 @@ var LOCKFILE_CANDIDATES = [
709
748
  async function detectLockfile(cwd) {
710
749
  const probed = await Promise.all(
711
750
  LOCKFILE_CANDIDATES.map(async (candidate) => {
712
- const filePath = import_node_path4.default.join(cwd, candidate.filename);
751
+ const filePath = import_node_path5.default.join(cwd, candidate.filename);
713
752
  return { ...candidate, filePath, present: await exists(filePath) };
714
753
  })
715
754
  );
@@ -729,9 +768,69 @@ async function detectLockfile(cwd) {
729
768
  );
730
769
  }
731
770
  async function scanLockfile(cwd) {
732
- const detected = await detectLockfile(cwd);
733
- const packages = await runStrategy(detected, cwd);
734
- return { ecosystem: detected.ecosystem, packages };
771
+ const candidates = await presentLockfiles(cwd);
772
+ if (candidates.length === 0) {
773
+ await detectLockfile(cwd);
774
+ }
775
+ const declared = await readDeclaredDependencyNames(cwd);
776
+ const warnings = [];
777
+ let firstParsed = null;
778
+ let walkTried = false;
779
+ for (const candidate of candidates) {
780
+ let packages;
781
+ try {
782
+ packages = await runStrategy(candidate, cwd);
783
+ } catch {
784
+ continue;
785
+ }
786
+ walkTried ||= candidate.strategy === "node-modules-walk";
787
+ firstParsed ??= { packages, filename: candidate.filename };
788
+ const missing = missingDependencies(declared, packages);
789
+ if (missing.length === 0) {
790
+ return manifestWith(packages, warnings);
791
+ }
792
+ warnings.push(staleWarning(candidate.filename, missing));
793
+ }
794
+ if (!walkTried) {
795
+ try {
796
+ const packages = await walkNodeModules(cwd);
797
+ const missing = missingDependencies(declared, packages);
798
+ if (missing.length > 0) {
799
+ warnings.push(staleWarning("node_modules/", missing));
800
+ }
801
+ warnings.push("Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.");
802
+ return manifestWith(packages, warnings);
803
+ } catch {
804
+ }
805
+ }
806
+ if (firstParsed === null) {
807
+ const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(", ");
808
+ throw new PatchstackError(
809
+ `No readable lockfile found in ${cwd}. Expected one of: ${expected}.`,
810
+ "LOCKFILE_NOT_FOUND"
811
+ );
812
+ }
813
+ warnings.push(
814
+ `No fully-consistent source found; reporting ${firstParsed.filename}. The manifest may understate the real dependency set.`
815
+ );
816
+ return manifestWith(firstParsed.packages, warnings);
817
+ }
818
+ function manifestWith(packages, warnings) {
819
+ return warnings.length > 0 ? { ecosystem: "npm", packages, warnings } : { ecosystem: "npm", packages };
820
+ }
821
+ function staleWarning(source, missing) {
822
+ const sample = missing.slice(0, 3).join(", ");
823
+ const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : "";
824
+ return `${source} looks stale: package.json declares ${missing.length} dependenc${missing.length === 1 ? "y" : "ies"} it does not contain (${sample}${suffix}).`;
825
+ }
826
+ async function presentLockfiles(cwd) {
827
+ const probed = await Promise.all(
828
+ LOCKFILE_CANDIDATES.map(async (candidate) => {
829
+ const filePath = import_node_path5.default.join(cwd, candidate.filename);
830
+ return { ...candidate, filePath, present: await exists(filePath) };
831
+ })
832
+ );
833
+ return probed.filter((candidate) => candidate.present).map(({ filename, strategy, filePath }) => ({ ecosystem: "npm", filename, strategy, filePath }));
735
834
  }
736
835
  async function runStrategy(detected, cwd) {
737
836
  switch (detected.strategy) {
@@ -747,7 +846,7 @@ async function runStrategy(detected, cwd) {
747
846
  }
748
847
  async function exists(filePath) {
749
848
  try {
750
- await (0, import_promises5.access)(filePath);
849
+ await (0, import_promises6.access)(filePath);
751
850
  return true;
752
851
  } catch {
753
852
  return false;
@@ -934,8 +1033,8 @@ function isTimeoutError(cause) {
934
1033
  }
935
1034
 
936
1035
  // src/config.ts
937
- var import_promises6 = require("fs/promises");
938
- var import_node_path5 = __toESM(require("path"), 1);
1036
+ var import_promises7 = require("fs/promises");
1037
+ var import_node_path6 = __toESM(require("path"), 1);
939
1038
  var CONFIG_FILENAME = ".patchstackrc.json";
940
1039
  var DEFAULT_ENVIRONMENT = "production";
941
1040
  async function resolveConfig(options) {
@@ -972,9 +1071,9 @@ async function resolveConfig(options) {
972
1071
  };
973
1072
  }
974
1073
  async function writeConfigFile(cwd, config) {
975
- const target = import_node_path5.default.join(cwd, CONFIG_FILENAME);
1074
+ const target = import_node_path6.default.join(cwd, CONFIG_FILENAME);
976
1075
  const content = JSON.stringify(config, null, 2) + "\n";
977
- await (0, import_promises6.writeFile)(target, content, "utf8");
1076
+ await (0, import_promises7.writeFile)(target, content, "utf8");
978
1077
  return target;
979
1078
  }
980
1079
  async function persistSiteUuid(cwd, siteUuid) {
@@ -982,10 +1081,10 @@ async function persistSiteUuid(cwd, siteUuid) {
982
1081
  return writeConfigFile(cwd, { ...existing, siteUuid });
983
1082
  }
984
1083
  async function readConfigFile(cwd) {
985
- const target = import_node_path5.default.join(cwd, CONFIG_FILENAME);
1084
+ const target = import_node_path6.default.join(cwd, CONFIG_FILENAME);
986
1085
  let raw;
987
1086
  try {
988
- raw = await (0, import_promises6.readFile)(target, "utf8");
1087
+ raw = await (0, import_promises7.readFile)(target, "utf8");
989
1088
  } catch (err) {
990
1089
  if (err.code === "ENOENT") {
991
1090
  return {};