@jvxtor-react/cli 1.0.1 → 1.0.3
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/bin/index.js +102 -0
- package/package.json +1 -1
- package/src/http.js +110 -0
- package/src/router.js +85 -0
- package/src/shadcn.js +141 -0
- package/src/utils.js +44 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cancel, intro, isCancel, log, multiselect, outro, select } from "@clack/prompts";
|
|
3
|
+
import { detectPM, install } from "../src/utils.js";
|
|
4
|
+
import { setupAxios, setupFetch } from "../src/http.js";
|
|
5
|
+
import { setupReactRouter } from "../src/router.js";
|
|
6
|
+
import { setupShadcn } from "../src/shadcn.js";
|
|
7
|
+
|
|
8
|
+
const comando = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (comando !== "init") {
|
|
11
|
+
console.log("Uso: npx @jvxtor-react/cli init");
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
intro("@jvxtor-react/cli");
|
|
16
|
+
|
|
17
|
+
const categorias = await multiselect({
|
|
18
|
+
message: "O que você quer instalar?",
|
|
19
|
+
options: [
|
|
20
|
+
{ value: "estilizacao", label: "Estilização" },
|
|
21
|
+
{ value: "http", label: "Cliente HTTP" },
|
|
22
|
+
{ value: "roteamento", label: "Roteamento" },
|
|
23
|
+
],
|
|
24
|
+
required: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (isCancel(categorias)) {
|
|
28
|
+
cancel("Cancelado.");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (categorias.includes("estilizacao")) {
|
|
34
|
+
const escolha = await select({
|
|
35
|
+
message: "Qual lib de estilização você quer instalar?",
|
|
36
|
+
options: [
|
|
37
|
+
{ value: "shadcn", label: "shadcn + Tailwind CSS" },
|
|
38
|
+
{ value: "antd", label: "Ant Design" },
|
|
39
|
+
{ value: "bootstrap", label: "Bootstrap" },
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (isCancel(escolha)) {
|
|
44
|
+
cancel("Cancelado.");
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (escolha === "shadcn") setupShadcn();
|
|
49
|
+
|
|
50
|
+
if (escolha === "antd") install(detectPM(), ["antd"]);
|
|
51
|
+
|
|
52
|
+
if (escolha === "bootstrap") {
|
|
53
|
+
install(detectPM(), ["bootstrap"]);
|
|
54
|
+
log.info(
|
|
55
|
+
'Importe no seu main: import "bootstrap/dist/css/bootstrap.min.css"',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (categorias.includes("http")) {
|
|
61
|
+
const cliente = await select({
|
|
62
|
+
message: "Qual cliente HTTP você quer usar?",
|
|
63
|
+
options: [
|
|
64
|
+
{ value: "axios", label: "axios (recomendado)" },
|
|
65
|
+
{ value: "fetch", label: "fetch" },
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (isCancel(cliente)) {
|
|
70
|
+
cancel("Cancelado.");
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (cliente === "axios") setupAxios();
|
|
75
|
+
|
|
76
|
+
if (cliente === "fetch") setupFetch();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (categorias.includes("roteamento")) {
|
|
80
|
+
const roteador = await select({
|
|
81
|
+
message: "Qual lib de roteamento você quer usar?",
|
|
82
|
+
options: [
|
|
83
|
+
{ value: "react-router", label: "React Router (recomendado)" },
|
|
84
|
+
{ value: "tanstack-router", label: "TanStack Router" },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (isCancel(roteador)) {
|
|
89
|
+
cancel("Cancelado.");
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (roteador === "react-router") setupReactRouter();
|
|
94
|
+
|
|
95
|
+
// TODO: implementar TanStack Router
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
outro("Pronto!");
|
|
99
|
+
} catch (err) {
|
|
100
|
+
log.error(err.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
package/package.json
CHANGED
package/src/http.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { log } from "@clack/prompts"
|
|
4
|
+
import { detectPM, install } from "./utils.js"
|
|
5
|
+
|
|
6
|
+
const API_TS = `import axios from "axios"
|
|
7
|
+
|
|
8
|
+
export const api = axios.create({
|
|
9
|
+
baseURL: import.meta.env.VITE_API_URL,
|
|
10
|
+
timeout: 10000,
|
|
11
|
+
headers: { "Content-Type": "application/json" },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// Interceptor de requisição (ex.: anexar token)
|
|
15
|
+
api.interceptors.request.use(
|
|
16
|
+
(config) => {
|
|
17
|
+
// const token = localStorage.getItem("token")
|
|
18
|
+
// if (token) config.headers.Authorization = \`Bearer \${token}\`
|
|
19
|
+
return config
|
|
20
|
+
},
|
|
21
|
+
(error) => Promise.reject(error),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// Interceptor de resposta (tratamento global de erros)
|
|
25
|
+
api.interceptors.response.use(
|
|
26
|
+
(response) => response,
|
|
27
|
+
(error) => {
|
|
28
|
+
// const status = error.response?.status
|
|
29
|
+
// if (status === 401) {
|
|
30
|
+
// localStorage.removeItem("token")
|
|
31
|
+
// window.location.href = "/login"
|
|
32
|
+
// }
|
|
33
|
+
// if (status === 403) { /* sem permissão */ }
|
|
34
|
+
// if (status >= 500) { /* erro no servidor */ }
|
|
35
|
+
return Promise.reject(error)
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
`
|
|
39
|
+
|
|
40
|
+
const ENDPOINTS_TS = `// Configuração central dos endpoints
|
|
41
|
+
// import { api } from "./api"
|
|
42
|
+
|
|
43
|
+
// export const endpoints = {
|
|
44
|
+
// users: "/users",
|
|
45
|
+
// userById: (id: string | number) => \`/users/\${id}\`,
|
|
46
|
+
// login: "/auth/login",
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
// Exemplo de uso:
|
|
50
|
+
// export const getUsers = () => api.get(endpoints.users)
|
|
51
|
+
`
|
|
52
|
+
|
|
53
|
+
const FETCH_TS = `const BASE_URL = import.meta.env.VITE_API_URL
|
|
54
|
+
|
|
55
|
+
export async function api<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
56
|
+
const res = await fetch(BASE_URL + path, {
|
|
57
|
+
...options,
|
|
58
|
+
headers: { "Content-Type": "application/json", ...options.headers },
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
if (!res.ok) throw new Error(\`Erro \${res.status}\`)
|
|
62
|
+
return res.json()
|
|
63
|
+
}
|
|
64
|
+
`
|
|
65
|
+
|
|
66
|
+
const toJs = (src) =>
|
|
67
|
+
src
|
|
68
|
+
.replace(/: string \| number/g, "")
|
|
69
|
+
|
|
70
|
+
function writeServices(cwd, files, ts) {
|
|
71
|
+
const ext = ts ? "ts" : "js"
|
|
72
|
+
const dir = path.join(cwd, "src", "services")
|
|
73
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
74
|
+
|
|
75
|
+
for (const [base, content] of Object.entries(files)) {
|
|
76
|
+
const name = `${base}.${ext}`
|
|
77
|
+
const file = path.join(dir, name)
|
|
78
|
+
if (fs.existsSync(file)) {
|
|
79
|
+
log.warn(`src/services/${name} já existe, pulando.`)
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
fs.writeFileSync(file, content)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const env = path.join(cwd, ".env")
|
|
86
|
+
if (!fs.existsSync(env)) {
|
|
87
|
+
fs.writeFileSync(env, "VITE_API_URL=http://localhost:3000\n")
|
|
88
|
+
}
|
|
89
|
+
log.info("Criado src/services e .env com VITE_API_URL.")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function setupFetch(cwd = process.cwd()) {
|
|
93
|
+
const ts = fs.existsSync(path.join(cwd, "tsconfig.json"))
|
|
94
|
+
const js = FETCH_TS.replace("<T>", "")
|
|
95
|
+
.replace(": string", "")
|
|
96
|
+
.replace(": RequestInit", "")
|
|
97
|
+
.replace(": Promise<T>", "")
|
|
98
|
+
writeServices(cwd, { api: ts ? FETCH_TS : js }, ts)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function setupAxios(cwd = process.cwd()) {
|
|
102
|
+
install(detectPM(cwd), ["axios"], { cwd })
|
|
103
|
+
|
|
104
|
+
const ts = fs.existsSync(path.join(cwd, "tsconfig.json"))
|
|
105
|
+
writeServices(
|
|
106
|
+
cwd,
|
|
107
|
+
{ api: API_TS, endpoints: ts ? ENDPOINTS_TS : toJs(ENDPOINTS_TS) },
|
|
108
|
+
ts,
|
|
109
|
+
)
|
|
110
|
+
}
|
package/src/router.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { log } from "@clack/prompts"
|
|
4
|
+
import { detectPM, install } from "./utils.js"
|
|
5
|
+
|
|
6
|
+
const ROUTER = `import { createBrowserRouter } from "react-router"
|
|
7
|
+
import Home from "./pages/Home"
|
|
8
|
+
import NotFound from "./pages/NotFound"
|
|
9
|
+
|
|
10
|
+
export const router = createBrowserRouter([
|
|
11
|
+
{ path: "/", element: <Home /> },
|
|
12
|
+
{ path: "*", element: <NotFound /> },
|
|
13
|
+
])
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
const HOME = `export default function Home() {
|
|
17
|
+
return (
|
|
18
|
+
<div>
|
|
19
|
+
<h1>Página inicial</h1>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
const NOT_FOUND = `import { Link } from "react-router"
|
|
26
|
+
|
|
27
|
+
export default function NotFound() {
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<h1>404</h1>
|
|
31
|
+
<p>Página não encontrada.</p>
|
|
32
|
+
<Link to="/">Voltar para o início</Link>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
function writeIfMissing(file, content, cwd) {
|
|
39
|
+
const rel = path.relative(cwd, file)
|
|
40
|
+
if (fs.existsSync(file)) {
|
|
41
|
+
log.warn(`${rel} já existe, pulando.`)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
45
|
+
fs.writeFileSync(file, content)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Troca <App /> por <RouterProvider /> no main
|
|
49
|
+
function patchMain(cwd, ext) {
|
|
50
|
+
const file = path.join(cwd, "src", `main.${ext}`)
|
|
51
|
+
if (!fs.existsSync(file)) {
|
|
52
|
+
log.warn(`src/main.${ext} não encontrado. Use <RouterProvider router={router} /> manualmente.`)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let src = fs.readFileSync(file, "utf8")
|
|
57
|
+
if (src.includes("RouterProvider")) return
|
|
58
|
+
|
|
59
|
+
src = src
|
|
60
|
+
.replace(/^import App from .*\r?\n/m, "")
|
|
61
|
+
.replace(/<App\s*\/>/, "<RouterProvider router={router} />")
|
|
62
|
+
|
|
63
|
+
const imports =
|
|
64
|
+
'import { RouterProvider } from "react-router/dom"\nimport { router } from "./router"\n'
|
|
65
|
+
const lastImport = [...src.matchAll(/^import .*\r?\n/gm)].pop()
|
|
66
|
+
const at = lastImport ? lastImport.index + lastImport[0].length : 0
|
|
67
|
+
src = src.slice(0, at) + imports + src.slice(at)
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(file, src)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function setupReactRouter(cwd = process.cwd()) {
|
|
73
|
+
install(detectPM(cwd), ["react-router"], { cwd })
|
|
74
|
+
|
|
75
|
+
const ts = fs.existsSync(path.join(cwd, "tsconfig.json"))
|
|
76
|
+
const ext = ts ? "tsx" : "jsx"
|
|
77
|
+
const src = path.join(cwd, "src")
|
|
78
|
+
|
|
79
|
+
writeIfMissing(path.join(src, `router.${ext}`), ROUTER, cwd)
|
|
80
|
+
writeIfMissing(path.join(src, "pages", `Home.${ext}`), HOME, cwd)
|
|
81
|
+
writeIfMissing(path.join(src, "pages", `NotFound.${ext}`), NOT_FOUND, cwd)
|
|
82
|
+
patchMain(cwd, ext)
|
|
83
|
+
|
|
84
|
+
log.info("React Router configurado: / (Home) e * (NotFound).")
|
|
85
|
+
}
|
package/src/shadcn.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { applyEdits, modify } from "jsonc-parser"
|
|
4
|
+
import { log } from "@clack/prompts"
|
|
5
|
+
import {
|
|
6
|
+
detectPM,
|
|
7
|
+
execPackage,
|
|
8
|
+
findClosingBracket,
|
|
9
|
+
install,
|
|
10
|
+
} from "./utils.js"
|
|
11
|
+
|
|
12
|
+
const VITE_CONFIGS = [
|
|
13
|
+
"vite.config.ts",
|
|
14
|
+
"vite.config.mts",
|
|
15
|
+
"vite.config.js",
|
|
16
|
+
"vite.config.mjs",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const ALIAS_SNIPPET = `resolve: {
|
|
20
|
+
alias: {
|
|
21
|
+
"@": path.resolve(__dirname, "./src"),
|
|
22
|
+
},
|
|
23
|
+
}`
|
|
24
|
+
|
|
25
|
+
// 2. vite.config: plugin do Tailwind + alias "@"
|
|
26
|
+
export function patchViteConfig(cwd) {
|
|
27
|
+
const file = VITE_CONFIGS.map((f) => path.join(cwd, f)).find((f) =>
|
|
28
|
+
fs.existsSync(f)
|
|
29
|
+
)
|
|
30
|
+
if (!file) {
|
|
31
|
+
log.warn("vite.config não encontrado. Adicione o plugin e o alias manualmente.")
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let src = fs.readFileSync(file, "utf8")
|
|
36
|
+
const imports = []
|
|
37
|
+
|
|
38
|
+
if (!/from\s+["'](node:)?path["']/.test(src)) {
|
|
39
|
+
imports.push('import path from "path"')
|
|
40
|
+
}
|
|
41
|
+
if (!src.includes("@tailwindcss/vite")) {
|
|
42
|
+
imports.push('import tailwindcss from "@tailwindcss/vite"')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pluginsIdx = src.search(/plugins\s*:\s*\[/)
|
|
46
|
+
if (pluginsIdx === -1) {
|
|
47
|
+
log.warn(`Não achei "plugins: [...]" em ${path.basename(file)}. Adicione tailwindcss() e o alias manualmente.`)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
const open = src.indexOf("[", pluginsIdx)
|
|
51
|
+
const close = findClosingBracket(src, open)
|
|
52
|
+
if (close === -1) {
|
|
53
|
+
log.warn(`Não consegui interpretar ${path.basename(file)}. Edite manualmente.`)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let inner = src.slice(open + 1, close)
|
|
58
|
+
if (!/tailwindcss\s*\(/.test(inner)) {
|
|
59
|
+
const trimmed = inner.trimEnd()
|
|
60
|
+
const sep = trimmed === "" ? "" : trimmed.endsWith(",") ? " " : ", "
|
|
61
|
+
inner = trimmed + sep + "tailwindcss()"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let after = src.slice(close + 1)
|
|
65
|
+
if (!/resolve\s*:/.test(src)) {
|
|
66
|
+
after = `,\n ${ALIAS_SNIPPET}` + after
|
|
67
|
+
} else if (!/alias\s*:/.test(src)) {
|
|
68
|
+
log.warn('Já existe "resolve" no vite.config sem "alias". Adicione o alias "@" manualmente.')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
src = src.slice(0, open + 1) + inner + "]" + after
|
|
72
|
+
if (imports.length) src = imports.join("\n") + "\n" + src
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(file, src)
|
|
75
|
+
log.success(`${path.basename(file)} atualizado`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. CSS com @import "tailwindcss"
|
|
79
|
+
export function writeCss(cwd) {
|
|
80
|
+
const file = path.join(cwd, "src", "index.css")
|
|
81
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
82
|
+
fs.writeFileSync(file, '@import "tailwindcss";\n')
|
|
83
|
+
log.success("src/index.css atualizado")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function addAlias(file) {
|
|
87
|
+
let text = fs.readFileSync(file, "utf8")
|
|
88
|
+
const fmt = { formattingOptions: { insertSpaces: true, tabSize: 2 } }
|
|
89
|
+
text = applyEdits(text, modify(text, ["compilerOptions", "baseUrl"], ".", fmt))
|
|
90
|
+
text = applyEdits(
|
|
91
|
+
text,
|
|
92
|
+
modify(text, ["compilerOptions", "paths"], { "@/*": ["./src/*"] }, fmt)
|
|
93
|
+
)
|
|
94
|
+
fs.writeFileSync(file, text)
|
|
95
|
+
log.success(`${path.basename(file)} atualizado`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 4. Alias no TypeScript (ou jsconfig.json em projetos JS)
|
|
99
|
+
export function patchTsconfig(cwd) {
|
|
100
|
+
const ts = fs.existsSync(path.join(cwd, "tsconfig.json"))
|
|
101
|
+
if (!ts) {
|
|
102
|
+
const js = path.join(cwd, "jsconfig.json")
|
|
103
|
+
if (!fs.existsSync(js)) fs.writeFileSync(js, "{}\n")
|
|
104
|
+
addAlias(js)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
addAlias(path.join(cwd, "tsconfig.json"))
|
|
108
|
+
const app = path.join(cwd, "tsconfig.app.json")
|
|
109
|
+
if (fs.existsSync(app)) addAlias(app)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function setupShadcn(cwd = process.cwd()) {
|
|
113
|
+
const pkgPath = path.join(cwd, "package.json")
|
|
114
|
+
if (!fs.existsSync(pkgPath)) {
|
|
115
|
+
throw new Error("package.json não encontrado. Rode o comando na raiz do projeto.")
|
|
116
|
+
}
|
|
117
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
118
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
119
|
+
if (!deps.vite) {
|
|
120
|
+
throw new Error("Este projeto não parece usar Vite.")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const pm = detectPM(cwd)
|
|
124
|
+
const isTs = fs.existsSync(path.join(cwd, "tsconfig.json"))
|
|
125
|
+
|
|
126
|
+
log.step("1/5 Instalando Tailwind e o plugin do Vite")
|
|
127
|
+
install(pm, ["tailwindcss", "@tailwindcss/vite"], { cwd })
|
|
128
|
+
if (isTs) install(pm, ["@types/node"], { dev: true, cwd })
|
|
129
|
+
|
|
130
|
+
log.step("2/5 Configurando vite.config")
|
|
131
|
+
patchViteConfig(cwd)
|
|
132
|
+
|
|
133
|
+
log.step("3/5 Configurando o CSS")
|
|
134
|
+
writeCss(cwd)
|
|
135
|
+
|
|
136
|
+
log.step("4/5 Configurando o alias @")
|
|
137
|
+
patchTsconfig(cwd)
|
|
138
|
+
|
|
139
|
+
log.step("5/5 Inicializando o shadcn")
|
|
140
|
+
execPackage(pm, "shadcn@latest init", { cwd })
|
|
141
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { execSync } from "node:child_process"
|
|
4
|
+
|
|
5
|
+
export function detectPM(cwd = process.cwd()) {
|
|
6
|
+
const has = (f) => fs.existsSync(path.join(cwd, f))
|
|
7
|
+
if (has("pnpm-lock.yaml")) return "pnpm"
|
|
8
|
+
if (has("yarn.lock")) return "yarn"
|
|
9
|
+
if (has("bun.lockb") || has("bun.lock")) return "bun"
|
|
10
|
+
return "npm"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const COMMANDS = {
|
|
14
|
+
npm: { add: "npm install", addDev: "npm install -D", exec: "npx" },
|
|
15
|
+
pnpm: { add: "pnpm add", addDev: "pnpm add -D", exec: "pnpm dlx" },
|
|
16
|
+
yarn: { add: "yarn add", addDev: "yarn add -D", exec: "npx" },
|
|
17
|
+
bun: { add: "bun add", addDev: "bun add -d", exec: "bunx" },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function run(cmd, cwd = process.cwd()) {
|
|
21
|
+
execSync(cmd, { stdio: "inherit", cwd })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function install(pm, packages, { dev = false, cwd } = {}) {
|
|
25
|
+
const base = dev ? COMMANDS[pm].addDev : COMMANDS[pm].add
|
|
26
|
+
run(`${base} ${packages.join(" ")}`, cwd)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function execPackage(pm, command, { cwd } = {}) {
|
|
30
|
+
run(`${COMMANDS[pm].exec} ${command}`, cwd)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Acha o índice do "]" que fecha o "[" em openIndex (considera colchetes aninhados)
|
|
34
|
+
export function findClosingBracket(src, openIndex) {
|
|
35
|
+
let depth = 0
|
|
36
|
+
for (let i = openIndex; i < src.length; i++) {
|
|
37
|
+
if (src[i] === "[") depth++
|
|
38
|
+
if (src[i] === "]") {
|
|
39
|
+
depth--
|
|
40
|
+
if (depth === 0) return i
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return -1
|
|
44
|
+
}
|