@eslint-config-snapshot/api 0.1.0 → 0.1.4

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 ADDED
@@ -0,0 +1,25 @@
1
+ # @eslint-config-snapshot/api
2
+
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix npm trusted publishing provenance metadata by adding repository/homepage/bugs fields to package manifests.
8
+
9
+ ## 0.1.3
10
+
11
+ ### Patch Changes
12
+
13
+ - Release patch version to validate trusted publishing flow with protected release environment.
14
+
15
+ ## 0.1.2
16
+
17
+ ### Patch Changes
18
+
19
+ - Test release bump to validate automated publish flow with aligned package versions and tag.
20
+
21
+ ## 0.1.1
22
+
23
+ ### Patch Changes
24
+
25
+ - Release patch version for API and CLI packages using the new Changesets workflow.
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@eslint-config-snapshot/api",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/gabrielmoreira/eslint-config-snapshot"
8
+ },
9
+ "homepage": "https://github.com/gabrielmoreira/eslint-config-snapshot#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/gabrielmoreira/eslint-config-snapshot/issues"
12
+ },
5
13
  "engines": {
6
14
  "node": ">=20.0.0"
7
15
  },
package/project.json CHANGED
@@ -31,5 +31,5 @@
31
31
  }
32
32
  }
33
33
  }
34
- }
35
-
34
+ }
35
+
package/src/config.ts CHANGED
@@ -1,121 +1,121 @@
1
- import { cosmiconfig } from 'cosmiconfig'
2
- import path from 'node:path'
3
-
4
-
1
+ import { cosmiconfig } from 'cosmiconfig'
2
+ import path from 'node:path'
3
+
4
+
5
5
  export type SnapshotConfig = {
6
- workspaceInput:
7
- | {
8
- mode: 'discover'
9
- }
10
- | {
11
- mode: 'manual'
12
- rootAbs?: string
13
- workspaces: string[]
14
- }
15
- grouping: {
16
- mode: 'match' | 'standalone'
17
- allowEmptyGroups?: boolean
18
- groups?: Array<{
19
- name: string
20
- match: string[]
21
- }>
22
- }
23
- sampling: {
24
- maxFilesPerWorkspace: number
25
- includeGlobs: string[]
26
- excludeGlobs: string[]
27
- hintGlobs: string[]
28
- }
29
- }
30
-
6
+ workspaceInput:
7
+ | {
8
+ mode: 'discover'
9
+ }
10
+ | {
11
+ mode: 'manual'
12
+ rootAbs?: string
13
+ workspaces: string[]
14
+ }
15
+ grouping: {
16
+ mode: 'match' | 'standalone'
17
+ allowEmptyGroups?: boolean
18
+ groups?: Array<{
19
+ name: string
20
+ match: string[]
21
+ }>
22
+ }
23
+ sampling: {
24
+ maxFilesPerWorkspace: number
25
+ includeGlobs: string[]
26
+ excludeGlobs: string[]
27
+ hintGlobs: string[]
28
+ }
29
+ }
30
+
31
31
  export const DEFAULT_CONFIG: SnapshotConfig = {
32
- workspaceInput: { mode: 'discover' },
33
- grouping: {
34
- mode: 'match',
35
- groups: [{ name: 'default', match: ['**/*'] }]
36
- },
37
- sampling: {
38
- maxFilesPerWorkspace: 8,
39
- includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
40
- excludeGlobs: ['**/node_modules/**', '**/dist/**'],
41
- hintGlobs: []
42
- }
43
- }
44
-
45
- const SPEC_SEARCH_PLACES = [
46
- '.eslint-config-snapshot.js',
47
- '.eslint-config-snapshot.cjs',
48
- '.eslint-config-snapshot.mjs',
49
- 'eslint-config-snapshot.config.js',
50
- 'eslint-config-snapshot.config.cjs',
51
- 'eslint-config-snapshot.config.mjs',
52
- 'package.json',
53
- '.eslint-config-snapshotrc',
54
- '.eslint-config-snapshotrc.json',
55
- '.eslint-config-snapshotrc.yaml',
56
- '.eslint-config-snapshotrc.yml',
57
- '.eslint-config-snapshotrc.js',
58
- '.eslint-config-snapshotrc.cjs',
59
- '.eslint-config-snapshotrc.mjs'
60
- ]
61
-
32
+ workspaceInput: { mode: 'discover' },
33
+ grouping: {
34
+ mode: 'match',
35
+ groups: [{ name: 'default', match: ['**/*'] }]
36
+ },
37
+ sampling: {
38
+ maxFilesPerWorkspace: 8,
39
+ includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
40
+ excludeGlobs: ['**/node_modules/**', '**/dist/**'],
41
+ hintGlobs: []
42
+ }
43
+ }
44
+
45
+ const SPEC_SEARCH_PLACES = [
46
+ '.eslint-config-snapshot.js',
47
+ '.eslint-config-snapshot.cjs',
48
+ '.eslint-config-snapshot.mjs',
49
+ 'eslint-config-snapshot.config.js',
50
+ 'eslint-config-snapshot.config.cjs',
51
+ 'eslint-config-snapshot.config.mjs',
52
+ 'package.json',
53
+ '.eslint-config-snapshotrc',
54
+ '.eslint-config-snapshotrc.json',
55
+ '.eslint-config-snapshotrc.yaml',
56
+ '.eslint-config-snapshotrc.yml',
57
+ '.eslint-config-snapshotrc.js',
58
+ '.eslint-config-snapshotrc.cjs',
59
+ '.eslint-config-snapshotrc.mjs'
60
+ ]
61
+
62
62
  export async function loadConfig(cwd?: string): Promise<SnapshotConfig> {
63
- const found = await findConfigPath(cwd)
64
- if (!found) {
65
- return DEFAULT_CONFIG
66
- }
67
-
68
- return found.config
69
- }
70
-
63
+ const found = await findConfigPath(cwd)
64
+ if (!found) {
65
+ return DEFAULT_CONFIG
66
+ }
67
+
68
+ return found.config
69
+ }
70
+
71
71
  export async function findConfigPath(
72
72
  cwd?: string
73
73
  ): Promise<{ path: string; config: SnapshotConfig } | null> {
74
- const root = path.resolve(cwd ?? process.cwd())
75
- const explorer = cosmiconfig('eslint-config-snapshot', {
76
- searchPlaces: SPEC_SEARCH_PLACES,
77
- stopDir: root
78
- })
79
-
80
- const result = await explorer.search(root)
81
- if (!result) {
82
- return null
83
- }
84
-
85
- const maybeConfig = await loadUserConfig(result.config)
86
-
74
+ const root = path.resolve(cwd ?? process.cwd())
75
+ const explorer = cosmiconfig('eslint-config-snapshot', {
76
+ searchPlaces: SPEC_SEARCH_PLACES,
77
+ stopDir: root
78
+ })
79
+
80
+ const result = await explorer.search(root)
81
+ if (!result) {
82
+ return null
83
+ }
84
+
85
+ const maybeConfig = await loadUserConfig(result.config)
86
+
87
87
  const config: SnapshotConfig = {
88
- ...DEFAULT_CONFIG,
89
- ...maybeConfig,
90
- grouping: {
91
- ...DEFAULT_CONFIG.grouping,
92
- ...maybeConfig.grouping
93
- },
94
- sampling: {
95
- ...DEFAULT_CONFIG.sampling,
96
- ...maybeConfig.sampling
97
- }
98
- }
99
-
100
- return {
101
- path: result.filepath,
102
- config
103
- }
104
- }
105
-
88
+ ...DEFAULT_CONFIG,
89
+ ...maybeConfig,
90
+ grouping: {
91
+ ...DEFAULT_CONFIG.grouping,
92
+ ...maybeConfig.grouping
93
+ },
94
+ sampling: {
95
+ ...DEFAULT_CONFIG.sampling,
96
+ ...maybeConfig.sampling
97
+ }
98
+ }
99
+
100
+ return {
101
+ path: result.filepath,
102
+ config
103
+ }
104
+ }
105
+
106
106
  async function loadUserConfig(rawConfig: unknown): Promise<Partial<SnapshotConfig>> {
107
- const resolved = typeof rawConfig === 'function' ? await rawConfig() : rawConfig
108
- if (resolved === null || resolved === undefined) {
109
- return {}
110
- }
111
-
112
- if (typeof resolved !== 'object' || Array.isArray(resolved)) {
113
- throw new TypeError('Invalid config export: expected object, function, or async function returning an object')
114
- }
115
-
107
+ const resolved = typeof rawConfig === 'function' ? await rawConfig() : rawConfig
108
+ if (resolved === null || resolved === undefined) {
109
+ return {}
110
+ }
111
+
112
+ if (typeof resolved !== 'object' || Array.isArray(resolved)) {
113
+ throw new TypeError('Invalid config export: expected object, function, or async function returning an object')
114
+ }
115
+
116
116
  return resolved as Partial<SnapshotConfig>
117
- }
118
-
117
+ }
118
+
119
119
  export type ConfigPreset = 'minimal' | 'full'
120
120
 
121
121
  export function getConfigScaffold(preset: ConfigPreset = 'minimal'): string {
@@ -127,13 +127,13 @@ export function getConfigScaffold(preset: ConfigPreset = 'minimal'): string {
127
127
  workspaceInput: { mode: 'discover' },
128
128
  grouping: {
129
129
  mode: 'match',
130
- groups: [{ name: 'default', match: ['**/*'] }]
131
- },
132
- sampling: {
133
- maxFilesPerWorkspace: 8,
134
- includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
135
- excludeGlobs: ['**/node_modules/**', '**/dist/**'],
136
- hintGlobs: []
130
+ groups: [{ name: 'default', match: ['**/*'] }]
131
+ },
132
+ sampling: {
133
+ maxFilesPerWorkspace: 8,
134
+ includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
135
+ excludeGlobs: ['**/node_modules/**', '**/dist/**'],
136
+ hintGlobs: []
137
137
  }
138
138
  }\n`
139
139
  }
package/src/core.ts CHANGED
@@ -52,4 +52,4 @@ export function normalizeSeverity(value: unknown): 'off' | 'warn' | 'error' {
52
52
  return 'error'
53
53
  }
54
54
  throw new Error(`Unsupported severity: ${String(value)}`)
55
- }
55
+ }
package/src/diff.ts CHANGED
@@ -100,4 +100,4 @@ export function hasDiff(diff: SnapshotDiff): boolean {
100
100
  diff.workspaceMembershipChanges.added.length > 0 ||
101
101
  diff.workspaceMembershipChanges.removed.length > 0
102
102
  )
103
- }
103
+ }
package/src/extract.ts CHANGED
@@ -1,119 +1,119 @@
1
- import { spawnSync } from 'node:child_process'
2
- import { existsSync, readFileSync } from 'node:fs'
3
- import { createRequire } from 'node:module'
4
- import path from 'node:path'
5
-
6
- import { canonicalizeJson, normalizeSeverity } from './core.js'
7
-
8
- export type NormalizedRuleEntry = [severity: 'off' | 'warn' | 'error'] | [severity: 'off' | 'warn' | 'error', options: unknown]
9
-
10
- export type ExtractedWorkspaceRules = Map<string, NormalizedRuleEntry>
11
-
12
- export function resolveEslintBinForWorkspace(workspaceAbs: string): string {
13
- const anchor = path.join(workspaceAbs, '__snapshot_anchor__.cjs')
14
- const req = createRequire(anchor)
15
- try {
16
- return req.resolve('eslint/bin/eslint.js')
17
- } catch {
18
- try {
19
- const eslintEntry = req.resolve('eslint')
20
- const eslintRoot = findPackageRoot(eslintEntry)
21
- const packageJsonPath = path.join(eslintRoot, 'package.json')
22
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { bin?: string | Record<string, string> }
23
- const relativeBin = resolveBinPath(packageJson.bin)
24
- const binAbs = path.resolve(eslintRoot, relativeBin)
25
-
26
- if (existsSync(binAbs)) {
27
- return binAbs
28
- }
29
- } catch {
30
- // ignore fallback errors and throw deterministic workspace-scoped message below
31
- }
32
-
33
- throw new Error(`Unable to resolve eslint from workspace: ${workspaceAbs}`)
34
- }
35
- }
36
-
37
- function resolveBinPath(bin: string | Record<string, string> | undefined): string {
38
- if (typeof bin === 'string') {
39
- return bin
40
- }
41
-
42
- if (typeof bin?.eslint === 'string') {
43
- return bin.eslint
44
- }
45
-
46
- return 'bin/eslint.js'
47
- }
48
-
49
- function findPackageRoot(entryAbs: string): string {
50
- let current = path.dirname(entryAbs)
51
- while (true) {
52
- const packageJsonPath = path.join(current, 'package.json')
53
- if (existsSync(packageJsonPath)) {
54
- return current
55
- }
56
-
57
- const parent = path.dirname(current)
58
- if (parent === current) {
59
- throw new Error('Package root not found')
60
- }
61
- current = parent
62
- }
63
- }
64
-
65
- export function extractRulesFromPrintConfig(workspaceAbs: string, fileAbs: string): ExtractedWorkspaceRules {
66
- const eslintBin = resolveEslintBinForWorkspace(workspaceAbs)
67
- const proc = spawnSync(process.execPath, [eslintBin, '--print-config', fileAbs], {
68
- cwd: workspaceAbs,
69
- encoding: 'utf8'
70
- })
71
-
72
- if (proc.status !== 0) {
73
- throw new Error(`Failed to run eslint --print-config for ${fileAbs}`)
74
- }
75
-
76
- const stdout = proc.stdout.trim()
77
- if (stdout.length === 0 || stdout === 'undefined') {
78
- throw new Error(`Empty ESLint print-config output for ${fileAbs}`)
79
- }
80
-
81
- let parsed: unknown
82
- try {
83
- parsed = JSON.parse(stdout)
84
- } catch {
85
- throw new Error(`Invalid JSON from eslint --print-config for ${fileAbs}`)
86
- }
87
-
88
- const rules = (parsed as { rules?: Record<string, unknown> }).rules ?? {}
89
- const normalized = new Map<string, NormalizedRuleEntry>()
90
-
91
- for (const [ruleName, ruleConfig] of Object.entries(rules)) {
92
- normalized.set(ruleName, normalizeRuleEntry(ruleConfig))
93
- }
94
-
95
- return normalized
96
- }
97
-
98
- function normalizeRuleEntry(raw: unknown): NormalizedRuleEntry {
99
- if (Array.isArray(raw)) {
100
- if (raw.length === 0) {
101
- throw new Error('Rule configuration array cannot be empty')
102
- }
103
-
104
- const severity = normalizeSeverity(raw[0])
105
- const rest = raw.slice(1).map((item) => canonicalizeJson(item))
106
-
107
- if (rest.length === 0) {
108
- return [severity]
109
- }
110
-
111
- if (rest.length === 1) {
112
- return [severity, rest[0]]
113
- }
114
-
115
- return [severity, canonicalizeJson(rest)]
116
- }
117
-
118
- return [normalizeSeverity(raw)]
119
- }
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import { createRequire } from 'node:module'
4
+ import path from 'node:path'
5
+
6
+ import { canonicalizeJson, normalizeSeverity } from './core.js'
7
+
8
+ export type NormalizedRuleEntry = [severity: 'off' | 'warn' | 'error'] | [severity: 'off' | 'warn' | 'error', options: unknown]
9
+
10
+ export type ExtractedWorkspaceRules = Map<string, NormalizedRuleEntry>
11
+
12
+ export function resolveEslintBinForWorkspace(workspaceAbs: string): string {
13
+ const anchor = path.join(workspaceAbs, '__snapshot_anchor__.cjs')
14
+ const req = createRequire(anchor)
15
+ try {
16
+ return req.resolve('eslint/bin/eslint.js')
17
+ } catch {
18
+ try {
19
+ const eslintEntry = req.resolve('eslint')
20
+ const eslintRoot = findPackageRoot(eslintEntry)
21
+ const packageJsonPath = path.join(eslintRoot, 'package.json')
22
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { bin?: string | Record<string, string> }
23
+ const relativeBin = resolveBinPath(packageJson.bin)
24
+ const binAbs = path.resolve(eslintRoot, relativeBin)
25
+
26
+ if (existsSync(binAbs)) {
27
+ return binAbs
28
+ }
29
+ } catch {
30
+ // ignore fallback errors and throw deterministic workspace-scoped message below
31
+ }
32
+
33
+ throw new Error(`Unable to resolve eslint from workspace: ${workspaceAbs}`)
34
+ }
35
+ }
36
+
37
+ function resolveBinPath(bin: string | Record<string, string> | undefined): string {
38
+ if (typeof bin === 'string') {
39
+ return bin
40
+ }
41
+
42
+ if (typeof bin?.eslint === 'string') {
43
+ return bin.eslint
44
+ }
45
+
46
+ return 'bin/eslint.js'
47
+ }
48
+
49
+ function findPackageRoot(entryAbs: string): string {
50
+ let current = path.dirname(entryAbs)
51
+ while (true) {
52
+ const packageJsonPath = path.join(current, 'package.json')
53
+ if (existsSync(packageJsonPath)) {
54
+ return current
55
+ }
56
+
57
+ const parent = path.dirname(current)
58
+ if (parent === current) {
59
+ throw new Error('Package root not found')
60
+ }
61
+ current = parent
62
+ }
63
+ }
64
+
65
+ export function extractRulesFromPrintConfig(workspaceAbs: string, fileAbs: string): ExtractedWorkspaceRules {
66
+ const eslintBin = resolveEslintBinForWorkspace(workspaceAbs)
67
+ const proc = spawnSync(process.execPath, [eslintBin, '--print-config', fileAbs], {
68
+ cwd: workspaceAbs,
69
+ encoding: 'utf8'
70
+ })
71
+
72
+ if (proc.status !== 0) {
73
+ throw new Error(`Failed to run eslint --print-config for ${fileAbs}`)
74
+ }
75
+
76
+ const stdout = proc.stdout.trim()
77
+ if (stdout.length === 0 || stdout === 'undefined') {
78
+ throw new Error(`Empty ESLint print-config output for ${fileAbs}`)
79
+ }
80
+
81
+ let parsed: unknown
82
+ try {
83
+ parsed = JSON.parse(stdout)
84
+ } catch {
85
+ throw new Error(`Invalid JSON from eslint --print-config for ${fileAbs}`)
86
+ }
87
+
88
+ const rules = (parsed as { rules?: Record<string, unknown> }).rules ?? {}
89
+ const normalized = new Map<string, NormalizedRuleEntry>()
90
+
91
+ for (const [ruleName, ruleConfig] of Object.entries(rules)) {
92
+ normalized.set(ruleName, normalizeRuleEntry(ruleConfig))
93
+ }
94
+
95
+ return normalized
96
+ }
97
+
98
+ function normalizeRuleEntry(raw: unknown): NormalizedRuleEntry {
99
+ if (Array.isArray(raw)) {
100
+ if (raw.length === 0) {
101
+ throw new Error('Rule configuration array cannot be empty')
102
+ }
103
+
104
+ const severity = normalizeSeverity(raw[0])
105
+ const rest = raw.slice(1).map((item) => canonicalizeJson(item))
106
+
107
+ if (rest.length === 0) {
108
+ return [severity]
109
+ }
110
+
111
+ if (rest.length === 1) {
112
+ return [severity, rest[0]]
113
+ }
114
+
115
+ return [severity, canonicalizeJson(rest)]
116
+ }
117
+
118
+ return [normalizeSeverity(raw)]
119
+ }