@eslint-config-snapshot/api 1.0.0 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @eslint-config-snapshot/api
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Minor release: improve skipped workspace messaging and OSS compatibility documentation.
8
+
3
9
  ## 1.0.0
4
10
 
5
11
  ### Major Changes
package/dist/index.cjs CHANGED
@@ -104,27 +104,117 @@ function normalizeSeverity(value) {
104
104
 
105
105
  // src/workspace.ts
106
106
  var import_get_packages = require("@manypkg/get-packages");
107
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
108
+ var import_promises = require("fs/promises");
107
109
  var import_node_path = __toESM(require("path"), 1);
108
110
  var import_picomatch = __toESM(require("picomatch"), 1);
109
111
  async function discoverWorkspaces(options) {
110
112
  const cwd = options?.cwd ? import_node_path.default.resolve(options.cwd) : process.cwd();
111
113
  const workspaceInput = options?.workspaceInput ?? { mode: "discover" };
112
114
  if (workspaceInput.mode === "manual") {
113
- const rootAbs2 = import_node_path.default.resolve(workspaceInput.rootAbs ?? cwd);
115
+ const rootAbs = import_node_path.default.resolve(workspaceInput.rootAbs ?? cwd);
114
116
  return {
115
- rootAbs: rootAbs2,
117
+ rootAbs,
116
118
  workspacesRel: sortUnique(workspaceInput.workspaces)
117
119
  };
118
120
  }
119
- const { rootDir, packages } = await (0, import_get_packages.getPackages)(cwd);
120
- const workspacesAbs = packages.map((pkg) => pkg.dir);
121
- const rootAbs = rootDir ? import_node_path.default.resolve(rootDir) : lowestCommonAncestor(workspacesAbs);
122
- const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(import_node_path.default.relative(rootAbs, entry))));
121
+ try {
122
+ const { rootDir, packages } = await (0, import_get_packages.getPackages)(cwd);
123
+ const workspacesAbs = packages.map((pkg) => pkg.dir);
124
+ const rootAbs = rootDir ? import_node_path.default.resolve(rootDir) : lowestCommonAncestor(workspacesAbs);
125
+ const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(import_node_path.default.relative(rootAbs, entry))));
126
+ if (workspacesRel.length > 1 || workspacesRel.length === 1 && workspacesRel[0] !== ".") {
127
+ return {
128
+ rootAbs,
129
+ workspacesRel
130
+ };
131
+ }
132
+ const packageJsonFallback2 = await discoverWorkspacesFromPackageJson(cwd);
133
+ if (packageJsonFallback2 && packageJsonFallback2.workspacesRel.length > 0) {
134
+ return packageJsonFallback2;
135
+ }
136
+ if (workspacesRel.length > 0) {
137
+ return {
138
+ rootAbs,
139
+ workspacesRel
140
+ };
141
+ }
142
+ } catch {
143
+ }
144
+ const packageJsonFallback = await discoverWorkspacesFromPackageJson(cwd);
145
+ if (packageJsonFallback) {
146
+ return packageJsonFallback;
147
+ }
148
+ return {
149
+ rootAbs: cwd,
150
+ workspacesRel: ["."]
151
+ };
152
+ }
153
+ async function discoverWorkspacesFromPackageJson(cwd) {
154
+ const packageJsonPath = import_node_path.default.join(cwd, "package.json");
155
+ const packageJsonRaw = await safeReadFile(packageJsonPath);
156
+ if (!packageJsonRaw) {
157
+ return null;
158
+ }
159
+ const parsed = safeJsonParse(packageJsonRaw);
160
+ if (!isObjectRecord(parsed) || Array.isArray(parsed)) {
161
+ return null;
162
+ }
163
+ const workspaceGlobs = extractWorkspaceGlobs(parsed);
164
+ if (workspaceGlobs.length === 0) {
165
+ return null;
166
+ }
167
+ const workspacePackageJsonGlobs = workspaceGlobs.map((entry) => normalizePath(import_node_path.default.posix.join(entry, "package.json")));
168
+ const foundPackageJsonFiles = await (0, import_fast_glob.default)(workspacePackageJsonGlobs, {
169
+ cwd,
170
+ onlyFiles: true,
171
+ dot: true,
172
+ unique: true,
173
+ ignore: ["**/node_modules/**"]
174
+ });
175
+ const workspacesRel = sortUnique(foundPackageJsonFiles.map((entry) => normalizePath(import_node_path.default.posix.dirname(entry))));
176
+ if (workspacesRel.length === 0) {
177
+ return {
178
+ rootAbs: cwd,
179
+ workspacesRel: ["."]
180
+ };
181
+ }
123
182
  return {
124
- rootAbs,
183
+ rootAbs: cwd,
125
184
  workspacesRel
126
185
  };
127
186
  }
187
+ async function safeReadFile(filePath) {
188
+ try {
189
+ return await (0, import_promises.readFile)(filePath, "utf8");
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+ function safeJsonParse(raw) {
195
+ try {
196
+ return JSON.parse(raw);
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+ function isObjectRecord(value) {
202
+ return value !== null && typeof value === "object";
203
+ }
204
+ function extractWorkspaceGlobs(packageJson) {
205
+ const workspaces = packageJson.workspaces;
206
+ if (Array.isArray(workspaces)) {
207
+ return workspaces.filter((entry) => typeof entry === "string");
208
+ }
209
+ if (!workspaces || typeof workspaces !== "object" || Array.isArray(workspaces)) {
210
+ return [];
211
+ }
212
+ const packages = workspaces.packages;
213
+ if (Array.isArray(packages)) {
214
+ return packages.filter((entry) => typeof entry === "string");
215
+ }
216
+ return [];
217
+ }
128
218
  function assignGroupsByMatch(workspacesRel, groups) {
129
219
  const sortedWorkspaces = sortUnique([...workspacesRel]);
130
220
  const assignments = /* @__PURE__ */ new Map();
@@ -186,11 +276,11 @@ function lowestCommonAncestor(paths) {
186
276
 
187
277
  // src/sampling.ts
188
278
  var import_debug = __toESM(require("debug"), 1);
189
- var import_fast_glob = __toESM(require("fast-glob"), 1);
279
+ var import_fast_glob2 = __toESM(require("fast-glob"), 1);
190
280
  var debugSampling = (0, import_debug.default)("eslint-config-snapshot:sampling");
191
281
  async function sampleWorkspaceFiles(workspaceAbs, config) {
192
282
  const startedAt = Date.now();
193
- const all = await (0, import_fast_glob.default)(config.includeGlobs, {
283
+ const all = await (0, import_fast_glob2.default)(config.includeGlobs, {
194
284
  cwd: workspaceAbs,
195
285
  ignore: config.excludeGlobs,
196
286
  onlyFiles: true,
@@ -727,7 +817,7 @@ function normalizeRuleEntry(raw) {
727
817
  }
728
818
 
729
819
  // src/snapshot.ts
730
- var import_promises = require("fs/promises");
820
+ var import_promises2 = require("fs/promises");
731
821
  var import_node_path3 = __toESM(require("path"), 1);
732
822
  function aggregateRules(ruleMaps) {
733
823
  const aggregated = /* @__PURE__ */ new Map();
@@ -763,16 +853,16 @@ function buildSnapshot(groupId, workspaces, rules) {
763
853
  };
764
854
  }
765
855
  async function writeSnapshotFile(snapshotDirAbs, snapshot) {
766
- await (0, import_promises.mkdir)(snapshotDirAbs, { recursive: true });
856
+ await (0, import_promises2.mkdir)(snapshotDirAbs, { recursive: true });
767
857
  const filePath = import_node_path3.default.join(snapshotDirAbs, `${snapshot.groupId}.json`);
768
- await (0, import_promises.mkdir)(import_node_path3.default.dirname(filePath), { recursive: true });
858
+ await (0, import_promises2.mkdir)(import_node_path3.default.dirname(filePath), { recursive: true });
769
859
  const payload = JSON.stringify(snapshot, null, 2);
770
- await (0, import_promises.writeFile)(filePath, `${payload}
860
+ await (0, import_promises2.writeFile)(filePath, `${payload}
771
861
  `, "utf8");
772
862
  return filePath;
773
863
  }
774
864
  async function readSnapshotFile(fileAbs) {
775
- const raw = await (0, import_promises.readFile)(fileAbs, "utf8");
865
+ const raw = await (0, import_promises2.readFile)(fileAbs, "utf8");
776
866
  return JSON.parse(raw);
777
867
  }
778
868
  function toVariantKey(entry) {
package/dist/index.js CHANGED
@@ -47,27 +47,117 @@ function normalizeSeverity(value) {
47
47
 
48
48
  // src/workspace.ts
49
49
  import { getPackages } from "@manypkg/get-packages";
50
+ import fg from "fast-glob";
51
+ import { readFile } from "fs/promises";
50
52
  import path from "path";
51
53
  import picomatch from "picomatch";
52
54
  async function discoverWorkspaces(options) {
53
55
  const cwd = options?.cwd ? path.resolve(options.cwd) : process.cwd();
54
56
  const workspaceInput = options?.workspaceInput ?? { mode: "discover" };
55
57
  if (workspaceInput.mode === "manual") {
56
- const rootAbs2 = path.resolve(workspaceInput.rootAbs ?? cwd);
58
+ const rootAbs = path.resolve(workspaceInput.rootAbs ?? cwd);
57
59
  return {
58
- rootAbs: rootAbs2,
60
+ rootAbs,
59
61
  workspacesRel: sortUnique(workspaceInput.workspaces)
60
62
  };
61
63
  }
62
- const { rootDir, packages } = await getPackages(cwd);
63
- const workspacesAbs = packages.map((pkg) => pkg.dir);
64
- const rootAbs = rootDir ? path.resolve(rootDir) : lowestCommonAncestor(workspacesAbs);
65
- const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(path.relative(rootAbs, entry))));
64
+ try {
65
+ const { rootDir, packages } = await getPackages(cwd);
66
+ const workspacesAbs = packages.map((pkg) => pkg.dir);
67
+ const rootAbs = rootDir ? path.resolve(rootDir) : lowestCommonAncestor(workspacesAbs);
68
+ const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(path.relative(rootAbs, entry))));
69
+ if (workspacesRel.length > 1 || workspacesRel.length === 1 && workspacesRel[0] !== ".") {
70
+ return {
71
+ rootAbs,
72
+ workspacesRel
73
+ };
74
+ }
75
+ const packageJsonFallback2 = await discoverWorkspacesFromPackageJson(cwd);
76
+ if (packageJsonFallback2 && packageJsonFallback2.workspacesRel.length > 0) {
77
+ return packageJsonFallback2;
78
+ }
79
+ if (workspacesRel.length > 0) {
80
+ return {
81
+ rootAbs,
82
+ workspacesRel
83
+ };
84
+ }
85
+ } catch {
86
+ }
87
+ const packageJsonFallback = await discoverWorkspacesFromPackageJson(cwd);
88
+ if (packageJsonFallback) {
89
+ return packageJsonFallback;
90
+ }
91
+ return {
92
+ rootAbs: cwd,
93
+ workspacesRel: ["."]
94
+ };
95
+ }
96
+ async function discoverWorkspacesFromPackageJson(cwd) {
97
+ const packageJsonPath = path.join(cwd, "package.json");
98
+ const packageJsonRaw = await safeReadFile(packageJsonPath);
99
+ if (!packageJsonRaw) {
100
+ return null;
101
+ }
102
+ const parsed = safeJsonParse(packageJsonRaw);
103
+ if (!isObjectRecord(parsed) || Array.isArray(parsed)) {
104
+ return null;
105
+ }
106
+ const workspaceGlobs = extractWorkspaceGlobs(parsed);
107
+ if (workspaceGlobs.length === 0) {
108
+ return null;
109
+ }
110
+ const workspacePackageJsonGlobs = workspaceGlobs.map((entry) => normalizePath(path.posix.join(entry, "package.json")));
111
+ const foundPackageJsonFiles = await fg(workspacePackageJsonGlobs, {
112
+ cwd,
113
+ onlyFiles: true,
114
+ dot: true,
115
+ unique: true,
116
+ ignore: ["**/node_modules/**"]
117
+ });
118
+ const workspacesRel = sortUnique(foundPackageJsonFiles.map((entry) => normalizePath(path.posix.dirname(entry))));
119
+ if (workspacesRel.length === 0) {
120
+ return {
121
+ rootAbs: cwd,
122
+ workspacesRel: ["."]
123
+ };
124
+ }
66
125
  return {
67
- rootAbs,
126
+ rootAbs: cwd,
68
127
  workspacesRel
69
128
  };
70
129
  }
130
+ async function safeReadFile(filePath) {
131
+ try {
132
+ return await readFile(filePath, "utf8");
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+ function safeJsonParse(raw) {
138
+ try {
139
+ return JSON.parse(raw);
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+ function isObjectRecord(value) {
145
+ return value !== null && typeof value === "object";
146
+ }
147
+ function extractWorkspaceGlobs(packageJson) {
148
+ const workspaces = packageJson.workspaces;
149
+ if (Array.isArray(workspaces)) {
150
+ return workspaces.filter((entry) => typeof entry === "string");
151
+ }
152
+ if (!workspaces || typeof workspaces !== "object" || Array.isArray(workspaces)) {
153
+ return [];
154
+ }
155
+ const packages = workspaces.packages;
156
+ if (Array.isArray(packages)) {
157
+ return packages.filter((entry) => typeof entry === "string");
158
+ }
159
+ return [];
160
+ }
71
161
  function assignGroupsByMatch(workspacesRel, groups) {
72
162
  const sortedWorkspaces = sortUnique([...workspacesRel]);
73
163
  const assignments = /* @__PURE__ */ new Map();
@@ -129,11 +219,11 @@ function lowestCommonAncestor(paths) {
129
219
 
130
220
  // src/sampling.ts
131
221
  import createDebug from "debug";
132
- import fg from "fast-glob";
222
+ import fg2 from "fast-glob";
133
223
  var debugSampling = createDebug("eslint-config-snapshot:sampling");
134
224
  async function sampleWorkspaceFiles(workspaceAbs, config) {
135
225
  const startedAt = Date.now();
136
- const all = await fg(config.includeGlobs, {
226
+ const all = await fg2(config.includeGlobs, {
137
227
  cwd: workspaceAbs,
138
228
  ignore: config.excludeGlobs,
139
229
  onlyFiles: true,
@@ -670,7 +760,7 @@ function normalizeRuleEntry(raw) {
670
760
  }
671
761
 
672
762
  // src/snapshot.ts
673
- import { mkdir, readFile, writeFile } from "fs/promises";
763
+ import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
674
764
  import path3 from "path";
675
765
  function aggregateRules(ruleMaps) {
676
766
  const aggregated = /* @__PURE__ */ new Map();
@@ -715,7 +805,7 @@ async function writeSnapshotFile(snapshotDirAbs, snapshot) {
715
805
  return filePath;
716
806
  }
717
807
  async function readSnapshotFile(fileAbs) {
718
- const raw = await readFile(fileAbs, "utf8");
808
+ const raw = await readFile2(fileAbs, "utf8");
719
809
  return JSON.parse(raw);
720
810
  }
721
811
  function toVariantKey(entry) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-config-snapshot/api",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
package/src/workspace.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { getPackages } from '@manypkg/get-packages'
2
+ import fg from 'fast-glob'
3
+ import { readFile } from 'node:fs/promises'
2
4
  import path from 'node:path'
3
5
  import picomatch from 'picomatch'
4
6
 
@@ -44,17 +46,122 @@ export async function discoverWorkspaces(options?: {
44
46
  }
45
47
  }
46
48
 
47
- const { rootDir, packages } = await getPackages(cwd)
48
- const workspacesAbs = packages.map((pkg) => pkg.dir)
49
- const rootAbs = rootDir ? path.resolve(rootDir) : lowestCommonAncestor(workspacesAbs)
50
- const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(path.relative(rootAbs, entry))))
49
+ try {
50
+ const { rootDir, packages } = await getPackages(cwd)
51
+ const workspacesAbs = packages.map((pkg) => pkg.dir)
52
+ const rootAbs = rootDir ? path.resolve(rootDir) : lowestCommonAncestor(workspacesAbs)
53
+ const workspacesRel = sortUnique(workspacesAbs.map((entry) => normalizePath(path.relative(rootAbs, entry))))
54
+ if (workspacesRel.length > 1 || (workspacesRel.length === 1 && workspacesRel[0] !== '.')) {
55
+ return {
56
+ rootAbs,
57
+ workspacesRel
58
+ }
59
+ }
60
+
61
+ const packageJsonFallback = await discoverWorkspacesFromPackageJson(cwd)
62
+ if (packageJsonFallback && packageJsonFallback.workspacesRel.length > 0) {
63
+ return packageJsonFallback
64
+ }
65
+
66
+ if (workspacesRel.length > 0) {
67
+ return {
68
+ rootAbs,
69
+ workspacesRel
70
+ }
71
+ }
72
+ } catch {
73
+ // Fallback to package.json workspace patterns when package manager metadata is unavailable.
74
+ }
75
+
76
+ const packageJsonFallback = await discoverWorkspacesFromPackageJson(cwd)
77
+ if (packageJsonFallback) {
78
+ return packageJsonFallback
79
+ }
80
+
81
+ return {
82
+ rootAbs: cwd,
83
+ workspacesRel: ['.']
84
+ }
85
+ }
86
+
87
+ async function discoverWorkspacesFromPackageJson(cwd: string): Promise<WorkspaceDiscovery | null> {
88
+ const packageJsonPath = path.join(cwd, 'package.json')
89
+ const packageJsonRaw = await safeReadFile(packageJsonPath)
90
+ if (!packageJsonRaw) {
91
+ return null
92
+ }
93
+
94
+ const parsed = safeJsonParse(packageJsonRaw)
95
+ if (!isObjectRecord(parsed) || Array.isArray(parsed)) {
96
+ return null
97
+ }
98
+
99
+ const workspaceGlobs = extractWorkspaceGlobs(parsed)
100
+ if (workspaceGlobs.length === 0) {
101
+ return null
102
+ }
103
+
104
+ const workspacePackageJsonGlobs = workspaceGlobs.map((entry) => normalizePath(path.posix.join(entry, 'package.json')))
105
+ const foundPackageJsonFiles = await fg(workspacePackageJsonGlobs, {
106
+ cwd,
107
+ onlyFiles: true,
108
+ dot: true,
109
+ unique: true,
110
+ ignore: ['**/node_modules/**']
111
+ })
112
+
113
+ const workspacesRel = sortUnique(foundPackageJsonFiles.map((entry) => normalizePath(path.posix.dirname(entry))))
114
+ if (workspacesRel.length === 0) {
115
+ return {
116
+ rootAbs: cwd,
117
+ workspacesRel: ['.']
118
+ }
119
+ }
51
120
 
52
121
  return {
53
- rootAbs,
122
+ rootAbs: cwd,
54
123
  workspacesRel
55
124
  }
56
125
  }
57
126
 
127
+ async function safeReadFile(filePath: string): Promise<string | null> {
128
+ try {
129
+ return await readFile(filePath, 'utf8')
130
+ } catch {
131
+ return null
132
+ }
133
+ }
134
+
135
+ function safeJsonParse(raw: string): unknown {
136
+ try {
137
+ return JSON.parse(raw)
138
+ } catch {
139
+ return null
140
+ }
141
+ }
142
+
143
+ function isObjectRecord(value: unknown): value is Record<string, unknown> {
144
+ return value !== null && typeof value === 'object'
145
+ }
146
+
147
+ function extractWorkspaceGlobs(packageJson: Record<string, unknown>): string[] {
148
+ const workspaces = packageJson.workspaces
149
+ if (Array.isArray(workspaces)) {
150
+ return workspaces.filter((entry): entry is string => typeof entry === 'string')
151
+ }
152
+
153
+ if (!workspaces || typeof workspaces !== 'object' || Array.isArray(workspaces)) {
154
+ return []
155
+ }
156
+
157
+ const packages = (workspaces as { packages?: unknown }).packages
158
+ if (Array.isArray(packages)) {
159
+ return packages.filter((entry): entry is string => typeof entry === 'string')
160
+ }
161
+
162
+ return []
163
+ }
164
+
58
165
  export function assignGroupsByMatch(workspacesRel: readonly string[], groups: readonly GroupDefinition[]): GroupAssignment[] {
59
166
  const sortedWorkspaces = sortUnique([...workspacesRel])
60
167
  const assignments = new Map<string, string[]>()
@@ -1,6 +1,9 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import path from 'node:path'
1
4
  import { describe, expect, it } from 'vitest'
2
5
 
3
- import { assignGroupsByMatch } from '../src/index.js'
6
+ import { assignGroupsByMatch, discoverWorkspaces } from '../src/index.js'
4
7
 
5
8
  describe('assignGroupsByMatch', () => {
6
9
  it('assigns by first matching group and supports negatives', () => {
@@ -23,3 +26,46 @@ describe('assignGroupsByMatch', () => {
23
26
  )
24
27
  })
25
28
  })
29
+
30
+ describe('discoverWorkspaces', () => {
31
+ it('falls back to package.json workspaces when package manager metadata is unavailable', async () => {
32
+ const root = await mkdtemp(path.join(tmpdir(), 'snapshot-workspaces-'))
33
+ await writeFile(
34
+ path.join(root, 'package.json'),
35
+ JSON.stringify(
36
+ {
37
+ name: 'fixture',
38
+ private: true,
39
+ workspaces: ['packages/*']
40
+ },
41
+ null,
42
+ 2
43
+ )
44
+ )
45
+ await mkdir(path.join(root, 'packages/a'), { recursive: true })
46
+ await mkdir(path.join(root, 'packages/b'), { recursive: true })
47
+ await writeFile(path.join(root, 'packages/a/package.json'), JSON.stringify({ name: 'a', version: '1.0.0' }, null, 2))
48
+ await writeFile(path.join(root, 'packages/b/package.json'), JSON.stringify({ name: 'b', version: '1.0.0' }, null, 2))
49
+
50
+ try {
51
+ const discovery = await discoverWorkspaces({ cwd: root })
52
+ expect(discovery.rootAbs).toBe(root)
53
+ expect(discovery.workspacesRel).toEqual(['packages/a', 'packages/b'])
54
+ } finally {
55
+ await rm(root, { recursive: true, force: true })
56
+ }
57
+ })
58
+
59
+ it('falls back to current directory when workspace discovery cannot find matches', async () => {
60
+ const root = await mkdtemp(path.join(tmpdir(), 'snapshot-workspaces-empty-'))
61
+ await writeFile(path.join(root, 'package.json'), JSON.stringify({ name: 'fixture', private: true, workspaces: ['packages/*'] }, null, 2))
62
+
63
+ try {
64
+ const discovery = await discoverWorkspaces({ cwd: root })
65
+ expect(discovery.rootAbs).toBe(root)
66
+ expect(discovery.workspacesRel).toEqual(['.'])
67
+ } finally {
68
+ await rm(root, { recursive: true, force: true })
69
+ }
70
+ })
71
+ })