rewampui 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/rewampui.js +40 -0
- package/package.json +35 -0
- package/src/commands/add.js +69 -0
- package/src/commands/init.js +27 -0
- package/src/utils/package-manager.js +50 -0
- package/src/utils/registry.js +56 -0
package/bin/rewampui.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runAdd } from '../src/commands/add.js';
|
|
3
|
+
import { runInit } from '../src/commands/init.js';
|
|
4
|
+
|
|
5
|
+
const [, , command, ...args] = process.argv;
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
if (command === 'init') {
|
|
9
|
+
await runInit(args);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (command === 'add') {
|
|
14
|
+
await runAdd(args);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(`rewampui <command>
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
init Create a components.json config in the current project
|
|
22
|
+
add <component...> Copy one or more component sources + deps into your project
|
|
23
|
+
add --all Install every component in the registry
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
npx rewampui init
|
|
27
|
+
npx rewampui add theme-toggle
|
|
28
|
+
npx rewampui add arch-card-carousel theme-toggle
|
|
29
|
+
npx rewampui add --all
|
|
30
|
+
|
|
31
|
+
pnpm dlx rewampui add theme-toggle
|
|
32
|
+
bunx rewampui add theme-toggle
|
|
33
|
+
yarn dlx rewampui add theme-toggle`);
|
|
34
|
+
process.exit(command ? 1 : 0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main().catch((err) => {
|
|
38
|
+
console.error(`✖ ${err.message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rewampui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to add Rewamp UI components' source code directly into your project.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rewampui": "bin/rewampui.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"prompts": "^2.4.2"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "palakonweb",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/palakonweb/Rewamp-UI.git",
|
|
24
|
+
"directory": "cli"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/palakonweb/Rewamp-UI#readme",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"components",
|
|
30
|
+
"ui",
|
|
31
|
+
"cli",
|
|
32
|
+
"framer-motion",
|
|
33
|
+
"tailwind"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import {
|
|
5
|
+
readConfig,
|
|
6
|
+
fetchRegistryItem,
|
|
7
|
+
readSourceFile,
|
|
8
|
+
listAllRegistryNames,
|
|
9
|
+
} from '../utils/registry.js';
|
|
10
|
+
import { detectPackageManager, installArgs, installCommand } from '../utils/package-manager.js';
|
|
11
|
+
|
|
12
|
+
export async function runAdd(args) {
|
|
13
|
+
if (args.length === 0) {
|
|
14
|
+
throw new Error('Usage: rewampui add <component...> | rewampui add --all');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const config = readConfig(cwd);
|
|
19
|
+
const componentsDir = path.resolve(cwd, config.aliases?.components || 'src/components/ui');
|
|
20
|
+
|
|
21
|
+
const names = args.includes('--all') ? listAllRegistryNames() : args;
|
|
22
|
+
if (names.length === 0) {
|
|
23
|
+
throw new Error('No components found in the registry.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolved = new Map(); // name -> registry item
|
|
27
|
+
const queue = [...names];
|
|
28
|
+
while (queue.length) {
|
|
29
|
+
const name = queue.shift();
|
|
30
|
+
if (resolved.has(name)) continue;
|
|
31
|
+
const item = await fetchRegistryItem(name, config);
|
|
32
|
+
resolved.set(name, item);
|
|
33
|
+
for (const dep of item.registryDependencies || []) {
|
|
34
|
+
if (!resolved.has(dep)) queue.push(dep);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const npmDeps = new Set();
|
|
39
|
+
fs.mkdirSync(componentsDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
for (const [name, item] of resolved) {
|
|
42
|
+
for (const dep of item.npmDependencies || []) npmDeps.add(dep);
|
|
43
|
+
|
|
44
|
+
for (const file of item.files) {
|
|
45
|
+
const targetPath = path.join(componentsDir, file.target);
|
|
46
|
+
const wasExplicitlyRequested = names.includes(name) || names.includes('--all');
|
|
47
|
+
if (fs.existsSync(targetPath) && !wasExplicitlyRequested) {
|
|
48
|
+
console.log(`- skip ${file.target} (already exists)`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const source = await readSourceFile(file.source, config);
|
|
52
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
53
|
+
fs.writeFileSync(targetPath, source, 'utf8');
|
|
54
|
+
console.log(`+ added ${file.target}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (npmDeps.size > 0) {
|
|
59
|
+
const pm = detectPackageManager(cwd);
|
|
60
|
+
const deps = [...npmDeps];
|
|
61
|
+
console.log(`\nInstalling dependencies with ${pm}: ${installCommand(pm, deps)}`);
|
|
62
|
+
const result = spawnSync(pm, installArgs(pm, deps), { cwd, stdio: 'inherit', shell: true });
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
throw new Error(`Failed to install dependencies. Run manually: ${installCommand(pm, deps)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(`\nDone. Added: ${[...resolved.keys()].join(', ')}`);
|
|
69
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const REPO_RAW_BASE = 'https://raw.githubusercontent.com/palakonweb/Rewamp-UI/main';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
$schema: `${REPO_RAW_BASE}/registry/schema.json`,
|
|
8
|
+
registryUrl: `${REPO_RAW_BASE}/registry`,
|
|
9
|
+
repoRawUrl: REPO_RAW_BASE,
|
|
10
|
+
aliases: {
|
|
11
|
+
components: 'src/components/ui',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function runInit(args) {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const configPath = path.join(cwd, 'components.json');
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(configPath) && !args.includes('--force')) {
|
|
20
|
+
console.log('components.json already exists. Pass --force to overwrite.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fs.writeFileSync(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`, 'utf8');
|
|
25
|
+
console.log('+ created components.json');
|
|
26
|
+
console.log('\nNow run: npx rewampui add <component>');
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const LOCKFILES = [
|
|
5
|
+
{ file: 'bun.lockb', pm: 'bun' },
|
|
6
|
+
{ file: 'pnpm-lock.yaml', pm: 'pnpm' },
|
|
7
|
+
{ file: 'yarn.lock', pm: 'yarn' },
|
|
8
|
+
{ file: 'package-lock.json', pm: 'npm' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
/** Walks up from cwd looking for a lockfile to infer the project's package manager. */
|
|
12
|
+
export function detectPackageManager(cwd = process.cwd()) {
|
|
13
|
+
let dir = cwd;
|
|
14
|
+
while (true) {
|
|
15
|
+
for (const { file, pm } of LOCKFILES) {
|
|
16
|
+
if (fs.existsSync(path.join(dir, file))) return pm;
|
|
17
|
+
}
|
|
18
|
+
const parent = path.dirname(dir);
|
|
19
|
+
if (parent === dir) break;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
return 'npm';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function installCommand(pm, packages) {
|
|
26
|
+
const list = packages.join(' ');
|
|
27
|
+
switch (pm) {
|
|
28
|
+
case 'pnpm':
|
|
29
|
+
return `pnpm add ${list}`;
|
|
30
|
+
case 'yarn':
|
|
31
|
+
return `yarn add ${list}`;
|
|
32
|
+
case 'bun':
|
|
33
|
+
return `bun add ${list}`;
|
|
34
|
+
default:
|
|
35
|
+
return `npm install ${list}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function installArgs(pm, packages) {
|
|
40
|
+
switch (pm) {
|
|
41
|
+
case 'pnpm':
|
|
42
|
+
return ['add', ...packages];
|
|
43
|
+
case 'yarn':
|
|
44
|
+
return ['add', ...packages];
|
|
45
|
+
case 'bun':
|
|
46
|
+
return ['add', ...packages];
|
|
47
|
+
default:
|
|
48
|
+
return ['install', ...packages];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
// When developing inside the RewampUI monorepo the registry lives two levels up.
|
|
7
|
+
// A real published install falls back to fetching from components.json's registryUrl.
|
|
8
|
+
const LOCAL_REGISTRY_DIR = path.resolve(__dirname, '../../../registry');
|
|
9
|
+
|
|
10
|
+
export function readConfig(cwd = process.cwd()) {
|
|
11
|
+
const configPath = path.join(cwd, 'components.json');
|
|
12
|
+
if (!fs.existsSync(configPath)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'No components.json found in this project. Run `npx rewampui init` first (or copy components.json from the RewampUI repo).'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fetchRegistryItem(name, config) {
|
|
21
|
+
const localPath = path.join(LOCAL_REGISTRY_DIR, `${name}.json`);
|
|
22
|
+
if (fs.existsSync(localPath)) {
|
|
23
|
+
return JSON.parse(fs.readFileSync(localPath, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const url = `${config.registryUrl.replace(/\/$/, '')}/${name}.json`;
|
|
27
|
+
const res = await fetch(url);
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(`Component "${name}" not found in registry (${url})`);
|
|
30
|
+
}
|
|
31
|
+
return res.json();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function listAllRegistryNames() {
|
|
35
|
+
if (!fs.existsSync(LOCAL_REGISTRY_DIR)) return [];
|
|
36
|
+
return fs
|
|
37
|
+
.readdirSync(LOCAL_REGISTRY_DIR)
|
|
38
|
+
.filter((f) => f.endsWith('.json') && f !== 'schema.json')
|
|
39
|
+
.map((f) => f.replace(/\.json$/, ''));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Resolves a file's raw source, either from local disk (monorepo dev) or over HTTP. */
|
|
43
|
+
export async function readSourceFile(source, config) {
|
|
44
|
+
const localPath = path.resolve(LOCAL_REGISTRY_DIR, '..', source);
|
|
45
|
+
if (fs.existsSync(localPath)) {
|
|
46
|
+
return fs.readFileSync(localPath, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Outside the monorepo (a real published install) there's no local registry/
|
|
50
|
+
// folder — fall back to fetching the file straight from the public GitHub repo.
|
|
51
|
+
const base = config?.repoRawUrl || config?.registryUrl?.replace(/\/registry\/?$/, '');
|
|
52
|
+
const url = base ? `${base.replace(/\/$/, '')}/${source}` : source;
|
|
53
|
+
const res = await fetch(url);
|
|
54
|
+
if (!res.ok) throw new Error(`Could not fetch source file: ${url}`);
|
|
55
|
+
return res.text();
|
|
56
|
+
}
|