rahman-resources 0.1.0 → 0.3.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/README.md +19 -10
- package/bin/cli.js +218 -47
- package/lib/manifest.json +389 -20
- package/lib/starter/_README.md +37 -0
- package/lib/starter/_env.example +16 -0
- package/lib/starter/_gitignore +12 -0
- package/lib/starter/_package.json +39 -0
- package/lib/starter/app/globals.css +85 -0
- package/lib/starter/app/layout.tsx +23 -0
- package/lib/starter/app/page.tsx +26 -0
- package/lib/starter/components/convex-provider.tsx +13 -0
- package/lib/starter/components/ui/button.tsx +43 -0
- package/lib/starter/components.json +21 -0
- package/lib/starter/convex/auth.ts +6 -0
- package/lib/starter/convex/http.ts +7 -0
- package/lib/starter/convex/schema.ts +14 -0
- package/lib/starter/lib/utils.ts +6 -0
- package/lib/starter/next.config.mjs +16 -0
- package/lib/starter/postcss.config.mjs +3 -0
- package/lib/starter/proxy.ts +12 -0
- package/lib/starter/tsconfig.json +23 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
# rahman-resources
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffolder + template installer for the [Rahman Resources kitab](https://github.com/rahmanef63/resource-site).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx rahman-resources
|
|
9
|
-
|
|
8
|
+
npx rahman-resources init my-app
|
|
9
|
+
cd my-app
|
|
10
|
+
cp .env.example .env.local # fill NEXT_PUBLIC_CONVEX_URL
|
|
11
|
+
npm install --legacy-peer-deps
|
|
12
|
+
npx convex dev --once # generates convex/_generated
|
|
13
|
+
npm run dev
|
|
10
14
|
```
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
`init` ships a minimal Next 16 + React 19 + Convex + Tailwind 4 + shadcn/ui skeleton (~18 files). Then drop in any layout/recipe/feature with `add`.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
13
19
|
|
|
14
20
|
```bash
|
|
15
|
-
npx rahman-resources
|
|
21
|
+
npx rahman-resources init <app-name> # scaffold fresh project
|
|
22
|
+
npx rahman-resources add <slug> [target-dir] # drop in a layout/recipe/feature
|
|
23
|
+
npx rahman-resources list [layouts|recipes|features]
|
|
24
|
+
npx rahman-resources info <slug>
|
|
16
25
|
```
|
|
17
26
|
|
|
18
27
|
### Inspect a template
|
|
@@ -24,11 +33,11 @@ npx rahman-resources info personal-brand-os
|
|
|
24
33
|
### Install into a project
|
|
25
34
|
|
|
26
35
|
```bash
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
npx rahman-resources add personal-brand-os .
|
|
36
|
+
# fresh
|
|
37
|
+
npx rahman-resources init my-app
|
|
38
|
+
cd my-app && npx rahman-resources add personal-brand-os .
|
|
30
39
|
|
|
31
|
-
# existing
|
|
40
|
+
# existing
|
|
32
41
|
cd existing-app
|
|
33
42
|
npx rahman-resources add personal-brand-os .
|
|
34
43
|
```
|
package/bin/cli.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// rahman-resources —
|
|
2
|
+
// rahman-resources — installer for the Rahman kitab.
|
|
3
3
|
// Usage:
|
|
4
|
-
// npx rahman-resources
|
|
5
|
-
// npx rahman-resources
|
|
6
|
-
// npx rahman-resources
|
|
4
|
+
// npx rahman-resources init <app-name> [--lite]
|
|
5
|
+
// npx rahman-resources add <slug> [target-dir]
|
|
6
|
+
// npx rahman-resources list [layouts|recipes|features]
|
|
7
|
+
// npx rahman-resources info <slug>
|
|
7
8
|
|
|
8
9
|
import { createRequire } from "node:module";
|
|
9
10
|
import { spawn } from "node:child_process";
|
|
10
|
-
import { existsSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import { fileURLToPath } from "node:url";
|
|
13
14
|
|
|
@@ -18,23 +19,33 @@ const require = createRequire(import.meta.url);
|
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const manifest = require(path.join(__dirname, "../lib/manifest.json"));
|
|
20
21
|
|
|
21
|
-
const REPO = "rahmanef63/resource-site";
|
|
22
|
-
const BRANCH = "main";
|
|
22
|
+
const REPO = manifest.repo ?? "rahmanef63/resource-site";
|
|
23
|
+
const BRANCH = manifest.branch ?? "main";
|
|
24
|
+
|
|
25
|
+
const KINDS = /** @type {const} */ (["layout", "recipe", "feature"]);
|
|
23
26
|
|
|
24
27
|
const [, , cmd, ...rest] = process.argv;
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
// Defer to next tick so all module-level const declarations finish initializing
|
|
30
|
+
// before the dispatch reaches functions that reference them.
|
|
31
|
+
queueMicrotask(() =>
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
console.error(kleur.red("✖"), err.message ?? err);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
30
37
|
|
|
31
38
|
async function main() {
|
|
32
39
|
switch (cmd) {
|
|
40
|
+
case "init":
|
|
41
|
+
case "create":
|
|
42
|
+
case "new":
|
|
43
|
+
return runInit(rest);
|
|
33
44
|
case "add":
|
|
34
45
|
return runAdd(rest);
|
|
35
46
|
case "list":
|
|
36
47
|
case "ls":
|
|
37
|
-
return runList();
|
|
48
|
+
return runList(rest);
|
|
38
49
|
case "info":
|
|
39
50
|
return runInfo(rest);
|
|
40
51
|
case undefined:
|
|
@@ -42,6 +53,10 @@ async function main() {
|
|
|
42
53
|
case "--help":
|
|
43
54
|
case "help":
|
|
44
55
|
return printHelp();
|
|
56
|
+
case "-v":
|
|
57
|
+
case "--version":
|
|
58
|
+
case "version":
|
|
59
|
+
return printVersion();
|
|
45
60
|
default:
|
|
46
61
|
console.error(kleur.red(`Unknown command: ${cmd}`));
|
|
47
62
|
printHelp();
|
|
@@ -49,65 +64,187 @@ async function main() {
|
|
|
49
64
|
}
|
|
50
65
|
}
|
|
51
66
|
|
|
67
|
+
function printVersion() {
|
|
68
|
+
const pkg = require(path.join(__dirname, "../package.json"));
|
|
69
|
+
console.log(pkg.version);
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
function printHelp() {
|
|
53
73
|
console.log(`
|
|
54
|
-
${kleur.bold("rahman-resources")} — install templates
|
|
74
|
+
${kleur.bold("rahman-resources")} — scaffold + install templates, recipes, features
|
|
55
75
|
|
|
56
76
|
${kleur.bold("Usage:")}
|
|
57
|
-
npx rahman-resources
|
|
58
|
-
npx rahman-resources
|
|
59
|
-
npx rahman-resources
|
|
77
|
+
npx rahman-resources init <app-name> [--lite]
|
|
78
|
+
npx rahman-resources add <slug> [target-dir]
|
|
79
|
+
npx rahman-resources list [layouts|recipes|features]
|
|
80
|
+
npx rahman-resources info <slug>
|
|
60
81
|
|
|
61
82
|
${kleur.bold("Examples:")}
|
|
62
|
-
npx rahman-resources
|
|
63
|
-
|
|
83
|
+
npx rahman-resources init my-app --lite ${kleur.dim("# scaffold from template-base, prune notion+builder+communications")}
|
|
84
|
+
npx rahman-resources add personal-brand-os my-app ${kleur.dim("# layout — pulls folders + installs deps")}
|
|
85
|
+
npx rahman-resources add ai-sdk-openrouter my-app ${kleur.dim("# feature — runs npm install")}
|
|
86
|
+
npx rahman-resources add block-editor ${kleur.dim("# recipe — prints code + source URL")}
|
|
87
|
+
npx rahman-resources list features
|
|
64
88
|
`);
|
|
65
89
|
}
|
|
66
90
|
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
91
|
+
function findEntry(slug) {
|
|
92
|
+
for (const kind of KINDS) {
|
|
93
|
+
const list = manifest[kind + "s"];
|
|
94
|
+
const e = list.find((x) => x.slug === slug);
|
|
95
|
+
if (e) return { kind, entry: e };
|
|
73
96
|
}
|
|
74
|
-
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runList([filter]) {
|
|
101
|
+
const groups = filter ? [filter] : ["layouts", "recipes", "features"];
|
|
102
|
+
for (const g of groups) {
|
|
103
|
+
const list = manifest[g];
|
|
104
|
+
if (!list || list.length === 0) continue;
|
|
105
|
+
console.log(`\n${kleur.bold(g.toUpperCase())} ${kleur.dim(`(${list.length})`)}\n`);
|
|
106
|
+
for (const t of list) {
|
|
107
|
+
const cat = (t.category ?? t.source ?? "").padEnd(20).slice(0, 20);
|
|
108
|
+
console.log(
|
|
109
|
+
` ${kleur.cyan(t.slug.padEnd(30))} ${kleur.dim(cat)} ${t.title}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
console.log(`\nRun ${kleur.cyan("info <slug>")} for detail, ${kleur.cyan("add <slug> [target]")} to install.\n`);
|
|
75
114
|
}
|
|
76
115
|
|
|
77
116
|
function runInfo([slug]) {
|
|
78
|
-
if (!slug) throw new Error("Usage: rahman-resources info <
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
117
|
+
if (!slug) throw new Error("Usage: rahman-resources info <slug>");
|
|
118
|
+
const found = findEntry(slug);
|
|
119
|
+
if (!found) throw new Error(`Slug not found: ${slug}. Run 'list' to see all.`);
|
|
120
|
+
const { kind, entry: t } = found;
|
|
121
|
+
|
|
81
122
|
console.log(`
|
|
82
|
-
${kleur.bold(t.title)} ${kleur.dim(`[${
|
|
123
|
+
${kleur.bold(t.title)} ${kleur.dim(`[${kind}]`)} ${kleur.dim(t.category ?? "")}
|
|
83
124
|
|
|
84
125
|
${t.description}
|
|
126
|
+
`);
|
|
85
127
|
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
if (kind === "layout") {
|
|
129
|
+
console.log(`${kleur.bold("Pulls:")}`);
|
|
130
|
+
console.log(t.pullPaths.map((p) => ` · ${p}`).join("\n") || " (none)");
|
|
131
|
+
if (t.dependencies?.length) {
|
|
132
|
+
console.log(`\n${kleur.bold("Dependencies:")}`);
|
|
133
|
+
console.log(t.dependencies.map((d) => ` · ${d}`).join("\n"));
|
|
134
|
+
}
|
|
135
|
+
} else if (kind === "feature") {
|
|
136
|
+
console.log(`${kleur.bold("Install:")}\n ${t.install}`);
|
|
137
|
+
if (t.npmPackages?.length) {
|
|
138
|
+
console.log(`\n${kleur.bold("npm packages:")}`);
|
|
139
|
+
console.log(t.npmPackages.map((d) => ` · ${d}`).join("\n"));
|
|
140
|
+
}
|
|
141
|
+
} else if (kind === "recipe") {
|
|
142
|
+
console.log(`${kleur.bold("Files:")}`);
|
|
143
|
+
console.log(t.files.map((f) => ` · ${f}`).join("\n"));
|
|
144
|
+
}
|
|
88
145
|
|
|
89
|
-
${kleur.
|
|
90
|
-
${
|
|
146
|
+
if (t.docsUrl) console.log(`\n${kleur.dim(`Docs: ${t.docsUrl}`)}`);
|
|
147
|
+
console.log(`${kleur.dim(`Source: ${t.source ?? "—"}`)}\n`);
|
|
148
|
+
}
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
150
|
+
// Files in lib/starter prefixed with `_` get renamed on copy.
|
|
151
|
+
// Done because npm pack filters .gitignore and warns on nested package.json.
|
|
152
|
+
const STARTER_RENAME_PAIRS = [
|
|
153
|
+
["_package", "package"],
|
|
154
|
+
["_gitignore", ".gitignore"],
|
|
155
|
+
["_env", ".env"],
|
|
156
|
+
["_README", "README"],
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
function renameStarterFile(name) {
|
|
160
|
+
for (const pair of STARTER_RENAME_PAIRS) {
|
|
161
|
+
const f = pair[0];
|
|
162
|
+
const t = pair[1];
|
|
163
|
+
if (name === f || name.startsWith(f + ".")) return t + name.slice(f.length);
|
|
164
|
+
}
|
|
165
|
+
return name;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function copyStarterTree(src, dest, appName, slug) {
|
|
169
|
+
mkdirSync(dest, { recursive: true });
|
|
170
|
+
for (const entry of readdirSync(src)) {
|
|
171
|
+
const sFull = path.join(src, entry);
|
|
172
|
+
const dEntry = renameStarterFile(entry);
|
|
173
|
+
const dFull = path.join(dest, dEntry);
|
|
174
|
+
const stat = statSync(sFull);
|
|
175
|
+
if (stat.isDirectory()) {
|
|
176
|
+
copyStarterTree(sFull, dFull, appName, slug);
|
|
177
|
+
} else {
|
|
178
|
+
let body = readFileSync(sFull, "utf8");
|
|
179
|
+
body = body.replaceAll("__APP_NAME__", appName).replaceAll("__APP_SLUG__", slug);
|
|
180
|
+
writeFileSync(dFull, body);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runInit([appName, ...flags]) {
|
|
186
|
+
if (!appName || appName.startsWith("-")) {
|
|
187
|
+
throw new Error("Usage: rahman-resources init <app-name>");
|
|
188
|
+
}
|
|
189
|
+
const slug = appName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase();
|
|
190
|
+
const target = path.resolve(process.cwd(), appName);
|
|
191
|
+
if (existsSync(target)) {
|
|
192
|
+
throw new Error(`Directory already exists: ${target}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(kleur.bold(`\n→ Scaffolding ${kleur.cyan(slug)} (Next 16 + Convex + shadcn)\n`));
|
|
196
|
+
|
|
197
|
+
const starter = path.join(__dirname, "../lib/starter");
|
|
198
|
+
if (!existsSync(starter)) {
|
|
199
|
+
throw new Error(`Starter not found at ${starter}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
process.stdout.write(` copying starter ... `);
|
|
203
|
+
copyStarterTree(starter, target, appName, slug);
|
|
204
|
+
console.log(kleur.green("ok"));
|
|
205
|
+
|
|
206
|
+
// Ignore --lite flag for now (no template-base pull yet — starter is minimal already).
|
|
207
|
+
if (flags.includes("--lite")) {
|
|
208
|
+
console.log(kleur.dim(` (--lite is a no-op — starter is already minimal)`));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`\n${kleur.green("✓")} Done. ${kleur.bold(slug)} scaffolded.\n`);
|
|
212
|
+
console.log(`${kleur.bold("Next:")}`);
|
|
213
|
+
console.log(` cd ${appName}`);
|
|
214
|
+
console.log(` cp .env.example .env.local ${kleur.dim("# fill NEXT_PUBLIC_CONVEX_URL")}`);
|
|
215
|
+
console.log(` npm install --legacy-peer-deps`);
|
|
216
|
+
console.log(` npx convex dev --once ${kleur.dim("# generates convex/_generated")}`);
|
|
217
|
+
console.log(` npm run dev\n`);
|
|
218
|
+
console.log(
|
|
219
|
+
`${kleur.dim("Then drop in a layout:")} ${kleur.cyan("npx rahman-resources add personal-brand-os .")}\n`,
|
|
220
|
+
);
|
|
94
221
|
}
|
|
95
222
|
|
|
96
223
|
async function runAdd([slug, targetArg = "."]) {
|
|
97
224
|
if (!slug) {
|
|
98
|
-
console.error(kleur.red("Missing
|
|
225
|
+
console.error(kleur.red("Missing slug."));
|
|
99
226
|
printHelp();
|
|
100
227
|
process.exit(1);
|
|
101
228
|
}
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
229
|
+
const found = findEntry(slug);
|
|
230
|
+
if (!found) {
|
|
104
231
|
throw new Error(
|
|
105
|
-
`
|
|
232
|
+
`"${slug}" not found. Run ${kleur.cyan("npx rahman-resources list")}.`,
|
|
106
233
|
);
|
|
107
234
|
}
|
|
235
|
+
const { kind, entry } = found;
|
|
108
236
|
const target = path.resolve(process.cwd(), targetArg);
|
|
109
237
|
|
|
238
|
+
if (kind === "layout") return addLayout(entry, target, targetArg);
|
|
239
|
+
if (kind === "feature") return addFeature(entry, target, targetArg);
|
|
240
|
+
if (kind === "recipe") return addRecipe(entry);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function addLayout(t, target, targetArg) {
|
|
110
244
|
console.log(kleur.bold(`\n→ Installing ${kleur.cyan(t.title)} into ${kleur.dim(target)}\n`));
|
|
245
|
+
if (!t.pullPaths || t.pullPaths.length === 0) {
|
|
246
|
+
throw new Error(`Layout "${t.slug}" has no valid pullPaths in manifest.`);
|
|
247
|
+
}
|
|
111
248
|
for (const p of t.pullPaths) {
|
|
112
249
|
const dest = path.join(target, p);
|
|
113
250
|
process.stdout.write(` pulling ${kleur.dim(p)} ... `);
|
|
@@ -119,19 +256,53 @@ async function runAdd([slug, targetArg = "."]) {
|
|
|
119
256
|
const pm = detectPM(target);
|
|
120
257
|
console.log(kleur.bold(`\n→ Installing dependencies via ${kleur.cyan(pm)}\n`));
|
|
121
258
|
if (!hasPackageJson(target)) {
|
|
122
|
-
console.log(
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
console.log(kleur.dim(` Run later: cd ${targetArg} && ${pm} add ${t.dependencies.join(" ")}`));
|
|
259
|
+
console.log(kleur.yellow(` ${target}/package.json not found — skipping install.`));
|
|
260
|
+
console.log(kleur.dim(` Run later: cd ${targetArg} && ${pm} ${pm === "npm" ? "install" : "add"} ${t.dependencies.join(" ")}`));
|
|
126
261
|
} else {
|
|
127
|
-
await
|
|
262
|
+
await runPM(pm, t.dependencies, target);
|
|
128
263
|
}
|
|
129
264
|
}
|
|
130
265
|
|
|
131
266
|
console.log(`\n${kleur.green("✓")} Done. ${kleur.bold(t.title)} installed.`);
|
|
132
|
-
if (t.agentRecipe) {
|
|
133
|
-
|
|
267
|
+
if (t.agentRecipe) console.log(`\n${kleur.bold("Next:")}\n${indent(t.agentRecipe, 2)}\n`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function addFeature(t, target, targetArg) {
|
|
271
|
+
console.log(kleur.bold(`\n→ Adding feature ${kleur.cyan(t.title)} to ${kleur.dim(target)}\n`));
|
|
272
|
+
if (!t.npmPackages || t.npmPackages.length === 0) {
|
|
273
|
+
console.log(kleur.dim(` No npm packages to install (${t.install}).`));
|
|
274
|
+
} else {
|
|
275
|
+
const pm = detectPM(target);
|
|
276
|
+
if (!hasPackageJson(target)) {
|
|
277
|
+
console.log(kleur.yellow(` ${target}/package.json not found — skipping install.`));
|
|
278
|
+
console.log(kleur.dim(` Run later: cd ${targetArg} && ${pm} ${pm === "npm" ? "install" : "add"} ${t.npmPackages.join(" ")}`));
|
|
279
|
+
} else {
|
|
280
|
+
console.log(kleur.dim(` via ${pm}: ${t.npmPackages.join(" ")}\n`));
|
|
281
|
+
await runPM(pm, t.npmPackages, target);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(`\n${kleur.green("✓")} Feature added: ${kleur.bold(t.title)}`);
|
|
286
|
+
if (t.exampleCode) {
|
|
287
|
+
console.log(`\n${kleur.bold("Example:")}`);
|
|
288
|
+
console.log(indent(t.exampleCode, 2));
|
|
289
|
+
}
|
|
290
|
+
if (t.agentRecipe) console.log(`\n${kleur.bold("Wire-up:")}\n${indent(t.agentRecipe, 2)}\n`);
|
|
291
|
+
if (t.docsUrl) console.log(`\n${kleur.dim(`Docs: ${t.docsUrl}`)}\n`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function addRecipe(t) {
|
|
295
|
+
console.log(`\n${kleur.bold(t.title)} ${kleur.dim("(recipe — manual port)")}\n`);
|
|
296
|
+
console.log(t.description);
|
|
297
|
+
console.log(`\n${kleur.bold("Source:")} ${t.source}`);
|
|
298
|
+
console.log(`\n${kleur.bold("Files to port:")}`);
|
|
299
|
+
console.log(t.files.map((f) => ` · ${f}`).join("\n"));
|
|
300
|
+
if (t.exampleCode) {
|
|
301
|
+
console.log(`\n${kleur.bold("Example:")}`);
|
|
302
|
+
console.log(indent(t.exampleCode, 2));
|
|
134
303
|
}
|
|
304
|
+
if (t.agentRecipe) console.log(`\n${kleur.bold("Wire-up:")}\n${indent(t.agentRecipe, 2)}\n`);
|
|
305
|
+
console.log(kleur.dim(`\n(Recipes are educational patterns — copy from source repo into your project manually.)\n`));
|
|
135
306
|
}
|
|
136
307
|
|
|
137
308
|
async function pull(repoPath, dest) {
|
|
@@ -154,7 +325,7 @@ function hasPackageJson(target) {
|
|
|
154
325
|
return existsSync(path.join(target, "package.json"));
|
|
155
326
|
}
|
|
156
327
|
|
|
157
|
-
function
|
|
328
|
+
function runPM(pm, deps, cwd) {
|
|
158
329
|
const args = pm === "npm" ? ["install", ...deps] : ["add", ...deps];
|
|
159
330
|
return new Promise((resolve, reject) => {
|
|
160
331
|
const ps = spawn(pm, args, { cwd, stdio: "inherit", shell: true });
|