skyltmax_config 0.0.8 → 0.0.10

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.
data/prettier.js CHANGED
@@ -1,11 +1,3 @@
1
- // Try to load the Tailwind plugin if available
2
- let tailwindPlugin
3
- try {
4
- tailwindPlugin = await import("prettier-plugin-tailwindcss")
5
- } catch {
6
- // Plugin not installed, that's okay
7
- }
8
-
9
1
  /** @type {import("prettier").Options} */
10
2
  export const config = {
11
3
  arrowParens: "avoid",
@@ -47,12 +39,9 @@ export const config = {
47
39
  },
48
40
  },
49
41
  ],
50
- // Only include Tailwind plugin and config if it's available
51
- ...(tailwindPlugin && {
52
- plugins: ["prettier-plugin-tailwindcss"],
53
- tailwindAttributes: ["class", "className", ".*[cC]lassName"],
54
- tailwindFunctions: ["clsx", "cn", "twcn"],
55
- }),
42
+ plugins: ["prettier-plugin-tailwindcss"],
43
+ tailwindAttributes: ["class", "className", ".*[cC]lassName"],
44
+ tailwindFunctions: ["clsx", "cn", "twcn"],
56
45
  }
57
46
 
58
47
  // this is for backward compatibility
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, realpathSync } from "node:fs"
4
+ import { readFile } from "node:fs/promises"
5
+ import { createRequire } from "node:module"
6
+ import { dirname, resolve } from "node:path"
7
+ import { fileURLToPath } from "node:url"
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = dirname(__filename)
11
+ const defaultManifestPath = resolve(__dirname, "..", "..", "package.json")
12
+ const defaultConsumerRoot = process.env.INIT_CWD ?? process.env.npm_config_local_prefix ?? process.cwd()
13
+
14
+ async function readManifest(path) {
15
+ const raw = await readFile(path, "utf8")
16
+ return JSON.parse(raw)
17
+ }
18
+
19
+ function createConsumerRequire(root) {
20
+ const candidate = resolve(root, "package.json")
21
+ if (existsSync(candidate)) {
22
+ return createRequire(candidate)
23
+ }
24
+
25
+ return createRequire(import.meta.url)
26
+ }
27
+
28
+ export async function auditPeerDependencies({
29
+ manifestPath = defaultManifestPath,
30
+ consumerRoot = defaultConsumerRoot,
31
+ } = {}) {
32
+ const manifest = await readManifest(manifestPath)
33
+ const peers = Object.entries(manifest.peerDependencies ?? {})
34
+
35
+ if (!peers.length) {
36
+ return {
37
+ peers,
38
+ missing: [],
39
+ mismatched: [],
40
+ }
41
+ }
42
+
43
+ const requireFromConsumer = createConsumerRequire(consumerRoot)
44
+ const missing = []
45
+ const mismatched = []
46
+
47
+ for (const [name, expectedVersion] of peers) {
48
+ let resolvedPackageJson
49
+ try {
50
+ resolvedPackageJson = requireFromConsumer.resolve(`${name}/package.json`)
51
+ } catch (error) {
52
+ missing.push({ name, expectedVersion, reason: error.message })
53
+ continue
54
+ }
55
+
56
+ try {
57
+ const pkg = JSON.parse(await readFile(resolvedPackageJson, "utf8"))
58
+ const actualVersion = pkg.version
59
+ if (actualVersion !== expectedVersion) {
60
+ mismatched.push({ name, expectedVersion, actualVersion })
61
+ }
62
+ } catch (error) {
63
+ mismatched.push({ name, expectedVersion, actualVersion: "unknown", reason: error.message })
64
+ }
65
+ }
66
+
67
+ return {
68
+ peers,
69
+ missing,
70
+ mismatched,
71
+ }
72
+ }
73
+
74
+ export function formatAuditMessage({ missing, mismatched }) {
75
+ if (!missing.length && !mismatched.length) {
76
+ return null
77
+ }
78
+
79
+ const lines = ["[signmax-config] Peer dependency check detected issues:"]
80
+
81
+ if (missing.length) {
82
+ lines.push(" Missing peers:")
83
+ for (const item of missing) {
84
+ lines.push(` - ${item.name}@${item.expectedVersion}`)
85
+ }
86
+ }
87
+
88
+ if (mismatched.length) {
89
+ lines.push(" Version mismatches:")
90
+ for (const item of mismatched) {
91
+ const details = item.actualVersion ? ` (found ${item.actualVersion})` : ""
92
+ lines.push(` - ${item.name}@${item.expectedVersion}${details}`)
93
+ }
94
+ }
95
+
96
+ lines.push(' Run "npx signmax-config-peers" to install the correct versions.')
97
+
98
+ return lines.join("\n")
99
+ }
100
+
101
+ export async function runPostinstallAudit() {
102
+ try {
103
+ const result = await auditPeerDependencies()
104
+ const message = formatAuditMessage(result)
105
+
106
+ if (message) {
107
+ console.warn(message)
108
+ }
109
+ } catch (error) {
110
+ console.warn(`[signmax-config] Peer dependency check skipped: ${error.message}`)
111
+ }
112
+ }
113
+
114
+ const isDirectExecution = (() => {
115
+ const [argvPath] = process.argv.slice(1, 2)
116
+
117
+ if (!argvPath) return false
118
+
119
+ try {
120
+ return realpathSync(argvPath) === realpathSync(__filename)
121
+ } catch (error) {
122
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
123
+ return false
124
+ }
125
+
126
+ throw error
127
+ }
128
+ })()
129
+
130
+ if (isDirectExecution) {
131
+ runPostinstallAudit()
132
+ }
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process"
3
+ import { existsSync, realpathSync } from "node:fs"
4
+ import { readFile } from "node:fs/promises"
5
+ import { dirname, resolve } from "node:path"
6
+ import { fileURLToPath } from "node:url"
7
+
8
+ const HELP = `Usage: signmax-config-peers [options]
9
+
10
+ Options:
11
+ --manager <name> npm | pnpm | bun (auto-detected by default)
12
+ --dry-run Print the install command without running it
13
+ --help Show this help message
14
+ `
15
+
16
+ const __filename = fileURLToPath(import.meta.url)
17
+ const __dirname = dirname(__filename)
18
+ const defaultManifestPath = resolve(__dirname, "..", "..", "package.json")
19
+
20
+ export async function loadPeerDependencies(manifestPath = defaultManifestPath) {
21
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"))
22
+ const peers = manifest.peerDependencies ?? {}
23
+ return Object.entries(peers).map(([name, version]) => `${name}@${version}`)
24
+ }
25
+
26
+ export function detectManager({ managerArg, userAgent = "", cwd = process.cwd() } = {}) {
27
+ if (managerArg) return managerArg
28
+
29
+ if (userAgent.startsWith("pnpm/")) return "pnpm"
30
+ if (userAgent.startsWith("bun/")) return "bun"
31
+ if (userAgent.startsWith("npm/")) return "npm"
32
+
33
+ if (existsSync(resolve(cwd, "pnpm-lock.yaml"))) return "pnpm"
34
+ if (existsSync(resolve(cwd, "bun.lockb"))) return "bun"
35
+ if (existsSync(resolve(cwd, "package-lock.json"))) return "npm"
36
+
37
+ return "npm"
38
+ }
39
+
40
+ export function buildInstallCommand(manager, packages) {
41
+ if (!packages.length) {
42
+ throw new Error("No peer dependencies to install.")
43
+ }
44
+
45
+ const commandByManager = {
46
+ npm: {
47
+ command: "npm",
48
+ args: ["install", "--save-dev", "--save-exact", ...packages],
49
+ },
50
+ pnpm: {
51
+ command: "pnpm",
52
+ args: ["add", "-D", "--save-exact", ...packages],
53
+ },
54
+ bun: {
55
+ command: "bun",
56
+ args: ["add", "--dev", "--exact", ...packages],
57
+ },
58
+ }
59
+
60
+ const selected = commandByManager[manager]
61
+
62
+ if (!selected) {
63
+ const supported = Object.keys(commandByManager).join(", ")
64
+ throw new Error(`Unsupported package manager: ${manager}. Supported managers: ${supported}`)
65
+ }
66
+
67
+ return selected
68
+ }
69
+
70
+ export function formatCommand({ command, args }) {
71
+ return `${command} ${args.join(" ")}`
72
+ }
73
+
74
+ export async function prepareInstall({
75
+ managerArg,
76
+ cwd = process.cwd(),
77
+ env = process.env,
78
+ manifestPath = defaultManifestPath,
79
+ } = {}) {
80
+ const packages = await loadPeerDependencies(manifestPath)
81
+
82
+ if (packages.length === 0) {
83
+ return { packages: [] }
84
+ }
85
+
86
+ const manager = detectManager({ managerArg, userAgent: env.npm_config_user_agent ?? "", cwd })
87
+ const command = buildInstallCommand(manager, packages)
88
+
89
+ return {
90
+ manager,
91
+ packages,
92
+ command: command.command,
93
+ args: command.args,
94
+ printable: formatCommand(command),
95
+ }
96
+ }
97
+
98
+ export async function runCli({ args = process.argv.slice(2), env = process.env, cwd = process.cwd() } = {}) {
99
+ if (args.includes("--help")) {
100
+ process.stdout.write(HELP)
101
+ return 0
102
+ }
103
+
104
+ const argValue = flag => {
105
+ const index = args.indexOf(flag)
106
+ if (index === -1) return undefined
107
+ return args[index + 1]
108
+ }
109
+
110
+ const managerArg = argValue("--manager")
111
+ const dryRun = args.includes("--dry-run")
112
+
113
+ try {
114
+ const result = await prepareInstall({ managerArg, cwd, env })
115
+
116
+ if (!result.packages || result.packages.length === 0) {
117
+ process.stdout.write("No peer dependencies found on @signmax/config.\n")
118
+ return 0
119
+ }
120
+
121
+ if (dryRun) {
122
+ process.stdout.write(`${result.printable}\n`)
123
+ return 0
124
+ }
125
+
126
+ process.stdout.write(`Running: ${result.printable}\n`)
127
+
128
+ const child = spawn(result.command, result.args, {
129
+ stdio: "inherit",
130
+ })
131
+
132
+ return await new Promise((resolve, reject) => {
133
+ child.on("close", code => {
134
+ if (code === 0) {
135
+ resolve(0)
136
+ } else {
137
+ reject(new Error(`${result.command} exited with code ${code}`))
138
+ }
139
+ })
140
+
141
+ child.on("error", error => {
142
+ reject(error)
143
+ })
144
+ })
145
+ } catch (error) {
146
+ process.stderr.write(`${error.message}\n`)
147
+ return 1
148
+ }
149
+ }
150
+
151
+ const isDirectExecution = (() => {
152
+ const [argvPath] = process.argv.slice(1, 2)
153
+
154
+ if (!argvPath) return false
155
+
156
+ try {
157
+ return realpathSync(argvPath) === realpathSync(__filename)
158
+ } catch (error) {
159
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
160
+ return false
161
+ }
162
+
163
+ throw error
164
+ }
165
+ })()
166
+
167
+ if (isDirectExecution) {
168
+ runCli()
169
+ .then(code => {
170
+ process.exit(code)
171
+ })
172
+ .catch(error => {
173
+ process.stderr.write(`${error.message}\n`)
174
+ process.exit(1)
175
+ })
176
+ }
@@ -0,0 +1,76 @@
1
+ import { mkdtemp, writeFile, mkdir } from "node:fs/promises"
2
+ import { tmpdir } from "node:os"
3
+ import { join } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { describe, expect, test } from "vitest"
6
+
7
+ import { auditPeerDependencies, formatAuditMessage } from "../scripts/peer-deps/audit.js"
8
+
9
+ const fixturePath = relative => fileURLToPath(new URL(`./fixtures/${relative}`, import.meta.url))
10
+
11
+ async function createPackage(root, name, version) {
12
+ const parts = name.split("/")
13
+ const packageDir = join(root, "node_modules", ...parts)
14
+ await mkdir(packageDir, { recursive: true })
15
+ await writeFile(join(packageDir, "package.json"), JSON.stringify({ name, version }), "utf8")
16
+ }
17
+
18
+ async function createProjectRoot() {
19
+ const root = await mkdtemp(join(tmpdir(), "signmax-check-peers-"))
20
+ await writeFile(join(root, "package.json"), JSON.stringify({ name: "example", version: "1.0.0" }), "utf8")
21
+ return root
22
+ }
23
+
24
+ describe("check-peers audit", () => {
25
+ test("returns empty when all peers match", async () => {
26
+ const root = await createProjectRoot()
27
+ await createPackage(root, "eslint", "9.39.1")
28
+ await createPackage(root, "prettier", "3.6.2")
29
+
30
+ const result = await auditPeerDependencies({
31
+ manifestPath: fixturePath("manifest-with-peers.json"),
32
+ consumerRoot: root,
33
+ })
34
+
35
+ expect(result.missing).toEqual([])
36
+ expect(result.mismatched).toEqual([])
37
+ expect(formatAuditMessage(result)).toBeNull()
38
+ })
39
+
40
+ test("detects missing peers", async () => {
41
+ const root = await createProjectRoot()
42
+ await createPackage(root, "eslint", "9.39.1")
43
+
44
+ const result = await auditPeerDependencies({
45
+ manifestPath: fixturePath("manifest-with-peers.json"),
46
+ consumerRoot: root,
47
+ })
48
+
49
+ expect(result.missing.map(item => item.name)).toEqual(["prettier"])
50
+ const message = formatAuditMessage(result)
51
+ expect(message).toContain("Missing peers")
52
+ expect(message).toContain("prettier@3.6.2")
53
+ })
54
+
55
+ test("detects version mismatches", async () => {
56
+ const root = await createProjectRoot()
57
+ await createPackage(root, "eslint", "9.0.0")
58
+ await createPackage(root, "prettier", "3.6.2")
59
+
60
+ const result = await auditPeerDependencies({
61
+ manifestPath: fixturePath("manifest-with-peers.json"),
62
+ consumerRoot: root,
63
+ })
64
+
65
+ expect(result.mismatched).toEqual([
66
+ {
67
+ name: "eslint",
68
+ expectedVersion: "9.39.1",
69
+ actualVersion: "9.0.0",
70
+ },
71
+ ])
72
+ const message = formatAuditMessage(result)
73
+ expect(message).toContain("Version mismatches")
74
+ expect(message).toContain("eslint@9.39.1 (found 9.0.0)")
75
+ })
76
+ })
@@ -0,0 +1,3 @@
1
+ {
2
+ "name": "example"
3
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "peerDependencies": {
3
+ "eslint": "9.39.1",
4
+ "prettier": "3.6.2"
5
+ }
6
+ }
@@ -0,0 +1,79 @@
1
+ import { mkdtemp, writeFile } from "node:fs/promises"
2
+ import { tmpdir } from "node:os"
3
+ import { join } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { describe, expect, test } from "vitest"
6
+
7
+ import {
8
+ buildInstallCommand,
9
+ detectManager,
10
+ formatCommand,
11
+ loadPeerDependencies,
12
+ prepareInstall,
13
+ } from "../scripts/peer-deps/install.js"
14
+
15
+ const fixturePath = relative => fileURLToPath(new URL(`./fixtures/${relative}`, import.meta.url))
16
+
17
+ describe("install-peers helper", () => {
18
+ test("loadPeerDependencies returns pinned peers", async () => {
19
+ const peers = await loadPeerDependencies(fixturePath("manifest-with-peers.json"))
20
+ expect(peers).toEqual(["eslint@9.39.1", "prettier@3.6.2"])
21
+ })
22
+
23
+ test("loadPeerDependencies handles missing peers", async () => {
24
+ const peers = await loadPeerDependencies(fixturePath("manifest-no-peers.json"))
25
+ expect(peers).toEqual([])
26
+ })
27
+
28
+ test("detectManager respects explicit flag", () => {
29
+ const manager = detectManager({ managerArg: "pnpm" })
30
+ expect(manager).toBe("pnpm")
31
+ })
32
+
33
+ test("detectManager infers from user agent", () => {
34
+ const manager = detectManager({ userAgent: "pnpm/9.0.0 npm/?" })
35
+ expect(manager).toBe("pnpm")
36
+ })
37
+
38
+ test("detectManager infers from lockfile", async () => {
39
+ const dir = await mkdtemp(join(tmpdir(), "signmax-config-"))
40
+ await writeFile(join(dir, "bun.lockb"), "")
41
+ const manager = detectManager({ cwd: dir })
42
+ expect(manager).toBe("bun")
43
+ })
44
+
45
+ test("buildInstallCommand supports npm", () => {
46
+ const result = buildInstallCommand("npm", ["eslint@9.39.1"])
47
+ expect(result.command).toBe("npm")
48
+ expect(result.args).toEqual(["install", "--save-dev", "--save-exact", "eslint@9.39.1"])
49
+ expect(formatCommand(result)).toBe("npm install --save-dev --save-exact eslint@9.39.1")
50
+ })
51
+
52
+ test("buildInstallCommand throws on unsupported manager", () => {
53
+ expect(() => buildInstallCommand("yarn", ["eslint@9.39.1"])).toThrow(/Unsupported package manager/)
54
+ })
55
+
56
+ test("prepareInstall returns printable command", async () => {
57
+ const result = await prepareInstall({
58
+ managerArg: "npm",
59
+ env: {},
60
+ cwd: process.cwd(),
61
+ manifestPath: fixturePath("manifest-with-peers.json"),
62
+ })
63
+
64
+ expect(result.manager).toBe("npm")
65
+ expect(result.packages).toEqual(["eslint@9.39.1", "prettier@3.6.2"])
66
+ expect(result.printable).toBe("npm install --save-dev --save-exact eslint@9.39.1 prettier@3.6.2")
67
+ })
68
+
69
+ test("prepareInstall returns empty when no peers", async () => {
70
+ const result = await prepareInstall({
71
+ managerArg: "npm",
72
+ env: {},
73
+ cwd: process.cwd(),
74
+ manifestPath: fixturePath("manifest-no-peers.json"),
75
+ })
76
+
77
+ expect(result.packages).toEqual([])
78
+ })
79
+ })
data/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "./typescript.json",
3
3
  "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
4
+ "exclude": ["fixture/**/*"],
4
5
  "compilerOptions": {
5
6
  "types": ["./reset.d.ts"]
6
7
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skyltmax_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Signmax AB
@@ -93,6 +93,7 @@ files:
93
93
  - ".devcontainer/devcontainer.json"
94
94
  - ".devcontainer/docker-compose.yml"
95
95
  - ".gitignore"
96
+ - ".prettierignore"
96
97
  - ".rubocop.yml"
97
98
  - AGENTS.md
98
99
  - CHANGELOG.md
@@ -103,6 +104,13 @@ files:
103
104
  - Rakefile
104
105
  - eslint.config.js
105
106
  - eslint.js
107
+ - fixture/.prettierignore
108
+ - fixture/eslint.config.js
109
+ - fixture/package.json
110
+ - fixture/pnpm-lock.yaml
111
+ - fixture/src/index.ts
112
+ - fixture/src/ui/button.tsx
113
+ - fixture/tsconfig.json
106
114
  - index.js
107
115
  - lib/skyltmax_config.rb
108
116
  - lib/skyltmax_config/version.rb
@@ -112,7 +120,13 @@ files:
112
120
  - reset.d.ts
113
121
  - rubocop.rails.yml
114
122
  - rubocop.yml
123
+ - scripts/peer-deps/audit.js
124
+ - scripts/peer-deps/install.js
115
125
  - skyltmax_config.gemspec
126
+ - tests/check-peers.test.js
127
+ - tests/fixtures/manifest-no-peers.json
128
+ - tests/fixtures/manifest-with-peers.json
129
+ - tests/install-peers.test.js
116
130
  - tsconfig.json
117
131
  - typescript.json
118
132
  homepage: https://github.com/skyltmax/config