faang 0.0.0-stage → 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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -2
  3. package/index.mjs +265 -0
  4. package/package.json +31 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mertcan Dundar Esmergul (Faang)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # faang
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Add [Faang](https://faang.vira.ia.br) components to your project as source.
4
+
5
+ ```bash
6
+ npx faang@latest list
7
+ npx faang@latest add button
8
+ npx faang@latest add data-table stat-cards
9
+ npx faang@latest add --all
10
+ ```
11
+
12
+ Each item declares the other items it imports and the npm packages it needs, so
13
+ `add` pulls the whole graph in order and prints the one `npm install` line the
14
+ host project is missing.
15
+
16
+ Options: `--cwd <dir>`, `--registry <url>`, `--overwrite`, `--dry-run`, `--json`.
17
+ `FAANG_REGISTRY` sets the registry for CI. Default registry:
18
+ `https://faang.vira.ia.br/r`.
package/index.mjs ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * faang — install Faang components into a project as source.
4
+ *
5
+ * npx faang@latest list
6
+ * npx faang@latest add button
7
+ * npx faang@latest add data-table stat-cards
8
+ * npx faang@latest add --all
9
+ *
10
+ * The components come from Faang's registry (a JSON index plus one file per
11
+ * item, served by the site). Every item declares its own npm dependencies and
12
+ * the other items it imports, so installing one pulls its whole graph in the
13
+ * right order and prints the packages the host project still needs.
14
+ */
15
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
16
+ import { dirname, isAbsolute, join, resolve } from "node:path";
17
+
18
+ const DEFAULT_REGISTRY = process.env.FAANG_REGISTRY ?? "https://faang.vira.ia.br/r";
19
+ const VERSION = "0.1.0";
20
+
21
+ const HELP = `
22
+ faang v${VERSION} — add Faang components to your project
23
+
24
+ Usage
25
+ faang list every component in the registry
26
+ faang add <name...> install one or more components
27
+ faang add --all install the whole registry
28
+ faang add all same thing
29
+ faang add <name> --dry-run show what would be written
30
+
31
+ Options
32
+ --cwd <dir> project to write into (default: current directory)
33
+ --registry <url> registry to read from (default: ${DEFAULT_REGISTRY})
34
+ --overwrite replace files that already exist
35
+ --dry-run print the plan without touching disk
36
+ --json machine-readable output for \`list\`
37
+ -h, --help this text
38
+
39
+ Registry
40
+ FAANG_REGISTRY same as --registry, for CI
41
+ `;
42
+
43
+ const color = process.env.NO_COLOR || !process.stdout.isTTY
44
+ ? { dim: (s) => s, bold: (s) => s, green: (s) => s, red: (s) => s, cyan: (s) => s }
45
+ : {
46
+ dim: (s) => `\u001b[2m${s}\u001b[0m`,
47
+ bold: (s) => `\u001b[1m${s}\u001b[0m`,
48
+ green: (s) => `\u001b[32m${s}\u001b[0m`,
49
+ red: (s) => `\u001b[31m${s}\u001b[0m`,
50
+ cyan: (s) => `\u001b[36m${s}\u001b[0m`,
51
+ };
52
+
53
+ function parseArgs(argv) {
54
+ const args = { _: [], names: [] };
55
+ for (let i = 0; i < argv.length; i += 1) {
56
+ const arg = argv[i];
57
+ switch (arg) {
58
+ case "--cwd":
59
+ args.cwd = argv[++i];
60
+ break;
61
+ case "--registry":
62
+ args.registry = argv[++i];
63
+ break;
64
+ case "--overwrite":
65
+ args.overwrite = true;
66
+ break;
67
+ case "--dry-run":
68
+ args.dryRun = true;
69
+ break;
70
+ case "--all":
71
+ args.all = true;
72
+ break;
73
+ case "--json":
74
+ args.json = true;
75
+ break;
76
+ case "-h":
77
+ case "--help":
78
+ args.help = true;
79
+ break;
80
+ case "-v":
81
+ case "--version":
82
+ args.version = true;
83
+ break;
84
+ default:
85
+ if (arg.startsWith("-")) {
86
+ console.error(color.red(`Unknown option: ${arg}`));
87
+ process.exit(1);
88
+ }
89
+ args.names.push(arg);
90
+ }
91
+ }
92
+ return args;
93
+ }
94
+
95
+ async function getJson(url) {
96
+ const response = await fetch(url);
97
+ if (!response.ok) {
98
+ throw new Error(`${response.status} ${response.statusText} — ${url}`);
99
+ }
100
+ return response.json();
101
+ }
102
+
103
+ function registryUrl(args) {
104
+ return (args.registry ?? DEFAULT_REGISTRY).replace(/\/+$/, "");
105
+ }
106
+
107
+ async function loadIndex(args) {
108
+ try {
109
+ return await getJson(`${registryUrl(args)}/index.json`);
110
+ } catch (error) {
111
+ console.error(color.red(`Could not read the registry at ${registryUrl(args)}`));
112
+ console.error(color.dim(String(error.message ?? error)));
113
+ console.error(color.dim("Use --registry <url> or set FAANG_REGISTRY."));
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ /** "Button", "buttons/button" and "button" all resolve to the item named button. */
119
+ function resolveName(index, wanted) {
120
+ const term = wanted.toLowerCase().replace(/^@faang\//, "");
121
+ return (
122
+ index.items.find((item) => item.name === term) ??
123
+ index.items.find((item) => item.name === term.split("/").pop()) ??
124
+ index.items.find((item) => item.title.toLowerCase() === term)
125
+ );
126
+ }
127
+
128
+ async function cmdList(args) {
129
+ const index = await loadIndex(args);
130
+ if (args.json) {
131
+ console.log(JSON.stringify(index.items.map(({ name, title, type }) => ({ name, title, type })), null, 2));
132
+ return;
133
+ }
134
+ const width = Math.max(...index.items.map((item) => item.name.length));
135
+ for (const item of index.items) {
136
+ console.log(` ${item.name.padEnd(width)} ${color.dim(item.title)}`);
137
+ }
138
+ console.log(color.dim(`\n ${index.items.length} items — ${registryUrl(args)}`));
139
+ }
140
+
141
+ async function cmdAdd(args) {
142
+ if (!args.all && args.names.length === 0) {
143
+ console.error(color.red("Name at least one component, or pass --all."));
144
+ console.error(color.dim("faang list"));
145
+ process.exit(1);
146
+ }
147
+
148
+ const cwd = isAbsolute(args.cwd ?? "") ? args.cwd : resolve(process.cwd(), args.cwd ?? ".");
149
+ const index = await loadIndex(args);
150
+
151
+ let wanted;
152
+ if (args.all) {
153
+ wanted = index.items;
154
+ } else {
155
+ wanted = args.names.map((name) => {
156
+ const item = resolveName(index, name);
157
+ if (!item) {
158
+ console.error(color.red(`Not in the registry: ${name}`));
159
+ process.exit(1);
160
+ }
161
+ return item;
162
+ });
163
+ }
164
+
165
+ // Items are summaries in the index; fetch each one for its file contents.
166
+ const detail = async (item) => getJson(`${registryUrl(args)}/${item.path ?? `${item.name}.json`}`);
167
+ const resolved = [];
168
+ for (const item of wanted) resolved.push(await detail(item));
169
+
170
+ // Resolve registry dependencies to their full items before installing.
171
+ async function expand(item, acc, seen) {
172
+ if (seen.has(item.name)) return;
173
+ seen.add(item.name);
174
+ for (const name of item.registryDependencies ?? []) {
175
+ const summary = index.items.find((entry) => entry.name === name);
176
+ if (!summary) continue;
177
+ await expand(await detail(summary), acc, seen);
178
+ }
179
+ acc.push(item);
180
+ }
181
+ const ordered = [];
182
+ const seen = new Set();
183
+ for (const item of resolved) await expand(item, ordered, seen);
184
+
185
+ const dependencies = new Set();
186
+ const plan = [];
187
+ for (const item of ordered) {
188
+ for (const dep of item.dependencies ?? []) dependencies.add(dep);
189
+ for (const file of item.files ?? []) plan.push({ ...file, item: item.name });
190
+ }
191
+
192
+ const written = [];
193
+ const skipped = [];
194
+
195
+ for (const file of plan) {
196
+ const target = join(cwd, file.path);
197
+ const exists = existsSync(target);
198
+ if (exists && !args.overwrite) {
199
+ skipped.push(target);
200
+ continue;
201
+ }
202
+ if (!args.dryRun) {
203
+ mkdirSync(dirname(target), { recursive: true });
204
+ writeFileSync(target, file.content ?? "", "utf8");
205
+ }
206
+ written.push(target);
207
+ }
208
+
209
+ const label = args.dryRun ? "would write" : "wrote";
210
+ console.log("");
211
+ for (const item of ordered) {
212
+ console.log(`${color.green("✔")} ${color.bold(item.name)}`);
213
+ }
214
+ console.log("");
215
+ for (const file of written) {
216
+ console.log(` ${color.dim(label)} ${file.replace(`${cwd}/`, "")}`);
217
+ }
218
+ for (const file of skipped) {
219
+ console.log(` ${color.dim("skipped (exists)")} ${file.replace(`${cwd}/`, "")}`);
220
+ }
221
+
222
+ if (dependencies.size > 0) {
223
+ console.log(`\n ${color.bold("npm packages")}\n`);
224
+ console.log(` npm install ${[...dependencies].join(" ")}`);
225
+ }
226
+
227
+ if (skipped.length > 0) {
228
+ console.log(color.dim("\n Existing files were left alone; pass --overwrite to replace them."));
229
+ }
230
+ if (args.dryRun) {
231
+ console.log(color.dim("\n Dry run: nothing was written."));
232
+ }
233
+ }
234
+
235
+ async function main() {
236
+ const args = parseArgs(process.argv.slice(2));
237
+ if (args.names.includes("all")) {
238
+ args.all = true;
239
+ args.names = args.names.filter((name) => name !== "all");
240
+ }
241
+ if (args.version) {
242
+ console.log(VERSION);
243
+ return;
244
+ }
245
+ if (args.help || args._.length === 0 && args.names.length === 0 && !args.all) {
246
+ console.log(HELP);
247
+ return;
248
+ }
249
+
250
+ const command = args.names[0] ?? args._[0];
251
+ const rest = args.names.slice(1);
252
+
253
+ if (command === "list") return cmdList(args);
254
+ if (command === "add") return cmdAdd({ ...args, names: rest });
255
+ if (command === "help") return console.log(HELP);
256
+
257
+ console.error(color.red(`Unknown command: ${command}`));
258
+ console.log(HELP);
259
+ process.exit(1);
260
+ }
261
+
262
+ main().catch((error) => {
263
+ console.error(color.red(String(error?.message ?? error)));
264
+ process.exit(1);
265
+ });
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "faang",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Add Faang components to your project.",
5
+ "type": "module",
6
+ "bin": {
7
+ "faang": "index.mjs"
8
+ },
9
+ "files": [
10
+ "index.mjs",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "license": "MIT",
18
+ "homepage": "https://faang.vira.ia.br",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ferforastieri/faang.git",
22
+ "directory": "cli"
23
+ },
24
+ "keywords": [
25
+ "react",
26
+ "design-system",
27
+ "tailwind",
28
+ "nextjs",
29
+ "components",
30
+ "cli",
31
+ "registry"
32
+ ]
33
+ }