preskok-ui 0.0.4 → 0.0.6

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/README.md CHANGED
@@ -3,12 +3,14 @@
3
3
  Thin CLI wrapper around shadcn with Preskok registry wiring.
4
4
 
5
5
  ```bash
6
+ npx preskok-ui@latest init
6
7
  npx preskok-ui@latest init button
7
8
  ```
8
9
 
9
- The CLI delegates to `shadcn@latest`. `init` installs the Preskok base and optional components in one pass, and `add` resolves bare component names through the Preskok registry.
10
+ The CLI delegates to `shadcn@latest`. `init` installs the Preskok base for new projects, only registers the Preskok namespace in projects that already have `components.json`, and can add optional components in one pass. `add` resolves bare component names through the Preskok registry.
10
11
 
11
12
  ```bash
12
13
  npx preskok-ui@latest registry
13
- npx preskok-ui@latest diff
14
+ npx preskok-ui@latest view button
15
+ npx preskok-ui@latest diff button
14
16
  ```
@@ -1,11 +1,44 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process"
3
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"
4
+ import path from "node:path"
3
5
 
4
- const REGISTRY = "@preskok=https://ui-three-mu.vercel.app/r/{name}.json"
5
- const DEFAULT_REGISTRY_ITEM = "https://ui-three-mu.vercel.app/r/default.json"
6
+ const REGISTRY_BASE_URL = (
7
+ process.env.PRESKOK_REGISTRY_URL ?? "https://ui-three-mu.vercel.app"
8
+ ).replace(/\/$/, "")
9
+ const REGISTRY = `@preskok=${REGISTRY_BASE_URL}/r/{name}.json`
10
+ const DEFAULT_REGISTRY_ITEM = `${REGISTRY_BASE_URL}/r/default.json`
11
+ const CWD_OPTION_VALUES = new Set(["-c", "--cwd"])
12
+ const ADD_OPTION_VALUES = new Set(["-c", "--cwd", "-p", "--path", "--diff"])
13
+ const DEPENDENCY_SECTIONS = [
14
+ "dependencies",
15
+ "devDependencies",
16
+ "optionalDependencies",
17
+ "peerDependencies",
18
+ ]
19
+ const INIT_OPTION_VALUES = new Set([
20
+ "-t",
21
+ "--template",
22
+ "-b",
23
+ "--base",
24
+ "-p",
25
+ "--preset",
26
+ "-c",
27
+ "--cwd",
28
+ "-n",
29
+ "--name",
30
+ ])
6
31
 
7
32
  const args = process.argv.slice(2)
8
33
  const [command, ...commandArgs] = args
34
+ const LOCKFILES = [
35
+ "package-lock.json",
36
+ "npm-shrinkwrap.json",
37
+ "pnpm-lock.yaml",
38
+ "yarn.lock",
39
+ "bun.lock",
40
+ "bun.lockb",
41
+ ]
9
42
 
10
43
  if (!command || command === "-h" || command === "--help") {
11
44
  printHelp()
@@ -13,20 +46,54 @@ if (!command || command === "-h" || command === "--help") {
13
46
  }
14
47
 
15
48
  if (command === "add") {
16
- const registryStatus = runShadcn(["registry", "add", REGISTRY])
49
+ const registryStatus = runShadcn([
50
+ "registry",
51
+ "add",
52
+ REGISTRY,
53
+ ...getCommandOptions(commandArgs, CWD_OPTION_VALUES),
54
+ ])
17
55
 
18
56
  if (registryStatus !== 0) {
19
57
  process.exit(registryStatus)
20
58
  }
21
59
 
22
60
  process.exit(
23
- runShadcn(["add", "--overwrite", "--yes", ...toPreskokItems(commandArgs)])
61
+ runPreskokAdd(toPreskokCommandItems(commandArgs, ADD_OPTION_VALUES))
24
62
  )
25
63
  }
26
64
 
27
65
  if (command === "init") {
66
+ const { initArgs, items } = splitInitArgs(commandArgs)
67
+ const cwd = getCwd(initArgs)
68
+
69
+ if (!existsSync(path.join(cwd, "components.json"))) {
70
+ const initStatus = runShadcn(["init", DEFAULT_REGISTRY_ITEM, ...initArgs])
71
+
72
+ if (initStatus !== 0) {
73
+ process.exit(initStatus)
74
+ }
75
+ }
76
+
77
+ const registryStatus = runShadcn([
78
+ "registry",
79
+ "add",
80
+ REGISTRY,
81
+ ...getCommandOptions(initArgs, CWD_OPTION_VALUES),
82
+ ])
83
+
84
+ if (registryStatus !== 0) {
85
+ process.exit(registryStatus)
86
+ }
87
+
88
+ if (items.length === 0) {
89
+ process.exit(0)
90
+ }
91
+
28
92
  process.exit(
29
- runShadcn(["init", DEFAULT_REGISTRY_ITEM, ...toPreskokInitItems(commandArgs)])
93
+ runPreskokAdd([
94
+ ...getCommandOptions(initArgs, CWD_OPTION_VALUES),
95
+ ...toPreskokItems(items),
96
+ ])
30
97
  )
31
98
  }
32
99
 
@@ -36,6 +103,25 @@ if (command === "registry") {
36
103
  process.exit(runShadcn(["registry", "add", REGISTRY, ...registryArgs]))
37
104
  }
38
105
 
106
+ if (command === "view") {
107
+ process.exit(
108
+ runShadcn([
109
+ "view",
110
+ ...toPreskokCommandItems(commandArgs, CWD_OPTION_VALUES),
111
+ ])
112
+ )
113
+ }
114
+
115
+ if (command === "diff") {
116
+ process.exit(
117
+ runShadcn([
118
+ "add",
119
+ ...toPreskokCommandItems(commandArgs, CWD_OPTION_VALUES),
120
+ "--diff",
121
+ ])
122
+ )
123
+ }
124
+
39
125
  process.exit(runShadcn(args))
40
126
 
41
127
  function runShadcn(shadcnArgs) {
@@ -59,58 +145,231 @@ function runShadcn(shadcnArgs) {
59
145
  return result.status ?? 1
60
146
  }
61
147
 
148
+ function runPreskokAdd(addArgs) {
149
+ const packageState = capturePackageState(addArgs)
150
+ const status = runShadcn(["add", "--yes", ...addArgs])
151
+ restorePackageState(packageState)
152
+
153
+ return status
154
+ }
155
+
62
156
  function toPreskokItems(values) {
157
+ return values.map(toPreskokItem)
158
+ }
159
+
160
+ function toPreskokItem(value) {
161
+ if (value.startsWith("-") || value.startsWith("@") || value.includes("/")) {
162
+ return value
163
+ }
164
+
165
+ return `@preskok/${value.replace(/\.(tsx|ts|jsx|js|json)$/, "")}`
166
+ }
167
+
168
+ function toPreskokCommandItems(values, optionsWithValues) {
169
+ let skipNext = false
170
+
63
171
  return values.map((value) => {
64
- if (value.startsWith("-") || value.startsWith("@") || value.includes("/")) {
172
+ if (skipNext) {
173
+ skipNext = false
65
174
  return value
66
175
  }
67
176
 
68
- return `@preskok/${value}`
177
+ if (optionsWithValues.has(value)) {
178
+ skipNext = true
179
+ return value
180
+ }
181
+
182
+ return toPreskokItem(value)
69
183
  })
70
184
  }
71
185
 
72
- function toPreskokInitItems(values) {
73
- const initOptionsWithValues = new Set([
74
- "-t",
75
- "--template",
76
- "-b",
77
- "--base",
78
- "-p",
79
- "--preset",
80
- "-c",
81
- "--cwd",
82
- "-n",
83
- "--name",
84
- ])
186
+ function getCommandOptions(values, optionsWithValues) {
187
+ let skipNext = false
188
+ const options = []
85
189
 
190
+ for (const value of values) {
191
+ if (skipNext) {
192
+ skipNext = false
193
+ options.push(value)
194
+ continue
195
+ }
196
+
197
+ if (optionsWithValues.has(value)) {
198
+ skipNext = true
199
+ options.push(value)
200
+ continue
201
+ }
202
+
203
+ if (hasInlineOptionValue(value, optionsWithValues)) {
204
+ options.push(value)
205
+ }
206
+ }
207
+
208
+ return options
209
+ }
210
+
211
+ function splitInitArgs(values) {
86
212
  let skipNext = false
213
+ const initArgs = []
214
+ const items = []
87
215
 
88
- return values.map((value) => {
216
+ for (const value of values) {
89
217
  if (skipNext) {
90
218
  skipNext = false
91
- return value
219
+ initArgs.push(value)
220
+ continue
92
221
  }
93
222
 
94
- if (initOptionsWithValues.has(value)) {
223
+ if (INIT_OPTION_VALUES.has(value)) {
95
224
  skipNext = true
96
- return value
225
+ initArgs.push(value)
226
+ continue
227
+ }
228
+
229
+ if (
230
+ hasInlineOptionValue(value, INIT_OPTION_VALUES) ||
231
+ value.startsWith("-")
232
+ ) {
233
+ initArgs.push(value)
234
+ continue
235
+ }
236
+
237
+ items.push(value)
238
+ }
239
+
240
+ return { initArgs, items }
241
+ }
242
+
243
+ function getCwd(values) {
244
+ for (let index = 0; index < values.length; index++) {
245
+ const value = values[index]
246
+
247
+ if (value === "-c" || value === "--cwd") {
248
+ return path.resolve(values[index + 1] ?? process.cwd())
97
249
  }
98
250
 
99
- return toPreskokItems([value])[0]
251
+ if (value.startsWith("--cwd=")) {
252
+ return path.resolve(value.slice("--cwd=".length))
253
+ }
254
+ }
255
+
256
+ return process.cwd()
257
+ }
258
+
259
+ function hasInlineOptionValue(value, options) {
260
+ return [...options].some((option) => value.startsWith(`${option}=`))
261
+ }
262
+
263
+ function capturePackageState(values) {
264
+ const cwd = getCwd(values)
265
+ const packageJsonPath = path.join(cwd, "package.json")
266
+
267
+ if (!existsSync(packageJsonPath)) {
268
+ return null
269
+ }
270
+
271
+ const packageJsonContent = readFileSync(packageJsonPath, "utf8")
272
+ const packageJson = JSON.parse(packageJsonContent)
273
+ const lockfiles = new Map()
274
+
275
+ for (const lockfile of LOCKFILES) {
276
+ const lockfilePath = path.join(cwd, lockfile)
277
+
278
+ if (existsSync(lockfilePath)) {
279
+ lockfiles.set(lockfile, readFileSync(lockfilePath))
280
+ }
281
+ }
282
+
283
+ return {
284
+ cwd,
285
+ lockfiles,
286
+ packageJson,
287
+ packageJsonContent,
288
+ packageJsonPath,
289
+ }
290
+ }
291
+
292
+ function restorePackageState(packageState) {
293
+ if (!packageState || !existsSync(packageState.packageJsonPath)) {
294
+ return
295
+ }
296
+
297
+ const packageJson = JSON.parse(
298
+ readFileSync(packageState.packageJsonPath, "utf8")
299
+ )
300
+ const beforeDependencyNames = getDependencyNames(packageState.packageJson)
301
+ const afterDependencyNames = getDependencyNames(packageJson)
302
+ const hasNewDependencies = [...afterDependencyNames].some((dependency) => {
303
+ return !beforeDependencyNames.has(dependency)
100
304
  })
305
+
306
+ if (!hasNewDependencies) {
307
+ writeFileSync(packageState.packageJsonPath, packageState.packageJsonContent)
308
+ restoreLockfiles(packageState)
309
+ return
310
+ }
311
+
312
+ restoreExistingDependencySpecs(packageState.packageJson, packageJson)
313
+ writeFileSync(
314
+ packageState.packageJsonPath,
315
+ `${JSON.stringify(packageJson, null, 2)}\n`
316
+ )
317
+ }
318
+
319
+ function getDependencyNames(packageJson) {
320
+ const names = new Set()
321
+
322
+ for (const section of DEPENDENCY_SECTIONS) {
323
+ for (const dependency of Object.keys(packageJson[section] ?? {})) {
324
+ names.add(dependency)
325
+ }
326
+ }
327
+
328
+ return names
329
+ }
330
+
331
+ function restoreExistingDependencySpecs(sourcePackageJson, targetPackageJson) {
332
+ for (const section of DEPENDENCY_SECTIONS) {
333
+ const sourceDependencies = sourcePackageJson[section] ?? {}
334
+ const targetDependencies = targetPackageJson[section] ?? {}
335
+
336
+ for (const [dependency, version] of Object.entries(sourceDependencies)) {
337
+ if (targetDependencies[dependency] !== undefined) {
338
+ targetDependencies[dependency] = version
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ function restoreLockfiles(packageState) {
345
+ for (const lockfile of LOCKFILES) {
346
+ const lockfilePath = path.join(packageState.cwd, lockfile)
347
+ const content = packageState.lockfiles.get(lockfile)
348
+
349
+ if (content) {
350
+ writeFileSync(lockfilePath, content)
351
+ continue
352
+ }
353
+
354
+ if (existsSync(lockfilePath)) {
355
+ unlinkSync(lockfilePath)
356
+ }
357
+ }
101
358
  }
102
359
 
103
360
  function printHelp() {
104
361
  console.log(`Usage: preskok-ui <command> [options]
105
362
 
106
363
  Commands:
107
- init [items...] run shadcn init with the Preskok base and optional items
364
+ init [items...] run shadcn init, register Preskok, and optionally add items
108
365
  add <items...> register Preskok and add items by name
109
366
  registry register the @preskok namespace in components.json
110
367
 
111
368
  Examples:
369
+ npx preskok-ui@latest init
112
370
  npx preskok-ui@latest init button
113
371
  npx preskok-ui@latest add button
114
- npx preskok-ui@latest diff
372
+ npx preskok-ui@latest view button
373
+ npx preskok-ui@latest diff button
115
374
  npx preskok-ui@latest registry`)
116
375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preskok-ui",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {