@react-native-reusables/cli 0.6.3 → 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.
- package/.changeset/config.json +16 -0
- package/.github/actions/setup/action.yml +21 -0
- package/.github/workflows/check.yml +54 -0
- package/.github/workflows/release.yml +0 -0
- package/.github/workflows/snapshot.yml +24 -0
- package/.prettierrc +8 -0
- package/.vscode/extensions.json +6 -0
- package/.vscode/settings.json +46 -0
- package/LICENSE +1 -1
- package/README.md +97 -0
- package/eslint.config.mjs +118 -0
- package/package.json +64 -8
- package/patches/@changesets__get-github-info@0.6.0.patch +48 -0
- package/scripts/copy-package-json.ts +32 -0
- package/src/bin.ts +18 -0
- package/src/cli.ts +66 -0
- package/src/contexts/cli-options.ts +29 -0
- package/src/project-manifest.ts +369 -0
- package/src/services/commands/add.ts +127 -0
- package/src/services/commands/doctor.ts +287 -0
- package/src/services/commands/init.ts +94 -0
- package/src/services/git.ts +39 -0
- package/src/services/package-manager.ts +48 -0
- package/src/services/project-config.ts +295 -0
- package/src/services/required-files-checker.ts +375 -0
- package/src/services/spinner.ts +15 -0
- package/src/services/template.ts +222 -0
- package/src/utils/retry-with.ts +9 -0
- package/src/utils/run-command.ts +10 -0
- package/test/Dummy.test.ts +7 -0
- package/tsconfig.base.json +53 -0
- package/tsconfig.json +14 -0
- package/tsconfig.scripts.json +17 -0
- package/tsconfig.src.json +10 -0
- package/tsconfig.test.json +10 -0
- package/tsup.config.ts +9 -0
- package/vitest.config.ts +12 -0
- package/bin.cjs +0 -59056
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { runCommand } from "@cli/utils/run-command.js"
|
|
2
|
+
import { Prompt } from "@effect/cli"
|
|
3
|
+
import { FileSystem, Path } from "@effect/platform"
|
|
4
|
+
import { Effect } from "effect"
|
|
5
|
+
import { Spinner } from "@cli/services/spinner.js"
|
|
6
|
+
import logSymbols from "log-symbols"
|
|
7
|
+
|
|
8
|
+
class Template extends Effect.Service<Template>()("src/services/template", {
|
|
9
|
+
dependencies: [Spinner.Default],
|
|
10
|
+
effect: Effect.gen(function* () {
|
|
11
|
+
const fs = yield* FileSystem.FileSystem
|
|
12
|
+
const path = yield* Path.Path
|
|
13
|
+
const spinner = yield* Spinner
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
clone: ({ cwd, name, repo }: { cwd: string; name: string; repo: { subPath?: string; url: string } }) =>
|
|
17
|
+
Effect.acquireUseRelease(
|
|
18
|
+
fs.makeTempDirectory(),
|
|
19
|
+
(tempDirPath) =>
|
|
20
|
+
Effect.gen(function* () {
|
|
21
|
+
yield* Effect.logDebug(`Template.clone args: ${JSON.stringify({ cwd, name, repo }, null, 2)}`)
|
|
22
|
+
|
|
23
|
+
const newRepoPath = path.join(cwd, name)
|
|
24
|
+
|
|
25
|
+
const newRepoPathExists = yield* fs.exists(newRepoPath)
|
|
26
|
+
|
|
27
|
+
yield* Effect.logDebug(`Does ${newRepoPath} exist? ${newRepoPathExists ? "yes" : "no"}`)
|
|
28
|
+
|
|
29
|
+
if (newRepoPathExists) {
|
|
30
|
+
yield* Effect.logWarning(`${logSymbols.warning} A project already exists in this directory.`)
|
|
31
|
+
const choice = yield* Prompt.select({
|
|
32
|
+
message: "How would you like to proceed?",
|
|
33
|
+
choices: [
|
|
34
|
+
{ title: "Cancel and exit", value: "cancel" },
|
|
35
|
+
{ title: "Overwrite the existing project", value: "overwrite" }
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (choice === "cancel") {
|
|
40
|
+
yield* Effect.logDebug(`User chose to cancel`)
|
|
41
|
+
return yield* Effect.succeed(true)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const confirmOverwrite = yield* Prompt.confirm({
|
|
45
|
+
message: "Are you sure you want to overwrite the existing project?",
|
|
46
|
+
initial: true
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (!confirmOverwrite) {
|
|
50
|
+
yield* Effect.logDebug(`User chose to not overwrite the existing project`)
|
|
51
|
+
return yield* Effect.succeed(true)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
yield* Effect.logDebug(`Created temp directory: ${tempDirPath}`)
|
|
56
|
+
|
|
57
|
+
const templateName = repo.subPath
|
|
58
|
+
? path.basename(repo.subPath)
|
|
59
|
+
: path.basename(repo.url).replace(".git", "")
|
|
60
|
+
|
|
61
|
+
spinner.start(`Initializing the ${templateName} template...`)
|
|
62
|
+
yield* runCommand("git", ["clone", "--depth=1", "--branch", "main", repo.url, name], {
|
|
63
|
+
cwd: tempDirPath
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const cloneToTempPath = path.join(tempDirPath, name)
|
|
67
|
+
|
|
68
|
+
yield* Effect.logDebug(`Cloned temp template to ${cloneToTempPath}`)
|
|
69
|
+
|
|
70
|
+
yield* fs.copy(repo.subPath ? path.join(cloneToTempPath, repo.subPath) : cloneToTempPath, newRepoPath, {
|
|
71
|
+
overwrite: true
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
yield* Effect.logDebug(`Copied template to ${newRepoPath}`)
|
|
75
|
+
|
|
76
|
+
const allPaths = yield* fs.readDirectory(newRepoPath, { recursive: true })
|
|
77
|
+
|
|
78
|
+
yield* Effect.logDebug(`Replacing template name ${templateName} with ${name} in ${allPaths.length} files`)
|
|
79
|
+
yield* Effect.logDebug(`All paths: ${allPaths.join("\n")}`)
|
|
80
|
+
|
|
81
|
+
yield* Effect.forEach(allPaths, (file) =>
|
|
82
|
+
Effect.gen(function* () {
|
|
83
|
+
const content = yield* fs
|
|
84
|
+
.readFileString(path.join(newRepoPath, file))
|
|
85
|
+
.pipe(Effect.catchAll(() => Effect.succeed("")))
|
|
86
|
+
|
|
87
|
+
if (!content.includes(templateName)) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
yield* Effect.logDebug(`Replacing template name "${templateName}" with "${name}" in ${file}`)
|
|
92
|
+
|
|
93
|
+
const replaced = content.replaceAll(templateName, name)
|
|
94
|
+
yield* fs.writeFileString(path.join(newRepoPath, file), replaced)
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
spinner.stop()
|
|
99
|
+
|
|
100
|
+
const installDependencies = yield* Prompt.confirm({
|
|
101
|
+
message: "Would you like to install dependencies?",
|
|
102
|
+
initial: true
|
|
103
|
+
})
|
|
104
|
+
let packageManager = "none"
|
|
105
|
+
if (installDependencies) {
|
|
106
|
+
packageManager = yield* Prompt.select({
|
|
107
|
+
message: "Which package manager would you like to use?",
|
|
108
|
+
choices: [
|
|
109
|
+
{ title: "bun", value: "bun" },
|
|
110
|
+
{ title: "pnpm", value: "pnpm" },
|
|
111
|
+
{ title: "npm", value: "npm" },
|
|
112
|
+
{ title: "yarn", value: "yarn" }
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const npmrcPath = path.join(newRepoPath, ".npmrc")
|
|
117
|
+
const hasNpmrc = yield* fs.exists(npmrcPath)
|
|
118
|
+
|
|
119
|
+
if (packageManager === "pnpm" && !hasNpmrc) {
|
|
120
|
+
yield* Effect.logDebug(`Writing .npmrc file...`)
|
|
121
|
+
yield* fs.writeFileString(npmrcPath, "node-linker=hoisted\nenable-pre-post-scripts=true")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (packageManager !== "pnpm" && packageManager !== "none" && hasNpmrc) {
|
|
125
|
+
yield* Effect.logDebug(`Removing .npmrc file...`)
|
|
126
|
+
yield* fs.remove(npmrcPath)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
yield* runCommand(packageManager, ["install"], {
|
|
130
|
+
cwd: newRepoPath,
|
|
131
|
+
stdio: "inherit"
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
yield* runCommand("npx", ["expo", "install", "--fix"], {
|
|
135
|
+
cwd: newRepoPath,
|
|
136
|
+
stdio: "inherit"
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const gitInit = yield* Prompt.confirm({
|
|
141
|
+
message: "Would you like to initialize a Git repository?",
|
|
142
|
+
initial: true
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if (gitInit) {
|
|
146
|
+
spinner.start(`Initializing Git repository...`)
|
|
147
|
+
|
|
148
|
+
let hasGitError = false
|
|
149
|
+
yield* runCommand("git", ["init"], {
|
|
150
|
+
cwd: newRepoPath,
|
|
151
|
+
stdio: "inherit"
|
|
152
|
+
}).pipe(
|
|
153
|
+
Effect.catchAll(() => {
|
|
154
|
+
hasGitError = true
|
|
155
|
+
return Effect.succeed(true)
|
|
156
|
+
})
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if (!hasGitError) {
|
|
160
|
+
yield* runCommand("git", ["add", "-A"], {
|
|
161
|
+
cwd: newRepoPath,
|
|
162
|
+
stdio: "inherit"
|
|
163
|
+
}).pipe(
|
|
164
|
+
Effect.catchAll(() => {
|
|
165
|
+
hasGitError = true
|
|
166
|
+
return Effect.succeed(true)
|
|
167
|
+
})
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!hasGitError) {
|
|
172
|
+
yield* runCommand("git", ["commit", "-m", "initialize project with @react-native-reusables/cli"], {
|
|
173
|
+
cwd: newRepoPath,
|
|
174
|
+
stdio: "inherit"
|
|
175
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(true)))
|
|
176
|
+
}
|
|
177
|
+
spinner.stop()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log("\n")
|
|
181
|
+
yield* Effect.log(`\x1b[37m${logSymbols.success} New project initialized successfully!\x1b[0m`)
|
|
182
|
+
if (packageManager !== "none") {
|
|
183
|
+
yield* Effect.log(
|
|
184
|
+
`\x1b[22m\x1b[38;5;250m${logSymbols.info} To get started, run: \x1b[37m\`cd ${path.join(
|
|
185
|
+
cwd,
|
|
186
|
+
name
|
|
187
|
+
)} && ${packageManager} ${
|
|
188
|
+
packageManager === "npm" || packageManager === "bun" ? "run" : ""
|
|
189
|
+
} dev\`\x1b[0m`
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (packageManager === "none") {
|
|
194
|
+
yield* Effect.log(`\x1b[22m\x1b[38;5;250m${logSymbols.info} To get started:\x1b[0m`)
|
|
195
|
+
yield* Effect.log(
|
|
196
|
+
"\x1b[22m\x1b[38;5;250m↪ Install the dependencies manually using your package manager of choice.\x1b[0m"
|
|
197
|
+
)
|
|
198
|
+
yield* Effect.log("\x1b[22m\x1b[38;5;250m↪ Run the dev script.\x1b[0m")
|
|
199
|
+
}
|
|
200
|
+
console.log("\n")
|
|
201
|
+
yield* Effect.log(`\x1b[37m${logSymbols.info} Additional resources\x1b[0m`)
|
|
202
|
+
yield* Effect.log(
|
|
203
|
+
`\x1b[22m\x1b[38;5;250m↪ Documentation: \x1b[37mhttps://reactnativereusables.com\x1b[0m`
|
|
204
|
+
)
|
|
205
|
+
yield* Effect.log(
|
|
206
|
+
`\x1b[22m\x1b[38;5;250m↪ Report issues: \x1b[37mhttps://github.com/founded-labs/react-native-reusables/issues\x1b[0m`
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return newRepoPath
|
|
210
|
+
}),
|
|
211
|
+
(tempDirPath) => {
|
|
212
|
+
spinner.stop()
|
|
213
|
+
return fs
|
|
214
|
+
.remove(tempDirPath, { recursive: true })
|
|
215
|
+
.pipe(Effect.catchAll(() => Effect.logError(`Failed to remove temp directory at ${tempDirPath}`)))
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
}) {}
|
|
221
|
+
|
|
222
|
+
export { Template }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
|
|
3
|
+
const retryWith = <A, R, E, B>(
|
|
4
|
+
fn: (input: A) => Effect.Effect<R, E, B>,
|
|
5
|
+
inputs: readonly [A, ...Array<A>]
|
|
6
|
+
): Effect.Effect<R, E, B> =>
|
|
7
|
+
inputs.slice(1).reduce((acc, input) => acc.pipe(Effect.orElse(() => fn(input))), fn(inputs[0]))
|
|
8
|
+
|
|
9
|
+
export { retryWith }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { execa } from "execa"
|
|
3
|
+
|
|
4
|
+
const runCommand = (file: string, args: Array<string>, options: Parameters<typeof execa>[1]) =>
|
|
5
|
+
Effect.tryPromise({
|
|
6
|
+
try: () => execa(file, args, options),
|
|
7
|
+
catch: (error) => new Error(`Failed to run command: ${file} ${args.join(" ")}`, { cause: String(error) })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export { runCommand }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": [],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"moduleDetection": "force",
|
|
6
|
+
"composite": true,
|
|
7
|
+
"downlevelIteration": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"esModuleInterop": false,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"exactOptionalPropertyTypes": true,
|
|
13
|
+
"emitDecoratorMetadata": false,
|
|
14
|
+
"experimentalDecorators": true,
|
|
15
|
+
"moduleResolution": "NodeNext",
|
|
16
|
+
"lib": [
|
|
17
|
+
"ES2022",
|
|
18
|
+
"DOM"
|
|
19
|
+
],
|
|
20
|
+
"isolatedModules": true,
|
|
21
|
+
"sourceMap": true,
|
|
22
|
+
"declarationMap": true,
|
|
23
|
+
"noImplicitReturns": false,
|
|
24
|
+
"noUnusedLocals": true,
|
|
25
|
+
"noUnusedParameters": false,
|
|
26
|
+
"noFallthroughCasesInSwitch": true,
|
|
27
|
+
"noEmitOnError": false,
|
|
28
|
+
"noErrorTruncation": false,
|
|
29
|
+
"allowJs": false,
|
|
30
|
+
"checkJs": false,
|
|
31
|
+
"forceConsistentCasingInFileNames": true,
|
|
32
|
+
"stripInternal": true,
|
|
33
|
+
"noImplicitAny": true,
|
|
34
|
+
"noImplicitThis": true,
|
|
35
|
+
"noUncheckedIndexedAccess": false,
|
|
36
|
+
"strictNullChecks": true,
|
|
37
|
+
"baseUrl": ".",
|
|
38
|
+
"target": "ES2022",
|
|
39
|
+
"module": "NodeNext",
|
|
40
|
+
"incremental": true,
|
|
41
|
+
"removeComments": false,
|
|
42
|
+
"plugins": [
|
|
43
|
+
{
|
|
44
|
+
"name": "@effect/language-service"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"paths": {
|
|
48
|
+
"@cli/*": [
|
|
49
|
+
"./src/*"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"include": [
|
|
4
|
+
"scripts",
|
|
5
|
+
"eslint.config.mjs",
|
|
6
|
+
"tsup.config.ts",
|
|
7
|
+
"vitest.config.ts"
|
|
8
|
+
],
|
|
9
|
+
"compilerOptions": {
|
|
10
|
+
"types": [
|
|
11
|
+
"node"
|
|
12
|
+
],
|
|
13
|
+
"tsBuildInfoFile": ".tsbuildinfo/scripts.tsbuildinfo",
|
|
14
|
+
"rootDir": ".",
|
|
15
|
+
"noEmit": true
|
|
16
|
+
}
|
|
17
|
+
}
|
package/tsup.config.ts
ADDED
package/vitest.config.ts
ADDED