ra-ui 0.1.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/bin/ra-ui.js +17 -0
- package/package.json +27 -0
- package/src/commands/add.js +61 -0
- package/src/commands/list.js +39 -0
- package/src/commands/remove.js +61 -0
- package/src/constants.js +7 -0
- package/src/utils/fetch-component.js +47 -0
- package/src/utils/get-config.js +24 -0
- package/src/utils/registry.js +18 -0
package/bin/ra-ui.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { addCommand } from "../src/commands/add.js";
|
|
5
|
+
import { listCommand } from "../src/commands/list.js";
|
|
6
|
+
import { removeCommand } from "../src/commands/remove.js";
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name("ra-ui")
|
|
10
|
+
.description("Fetch UI components from a GitHub registry into your project")
|
|
11
|
+
.version("0.1.0");
|
|
12
|
+
|
|
13
|
+
addCommand(program);
|
|
14
|
+
removeCommand(program);
|
|
15
|
+
listCommand(program);
|
|
16
|
+
|
|
17
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ra-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to fetch and install UI components from a GitHub registry into your project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ra-ui": "bin/ra-ui.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"ui",
|
|
15
|
+
"components",
|
|
16
|
+
"cli",
|
|
17
|
+
"react"
|
|
18
|
+
],
|
|
19
|
+
"author": "ranjeetkumar",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"commander": "^12.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { fetchRegistry } from "../utils/registry.js";
|
|
2
|
+
import { fetchComponentFiles } from "../utils/fetch-component.js";
|
|
3
|
+
import { getConfig } from "../utils/get-config.js";
|
|
4
|
+
|
|
5
|
+
export function addCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("add <component>")
|
|
8
|
+
.description("Add a component to your project")
|
|
9
|
+
.option("-f, --force", "Overwrite existing files")
|
|
10
|
+
.action(async (componentName, options) => {
|
|
11
|
+
try {
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
const config = await getConfig();
|
|
14
|
+
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(" \x1b[36m~\x1b[0m Resolving component...");
|
|
17
|
+
|
|
18
|
+
const registry = await fetchRegistry(config);
|
|
19
|
+
const installed = new Set();
|
|
20
|
+
|
|
21
|
+
await addWithDeps(config, registry, componentName, installed, options);
|
|
22
|
+
|
|
23
|
+
const ms = Date.now() - start;
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(` \x1b[32m\u2714\x1b[0m Done in \x1b[1m${ms}ms\x1b[0m`);
|
|
26
|
+
console.log(` \x1b[90mPath: ${config.outputDir}/\x1b[0m`);
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(" \x1b[35mThanks for using ra-ui! Happy coding \u2728\x1b[0m");
|
|
29
|
+
console.log();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(`\n \x1b[31m\u2718 ${err.message}\x1b[0m\n`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function addWithDeps(config, registry, name, installed, options) {
|
|
38
|
+
if (installed.has(name)) return;
|
|
39
|
+
installed.add(name);
|
|
40
|
+
|
|
41
|
+
const component = registry.components.find(
|
|
42
|
+
(c) => c.name.toLowerCase() === name.toLowerCase()
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (!component) {
|
|
46
|
+
const available = registry.components.map((c) => c.name).join(", ");
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Component "${name}" not found.\n Available: ${available}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (component.dependencies?.length) {
|
|
53
|
+
for (const dep of component.dependencies) {
|
|
54
|
+
console.log(` \x1b[33m\u25CB\x1b[0m Installing dependency: \x1b[1m${dep}\x1b[0m`);
|
|
55
|
+
await addWithDeps(config, registry, dep, installed, options);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(` \x1b[32m\u25CF\x1b[0m Added \x1b[1m${component.name}\x1b[0m`);
|
|
60
|
+
await fetchComponentFiles(config, component, { force: options.force });
|
|
61
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fetchRegistry } from "../utils/registry.js";
|
|
2
|
+
import { getConfig } from "../utils/get-config.js";
|
|
3
|
+
|
|
4
|
+
export function listCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("list")
|
|
7
|
+
.description("List all available components")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
try {
|
|
10
|
+
const config = await getConfig();
|
|
11
|
+
|
|
12
|
+
const registry = await fetchRegistry(config);
|
|
13
|
+
|
|
14
|
+
if (!registry.components?.length) {
|
|
15
|
+
console.log("\n \x1b[33mNo components found in the registry.\x1b[0m\n");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(" \x1b[36m\x1b[1mra-ui\x1b[0m \x1b[90m- React Native Components\x1b[0m");
|
|
21
|
+
console.log(" \x1b[90m" + "\u2500".repeat(40) + "\x1b[0m");
|
|
22
|
+
console.log();
|
|
23
|
+
|
|
24
|
+
const maxLen = Math.max(...registry.components.map((c) => c.name.length));
|
|
25
|
+
|
|
26
|
+
for (const component of registry.components) {
|
|
27
|
+
const name = component.name.padEnd(maxLen + 2);
|
|
28
|
+
console.log(` \x1b[32m\u25CF\x1b[0m \x1b[1m${name}\x1b[0m \x1b[90m${component.description || ""}\x1b[0m`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(" \x1b[90mUsage: \x1b[0mra-ui add \x1b[36m<name>\x1b[0m");
|
|
33
|
+
console.log();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(`\n \x1b[31m\u2718 ${err.message}\x1b[0m\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { unlink, access } from "node:fs/promises";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { getConfig } from "../utils/get-config.js";
|
|
4
|
+
import { fetchRegistry } from "../utils/registry.js";
|
|
5
|
+
|
|
6
|
+
export function removeCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("remove <component>")
|
|
9
|
+
.alias("rm")
|
|
10
|
+
.description("Remove a component from your project")
|
|
11
|
+
.action(async (componentName) => {
|
|
12
|
+
try {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
const config = await getConfig();
|
|
15
|
+
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(" \x1b[36m~\x1b[0m Resolving component...");
|
|
18
|
+
|
|
19
|
+
const registry = await fetchRegistry(config);
|
|
20
|
+
|
|
21
|
+
const component = registry.components.find(
|
|
22
|
+
(c) => c.name.toLowerCase() === componentName.toLowerCase()
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!component) {
|
|
26
|
+
const available = registry.components.map((c) => c.name).join(", ");
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Component "${componentName}" not found.\n Available: ${available}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const outputDir = resolve(process.cwd(), config.outputDir);
|
|
33
|
+
let removed = 0;
|
|
34
|
+
|
|
35
|
+
for (const file of component.files) {
|
|
36
|
+
const filePath = join(outputDir, file);
|
|
37
|
+
try {
|
|
38
|
+
await access(filePath);
|
|
39
|
+
await unlink(filePath);
|
|
40
|
+
console.log(` \x1b[31m\u25CF\x1b[0m Removed \x1b[1m${file}\x1b[0m`);
|
|
41
|
+
console.log(` \x1b[90m\u2514\u2500 ${config.outputDir}/${file}\x1b[0m`);
|
|
42
|
+
removed++;
|
|
43
|
+
} catch {
|
|
44
|
+
console.log(` \x1b[33m\u25CB\x1b[0m \x1b[1m${file}\x1b[0m \x1b[90mnot found, skipping\x1b[0m`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ms = Date.now() - start;
|
|
49
|
+
console.log();
|
|
50
|
+
if (removed > 0) {
|
|
51
|
+
console.log(` \x1b[32m\u2714\x1b[0m Removed in \x1b[1m${ms}ms\x1b[0m`);
|
|
52
|
+
} else {
|
|
53
|
+
console.log(` \x1b[33m\u2714\x1b[0m Nothing to remove`);
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(`\n \x1b[31m\u2718 ${err.message}\x1b[0m\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function fetchComponentFiles(config, component, options = {}) {
|
|
5
|
+
const outputDir = resolve(process.cwd(), config.outputDir);
|
|
6
|
+
await mkdir(outputDir, { recursive: true });
|
|
7
|
+
|
|
8
|
+
for (const file of component.files) {
|
|
9
|
+
const outputPath = join(outputDir, file);
|
|
10
|
+
|
|
11
|
+
if (!options.force) {
|
|
12
|
+
try {
|
|
13
|
+
await access(outputPath);
|
|
14
|
+
console.log(` \x1b[33m\u25CB\x1b[0m Skipped \x1b[1m${file}\x1b[0m \x1b[90m(already exists, use --force)\x1b[0m`);
|
|
15
|
+
continue;
|
|
16
|
+
} catch {
|
|
17
|
+
// File doesn't exist, proceed
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Try jsDelivr first, fallback to GitHub raw
|
|
22
|
+
const urls = [
|
|
23
|
+
`https://cdn.jsdelivr.net/gh/${config.registryRepo}@${config.branch}/${config.registryComponentsPath}/${file}`,
|
|
24
|
+
`https://raw.githubusercontent.com/${config.registryRepo}/${config.branch}/${config.registryComponentsPath}/${file}`,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
let content = null;
|
|
28
|
+
for (const url of urls) {
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(url);
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
content = await res.text();
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!content) {
|
|
41
|
+
throw new Error(`Failed to fetch ${file}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await writeFile(outputPath, content);
|
|
45
|
+
console.log(` \x1b[90m\u2514\u2500 ${config.outputDir}/${file}\x1b[0m`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { DEFAULTS } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
export async function getConfig() {
|
|
6
|
+
const config = { ...DEFAULTS };
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const pkgPath = resolve(process.cwd(), "package.json");
|
|
10
|
+
const raw = await readFile(pkgPath, "utf-8");
|
|
11
|
+
const pkg = JSON.parse(raw);
|
|
12
|
+
|
|
13
|
+
if (pkg["ra-ui"]) {
|
|
14
|
+
const userConfig = pkg["ra-ui"];
|
|
15
|
+
if (userConfig.path) config.outputDir = userConfig.path;
|
|
16
|
+
if (userConfig.registryRepo) config.registryRepo = userConfig.registryRepo;
|
|
17
|
+
if (userConfig.branch) config.branch = userConfig.branch;
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// No package.json found, use defaults
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function fetchRegistry(config) {
|
|
2
|
+
// Try jsDelivr first, fallback to GitHub raw
|
|
3
|
+
const urls = [
|
|
4
|
+
`https://cdn.jsdelivr.net/gh/${config.registryRepo}@${config.branch}/${config.registryJsonPath}`,
|
|
5
|
+
`https://raw.githubusercontent.com/${config.registryRepo}/${config.branch}/${config.registryJsonPath}`,
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
for (const url of urls) {
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetch(url);
|
|
11
|
+
if (res.ok) return await res.json();
|
|
12
|
+
} catch {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new Error("Failed to fetch registry. Check your internet connection.");
|
|
18
|
+
}
|