@webjsdev/ui 0.3.14 → 0.3.16

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.
@@ -82,6 +82,43 @@ export const registryItemCommonSchema = z.object({
82
82
  categories: z.array(z.string()).optional(),
83
83
  });
84
84
 
85
+ /** A rule's severity. Absent from `lint.rules` means `off`. */
86
+ export const lintSeveritySchema = z.enum(['off', 'warn', 'error']);
87
+
88
+ /**
89
+ * One rule's configuration: a bare severity, or an object adding `allow`
90
+ * (category names such as `layout` / `spacing`, or class-group names such
91
+ * as `rounded`). Kept `.strict()` so a typo is a config error, not a silent
92
+ * no-op.
93
+ */
94
+ export const lintRuleSchema = z.union([
95
+ lintSeveritySchema,
96
+ z
97
+ .object({
98
+ severity: lintSeveritySchema.default('warn'),
99
+ allow: z.array(z.string()).optional(),
100
+ })
101
+ .strict(),
102
+ ]);
103
+
104
+ /**
105
+ * The opt-in `lint` block of `components.json`, read by `webjsui lint`. No
106
+ * block means every rule is off and the command reports nothing.
107
+ */
108
+ export const lintConfigSchema = z
109
+ .object({
110
+ ignore: z.array(z.string()).default([]),
111
+ rules: z
112
+ .object({
113
+ 'no-raw-colors': lintRuleSchema.optional(),
114
+ 'no-arbitrary-values': lintRuleSchema.optional(),
115
+ 'no-restyle': lintRuleSchema.optional(),
116
+ })
117
+ .strict()
118
+ .default({}),
119
+ })
120
+ .strict();
121
+
85
122
  export const rawConfigSchema = z
86
123
  .object({
87
124
  $schema: z.string().optional(),
@@ -94,6 +131,7 @@ export const rawConfigSchema = z
94
131
  prefix: z.string().default('').optional(),
95
132
  }),
96
133
  iconLibrary: z.string().optional().default('lucide'),
134
+ lint: lintConfigSchema.optional(),
97
135
  aliases: z.object({
98
136
  components: z.string().default('components'),
99
137
  utils: z.string().default('lib/utils'),
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Package-manager detection for `webjs ui add` (#1494).
3
+ *
4
+ * KEEP IN SYNC with `packages/cli/lib/package-manager.js`. The two published
5
+ * packages carry the same small module rather than one importing the other,
6
+ * so a `@webjsdev/cli` release can never fail at import time against an older
7
+ * `@webjsdev/ui` that lacks the export. `packages/cli/test/package-manager.test.mjs`
8
+ * asserts both copies agree on every fixture.
9
+ *
10
+ * Detection order (with `prefer: 'lockfile'`, the default):
11
+ * 1. A lockfile in `cwd` or any ancestor. The walk stops at the first
12
+ * workspace root (a package.json declaring `workspaces`, or a
13
+ * `pnpm-workspace.yaml`) or at the filesystem root, so an app nested in a
14
+ * workspace finds the root's lockfile. Within one directory the order is
15
+ * pnpm, yarn, bun (`bun.lock`, the text lockfile Bun writes since 1.2,
16
+ * and the older binary `bun.lockb`), then npm.
17
+ * 2. `npm_config_user_agent`, which npm, pnpm, yarn and bun set when they
18
+ * run a script or a `dlx` / `bunx` / `npx` binary.
19
+ * 3. `npm`.
20
+ * With `prefer: 'agent'` steps 1 and 2 swap, which suits `webjs create`: the
21
+ * tool that invoked it is the strongest signal for a directory that has no
22
+ * lockfile yet.
23
+ */
24
+ import { existsSync, readFileSync } from 'node:fs';
25
+ import { dirname, join, resolve } from 'node:path';
26
+
27
+ /** @typedef {'npm'|'pnpm'|'yarn'|'bun'} PackageManager */
28
+
29
+ /** Lockfile name to manager, in per-directory precedence order. */
30
+ const LOCKFILES = /** @type {const} */ ([
31
+ ['pnpm-lock.yaml', 'pnpm'],
32
+ ['yarn.lock', 'yarn'],
33
+ ['bun.lock', 'bun'],
34
+ ['bun.lockb', 'bun'],
35
+ ['package-lock.json', 'npm'],
36
+ ['npm-shrinkwrap.json', 'npm'],
37
+ ]);
38
+
39
+ /**
40
+ * Whether `dir` is a workspace root, where the lockfile walk stops.
41
+ * @param {string} dir
42
+ * @returns {boolean}
43
+ */
44
+ function isWorkspaceRoot(dir) {
45
+ if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return true;
46
+ const pkgPath = join(dir, 'package.json');
47
+ if (!existsSync(pkgPath)) return false;
48
+ try {
49
+ return Boolean(JSON.parse(readFileSync(pkgPath, 'utf8')).workspaces);
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Walk up from `cwd` looking for a lockfile.
57
+ * @param {string} cwd
58
+ * @returns {PackageManager | null}
59
+ */
60
+ export function managerFromLockfile(cwd) {
61
+ let dir = resolve(cwd);
62
+ for (;;) {
63
+ for (const [file, manager] of LOCKFILES) {
64
+ if (existsSync(join(dir, file))) return manager;
65
+ }
66
+ if (isWorkspaceRoot(dir)) return null;
67
+ const parent = dirname(dir);
68
+ if (parent === dir) return null;
69
+ dir = parent;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Read the manager from an `npm_config_user_agent` value such as
75
+ * `bun/1.3.14 npm/? node/v24.0.0 linux x64`.
76
+ * @param {string | undefined} ua
77
+ * @returns {PackageManager | null}
78
+ */
79
+ export function managerFromUserAgent(ua) {
80
+ const name = String(ua || '').split('/')[0];
81
+ return name === 'pnpm' || name === 'yarn' || name === 'bun' || name === 'npm' ? name : null;
82
+ }
83
+
84
+ /**
85
+ * @param {{ cwd?: string | null, env?: Record<string, string | undefined>, prefer?: 'lockfile' | 'agent' }} [opts]
86
+ * @returns {PackageManager}
87
+ */
88
+ export function detectPackageManager({ cwd = null, env = process.env, prefer = 'lockfile' } = {}) {
89
+ const fromLock = () => (cwd ? managerFromLockfile(cwd) : null);
90
+ const fromAgent = () => managerFromUserAgent(env.npm_config_user_agent);
91
+ return (prefer === 'agent' ? fromAgent() ?? fromLock() : fromLock() ?? fromAgent()) ?? 'npm';
92
+ }