@react-native-reusables/cli 0.6.2 → 0.7.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.
@@ -0,0 +1,29 @@
1
+ import { Options } from "@effect/cli"
2
+ import { Context, Option } from "effect"
3
+
4
+ type StylingLibrary = "nativewind" | "uniwind"
5
+
6
+ class CliOptions extends Context.Tag("CommandOptions")<
7
+ CliOptions,
8
+ Readonly<{
9
+ cwd: string
10
+ yes: boolean
11
+ stylingLibrary: StylingLibrary | undefined
12
+ }>
13
+ >() { }
14
+
15
+ const cwd = Options.directory("cwd", { exists: "yes" }).pipe(Options.withDefault("."), Options.withAlias("c"))
16
+ const yes = Options.boolean("yes", { aliases: ["y"] })
17
+ const summary = Options.boolean("summary").pipe(Options.withAlias("s"))
18
+ const overwrite = Options.boolean("overwrite", { aliases: ["o"] })
19
+ const all = Options.boolean("all", { aliases: ["a"] })
20
+ const path = Options.text("path").pipe(Options.withDefault(""), Options.withAlias("p"))
21
+ const stylingLibrary = Options.choice("styling-library", ["nativewind", "uniwind"] as const).pipe(
22
+ Options.optional,
23
+ Options.map(Option.getOrUndefined),
24
+ Options.withDescription("Override the detected styling library for this command"),
25
+ )
26
+ const template = Options.text("template").pipe(Options.withAlias("t"), Options.withDefault(""))
27
+
28
+ export { CliOptions, cwd, summary, yes, overwrite, all, path, stylingLibrary, template }
29
+ export type { StylingLibrary }
@@ -0,0 +1,369 @@
1
+ interface FileCheck {
2
+ name: string
3
+ fileNames: Array<string>
4
+ docs: string
5
+ includes: Array<{
6
+ content: Array<string>
7
+ message: string
8
+ docs: string
9
+ }>
10
+ stylingLibraries: Array<"nativewind" | "uniwind">
11
+ }
12
+
13
+ type CustomFileCheck = Omit<FileCheck, "fileNames"> & { defaultFileNames?: ReadonlyArray<string> }
14
+
15
+ interface FileWithContent extends FileCheck {
16
+ content: string
17
+ }
18
+
19
+ interface MissingInclude {
20
+ fileName: string
21
+ content: ReadonlyArray<string>
22
+ message: string
23
+ docs: string
24
+ }
25
+
26
+ const CORE_DEPENDENCIES = [
27
+ "expo",
28
+ "react-native-reanimated",
29
+ "react-native-safe-area-context",
30
+ "tailwindcss-animate",
31
+ "class-variance-authority",
32
+ "clsx",
33
+ "tailwind-merge"
34
+ ]
35
+
36
+ const DEPENDENCIES = {
37
+ nativewind: [...CORE_DEPENDENCIES, "nativewind"],
38
+ uniwind: [...CORE_DEPENDENCIES, "uniwind"]
39
+ }
40
+
41
+ const DEV_DEPENDENCIES = ["tailwindcss@^3.4.14"]
42
+
43
+ const FILE_CHECKS: Array<FileCheck> = [
44
+ {
45
+ name: "Babel Config",
46
+ fileNames: ["babel.config.js", "babel.config.ts"],
47
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#3-add-the-babel-preset",
48
+ includes: [
49
+ {
50
+ content: ["nativewind/babel", "jsxImportSource"],
51
+ message: "jsxImportSource or nativewind/babel is missing",
52
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#3-add-the-babel-preset"
53
+ }
54
+ ],
55
+ stylingLibraries: ["nativewind"] as const
56
+ },
57
+ {
58
+ name: "Metro Config",
59
+ fileNames: ["metro.config.js", "metro.config.ts"],
60
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#4-create-or-modify-your-metroconfigjs",
61
+ includes: [
62
+ {
63
+ content: ["withNativeWind("],
64
+ message: "The withNativeWind function is missing",
65
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#4-create-or-modify-your-metroconfigjs"
66
+ },
67
+ {
68
+ content: ["inlineRem", "16"],
69
+ message: "The 'inlineRem: 16' is missing",
70
+ docs: "https://reactnativereusables.com/docs/installation/manual#update-the-default-inlined-rem-value"
71
+ }
72
+ ],
73
+ stylingLibraries: ["nativewind"] as const
74
+ },
75
+ {
76
+ name: "Metro Config",
77
+ fileNames: ["metro.config.js", "metro.config.ts"],
78
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#4-create-or-modify-your-metroconfigjs",
79
+ includes: [
80
+ {
81
+ content: ["withUniwindConfig("],
82
+ message: "The withUniwindConfig function is missing",
83
+ docs: "https://docs.uniwind.dev/api/metro-config#metro-config-js"
84
+ }
85
+ ],
86
+ stylingLibraries: ["uniwind"] as const
87
+ },
88
+ {
89
+ name: "Root Layout",
90
+ fileNames: ["app/_layout.tsx", "src/app/_layout.tsx"],
91
+ docs: "https://reactnativereusables.com/docs/installation/manual#add-the-portal-host-to-your-root-layout", //
92
+ includes: [
93
+ {
94
+ content: [".css"],
95
+ message: "The css file import is missing",
96
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#5-import-your-css-file"
97
+ },
98
+ {
99
+ content: ["<PortalHost"],
100
+ message: "The PortalHost component is missing",
101
+ docs: "https://reactnativereusables.com/docs/installation/manual#add-the-portal-host-to-your-root-layout"
102
+ }
103
+ ],
104
+ stylingLibraries: ["nativewind", "uniwind"] as const
105
+ }
106
+ ]
107
+
108
+ const DEPRECATED_FROM_LIB: Array<Omit<FileCheck, "docs" | "stylingLibraries">> = [
109
+ {
110
+ name: "Icons",
111
+ fileNames: ["icons/iconWithClassName.ts"],
112
+ includes: [
113
+ {
114
+ content: ["iconWithClassName"],
115
+ message: "lib/icons and its contents are deprecated. Use the new icon wrapper from components/ui/icon.",
116
+ docs: "https://reactnativereusables.com/docs/changelog#august-2025-deprecated"
117
+ }
118
+ ]
119
+ },
120
+ {
121
+ name: "Constants",
122
+ fileNames: ["constants.ts"],
123
+ includes: [
124
+ {
125
+ content: ["NAV_THEME"],
126
+ message: "Usage of lib/constants for NAV_THEME is deprecated. Use lib/theme instead.",
127
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ name: "useColorScheme",
133
+ fileNames: ["useColorScheme.tsx"],
134
+ includes: [
135
+ {
136
+ content: ["useColorScheme"],
137
+ message: "lib/useColorScheme is deprecated. Use Nativewind's color scheme hook instead.",
138
+ docs: "https://www.nativewind.dev/docs/api/use-color-scheme"
139
+ }
140
+ ]
141
+ }
142
+ ]
143
+
144
+ const DEPRECATED_FROM_UI: Array<Omit<FileCheck, "docs" | "stylingLibraries">> = [
145
+ {
146
+ name: "Typography",
147
+ fileNames: ["typography.tsx"],
148
+ includes: [
149
+ {
150
+ content: [
151
+ "function H1({",
152
+ "function H2({",
153
+ "function H3({",
154
+ "function H4({",
155
+ "function P({",
156
+ "function BlockQuote({",
157
+ "function Code({",
158
+ "function Lead({",
159
+ "function Large({",
160
+ "function Small({",
161
+ "function Muted({"
162
+ ],
163
+ message:
164
+ "Typography is deprecated. Instead, use the Text component with its variant prop (e.g. <Text variant='h1'>Title</Text>)",
165
+ docs: "https://reactnativereusables.com/docs/components/text#typography"
166
+ }
167
+ ]
168
+ }
169
+ ]
170
+
171
+ // Excludes foreground colors since it is formatted differently in all 3 styling files (tailwind config, global.css, theme.ts)
172
+ const CSS_VARIABLE_NAMES = [
173
+ "background",
174
+ "foreground",
175
+ "card",
176
+ "popover",
177
+ "primary",
178
+ "secondary",
179
+ "muted",
180
+ "accent",
181
+ "destructive",
182
+ "border",
183
+ "input",
184
+ "ring",
185
+ "radius"
186
+ ]
187
+
188
+ const CUSTOM_FILE_CHECKS: Record<string, CustomFileCheck> = {
189
+ tailwindConfig: {
190
+ name: "Tailwind Config",
191
+ defaultFileNames: ["tailwind.config.js", "tailwind.config.ts"],
192
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles",
193
+ includes: [
194
+ {
195
+ content: ["nativewind/preset"],
196
+ message: "The nativewind preset is missing",
197
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#2-setup-tailwind-css"
198
+ },
199
+ {
200
+ content: CSS_VARIABLE_NAMES,
201
+ message: "At least one of the color css variables is missing",
202
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
203
+ }
204
+ ],
205
+ stylingLibraries: ["nativewind"] as const
206
+ },
207
+ theme: {
208
+ name: "Theme",
209
+ defaultFileNames: ["lib/theme.ts"],
210
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles",
211
+ includes: [
212
+ {
213
+ content: CSS_VARIABLE_NAMES,
214
+ message: "At least one of the color variables is missing",
215
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
216
+ },
217
+ {
218
+ content: ["NAV_THEME"],
219
+ message: "The NAV_THEME is missing",
220
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
221
+ }
222
+ ],
223
+ stylingLibraries: ["nativewind", "uniwind"] as const
224
+ },
225
+ nativewindEnv: {
226
+ name: "Nativewind Env",
227
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#7-typescript-setup-optional",
228
+ includes: [
229
+ {
230
+ content: ["nativewind/types"],
231
+ message: "The nativewind types are missing",
232
+ docs: "https://www.nativewind.dev/docs/getting-started/installation#7-typescript-setup-optional"
233
+ }
234
+ ],
235
+ stylingLibraries: ["nativewind"] as const
236
+ },
237
+ uniwindTypes: {
238
+ name: "Uniwind Types",
239
+ defaultFileNames: ["uniwind-types.d.ts"],
240
+ docs: "https://docs.uniwind.dev/api/metro-config#dtsfile",
241
+ includes: [
242
+ {
243
+ content: ["uniwind/types"],
244
+ message: "The uniwind types are missing",
245
+ docs: "https://docs.uniwind.dev/api/metro-config#dtsfile"
246
+ }
247
+ ],
248
+ stylingLibraries: ["uniwind"] as const
249
+ },
250
+ utils: {
251
+ name: "Utils",
252
+ defaultFileNames: ["lib/utils.ts"],
253
+ docs: "https://reactnativereusables.com/docs/installation/manual#add-a-cn-helper",
254
+ includes: [
255
+ {
256
+ content: ["function cn("],
257
+ message: "The cn function is missing",
258
+ docs: "https://reactnativereusables.com/docs/installation/manual#add-a-cn-helper"
259
+ }
260
+ ],
261
+ stylingLibraries: ["nativewind", "uniwind"] as const
262
+ },
263
+ css: {
264
+ name: "CSS",
265
+ defaultFileNames: ["globals.css", "src/global.css"],
266
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles",
267
+ includes: [
268
+ {
269
+ content: ["@tailwind base", "@tailwind components", "@tailwind utilities"],
270
+ message: "The tailwind layer directives are missing",
271
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
272
+ },
273
+ {
274
+ content: CSS_VARIABLE_NAMES,
275
+ message: "At least one of the color css variables is missing",
276
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
277
+ }
278
+ ],
279
+ stylingLibraries: ["nativewind"] as const
280
+ },
281
+ uniwindCss: {
282
+ name: "CSS",
283
+ defaultFileNames: ["globals.css", "src/global.css"],
284
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles",
285
+ includes: [
286
+ {
287
+ content: ["tailwindcss", "uniwind"],
288
+ message: "The tailwind layer directives are missing",
289
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
290
+ },
291
+ {
292
+ content: CSS_VARIABLE_NAMES,
293
+ message: "At least one of the color css variables is missing",
294
+ docs: "https://reactnativereusables.com/docs/installation/manual#configure-your-styles"
295
+ }
296
+ ],
297
+ stylingLibraries: ["uniwind"] as const
298
+ }
299
+ }
300
+
301
+ const NATIVEWIND_ENV_FILE = "nativewind-env.d.ts"
302
+ const UNIWIND_TYPES_FILE = "uniwind-types.d.ts"
303
+
304
+ const COMPONENTS = [
305
+ "accordion",
306
+ "alert-dialog",
307
+ "alert",
308
+ "aspect-ratio",
309
+ "avatar",
310
+ "badge",
311
+ "button",
312
+ "card",
313
+ "checkbox",
314
+ "collapsible",
315
+ "context-menu",
316
+ "dialog",
317
+ "dropdown-menu",
318
+ "hover-card",
319
+ "input",
320
+ "label",
321
+ "menubar",
322
+ "popover",
323
+ "progress",
324
+ "radio-group",
325
+ "select",
326
+ "separator",
327
+ "skeleton",
328
+ "switch",
329
+ "tabs",
330
+ "text",
331
+ "textarea",
332
+ "toggle-group",
333
+ "toggle",
334
+ "tooltip"
335
+ ]
336
+
337
+ const TEMPLATES = [
338
+ {
339
+ name: "Minimal (Nativewind)",
340
+ url: "https://github.com/founded-labs/react-native-reusables-templates.git",
341
+ subPath: "minimal"
342
+ },
343
+ {
344
+ name: "Minimal (Uniwind)",
345
+ url: "https://github.com/founded-labs/react-native-reusables-templates.git",
346
+ subPath: "minimal-uniwind"
347
+ },
348
+ {
349
+ name: "Clerk auth (Nativewind)",
350
+ url: "https://github.com/founded-labs/react-native-reusables-templates.git",
351
+ subPath: "clerk-auth"
352
+ }
353
+ ]
354
+
355
+ const PROJECT_MANIFEST = {
356
+ dependencies: DEPENDENCIES,
357
+ devDependencies: DEV_DEPENDENCIES,
358
+ fileChecks: FILE_CHECKS,
359
+ deprecatedFromLib: DEPRECATED_FROM_LIB,
360
+ deprecatedFromUi: DEPRECATED_FROM_UI,
361
+ customFileChecks: CUSTOM_FILE_CHECKS,
362
+ nativewindEnvFile: NATIVEWIND_ENV_FILE,
363
+ uniwindTypesFile: UNIWIND_TYPES_FILE,
364
+ components: COMPONENTS,
365
+ templates: TEMPLATES
366
+ }
367
+
368
+ export { PROJECT_MANIFEST }
369
+ export type { FileCheck, CustomFileCheck, FileWithContent, MissingInclude }
@@ -0,0 +1,127 @@
1
+ import { CliOptions, type StylingLibrary } from "@cli/contexts/cli-options.js"
2
+ import { PROJECT_MANIFEST } from "@cli/project-manifest.js"
3
+ import { Doctor } from "@cli/services/commands/doctor.js"
4
+ import { runCommand } from "@cli/utils/run-command.js"
5
+ import { Prompt } from "@effect/cli"
6
+ import { Effect, Layer } from "effect"
7
+ import { PackageManager } from "../package-manager.js"
8
+ import { ProjectConfig } from "../project-config.js"
9
+
10
+ type AddOptions = {
11
+ cwd: string
12
+ args: { components: Array<string> }
13
+ yes: boolean
14
+ overwrite: boolean
15
+ all: boolean
16
+ path: string
17
+ stylingLibrary: StylingLibrary | undefined
18
+ }
19
+
20
+ class Add extends Effect.Service<Add>()("Add", {
21
+ dependencies: [PackageManager.Default],
22
+ effect: Effect.gen(function* () {
23
+ const doctor = yield* Doctor
24
+ const projectConfig = yield* ProjectConfig
25
+ const packageManager = yield* PackageManager
26
+
27
+ return {
28
+ run: (options: AddOptions) =>
29
+ Effect.gen(function* () {
30
+ yield* Effect.logDebug(`Add options: ${JSON.stringify(options, null, 2)}`)
31
+
32
+ yield* projectConfig.getComponentJson() // ensure components.json config is valid and prompt if not
33
+
34
+ const components = options.all ? PROJECT_MANIFEST.components : (options.args?.components ?? [])
35
+
36
+ if (components.length === 0) {
37
+ const selectedComponents = yield* Prompt.multiSelect({
38
+ message: "Select components to add",
39
+ choices: PROJECT_MANIFEST.components.map((component) => ({
40
+ title: component,
41
+ value: component
42
+ }))
43
+ })
44
+ for (const component of selectedComponents) {
45
+ components.push(component)
46
+ }
47
+ }
48
+
49
+ if (components.length === 0) {
50
+ yield* Effect.fail(new Error("No components selected."))
51
+ }
52
+
53
+ yield* Effect.logDebug(`Selected components: ${components.join(", ")}`)
54
+
55
+ const stylingLibrary = yield* projectConfig.getStylingLibrary()
56
+
57
+ const registry = stylingLibrary === "uniwind" ? "uniwind" : "nativewind"
58
+
59
+ const baseUrl =
60
+ process.env.INTERNAL_ENV === "development"
61
+ ? `http://localhost:3000/local/r/${registry}`
62
+ : `https://reactnativereusables.com/r/${registry}`
63
+
64
+ const componentUrls = components.map((component) => {
65
+ const lowerCaseComponent = component.toLocaleLowerCase()
66
+ return lowerCaseComponent.startsWith("http") ? lowerCaseComponent : `${baseUrl}/${lowerCaseComponent}.json`
67
+ })
68
+
69
+ const shadcnOptions = toShadcnOptions(options)
70
+
71
+ const binaryRunner = yield* packageManager.getBinaryRunner(options.cwd)
72
+
73
+ const commandArgs = [
74
+ ...binaryRunner.slice(1),
75
+ "shadcn@latest",
76
+ "add",
77
+ ...shadcnOptions,
78
+ ...componentUrls
79
+ ].filter((option) => option !== undefined)
80
+
81
+ yield* Effect.logDebug(`Running command: ${binaryRunner[0]} ${commandArgs.join(" ")}`)
82
+
83
+ yield* runCommand(binaryRunner[0], commandArgs, {
84
+ cwd: options.cwd,
85
+ stdio: "inherit"
86
+ })
87
+
88
+ yield* doctor.run({ ...options, summary: true })
89
+ })
90
+ }
91
+ })
92
+ }) {}
93
+
94
+ function make(options: AddOptions) {
95
+ const optionsLayer = Layer.succeed(CliOptions, { ...options, yes: true }) // For the project config
96
+ return Effect.gen(function* () {
97
+ const add = yield* Add
98
+
99
+ return yield* add.run(options)
100
+ }).pipe(
101
+ Effect.provide(Add.Default),
102
+ Effect.provide(Doctor.Default),
103
+ Effect.provide(ProjectConfig.Default),
104
+ Effect.provide(optionsLayer)
105
+ )
106
+ }
107
+
108
+ export { make }
109
+
110
+ function toShadcnOptions(options: AddOptions) {
111
+ const shadcnOptions = []
112
+
113
+ if (options.overwrite) {
114
+ shadcnOptions.push("--overwrite")
115
+ }
116
+
117
+ if (options.yes) {
118
+ shadcnOptions.push("--yes")
119
+ }
120
+
121
+ if (options.path) {
122
+ shadcnOptions.push("--path")
123
+ shadcnOptions.push(options.path)
124
+ }
125
+
126
+ return shadcnOptions
127
+ }