@mahiraltinkaya/me-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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/bin/me-ui.js +60 -0
  4. package/package.json +34 -0
  5. package/registry/assets/breadcrumb_arrow.svg +3 -0
  6. package/registry/components/stepper-provider.tsx +88 -0
  7. package/registry/components/stepper-view.tsx +40 -0
  8. package/registry/components/steps/form-input.tsx +68 -0
  9. package/registry/components/steps/form-select.tsx +42 -0
  10. package/registry/components/steps/step-field.tsx +43 -0
  11. package/registry/components/ui/button.tsx +69 -0
  12. package/registry/components/ui/field-hint.tsx +68 -0
  13. package/registry/components/ui/input.tsx +87 -0
  14. package/registry/components/ui/select.tsx +128 -0
  15. package/registry/components/ui/stepper/index.tsx +24 -0
  16. package/registry/components/ui/stepper/step-divider.tsx +20 -0
  17. package/registry/components/ui/stepper/step-indicator.tsx +33 -0
  18. package/registry/components/ui/stepper/step-label.tsx +29 -0
  19. package/registry/components/ui/stepper/stepper-progress.tsx +40 -0
  20. package/registry/components/ui/stepper/stepper-rail.tsx +107 -0
  21. package/registry/components/ui/stepper/types.ts +26 -0
  22. package/registry/components/ui/tooltip.tsx +54 -0
  23. package/registry/lib/normalize.ts +2 -0
  24. package/registry/lib/quote-schema.ts +45 -0
  25. package/registry/lib/tckn.ts +31 -0
  26. package/registry/lib/utils.ts +6 -0
  27. package/registry.json +252 -0
  28. package/src/args.js +47 -0
  29. package/src/commands/add.js +118 -0
  30. package/src/commands/list.js +20 -0
  31. package/src/css.js +80 -0
  32. package/src/deps.js +52 -0
  33. package/src/jsonc.js +44 -0
  34. package/src/log.js +32 -0
  35. package/src/manifest.js +58 -0
  36. package/src/paths.js +65 -0
  37. package/src/project.js +100 -0
@@ -0,0 +1,58 @@
1
+ /** The shipped item catalogue and its dependency graph. */
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ export const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
8
+ export const registryDir = join(packageRoot, "registry");
9
+
10
+ export const manifest = JSON.parse(readFileSync(join(packageRoot, "registry.json"), "utf8"));
11
+
12
+ const byName = new Map(manifest.items.map((item) => [item.name, item]));
13
+
14
+ export const itemNames = manifest.items.map((item) => item.name);
15
+
16
+ /**
17
+ * Flattens the requested names into the full set of items to install, with each
18
+ * dependency ordered ahead of whatever asked for it. Cycles resolve rather than
19
+ * hang: a name already being visited is treated as satisfied.
20
+ */
21
+ export function resolveItems(names) {
22
+ const ordered = [];
23
+ const settled = new Set();
24
+ const visiting = new Set();
25
+
26
+ const visit = (name) => {
27
+ if (settled.has(name) || visiting.has(name)) return;
28
+
29
+ const item = byName.get(name);
30
+ if (!item) throw new Error(`Unknown item "${name}". Run \`me-ui list\` to see what exists.`);
31
+
32
+ visiting.add(name);
33
+ for (const dependency of item.registryDependencies ?? []) visit(dependency);
34
+ visiting.delete(name);
35
+
36
+ settled.add(name);
37
+ ordered.push(item);
38
+ };
39
+
40
+ names.forEach(visit);
41
+ return ordered;
42
+ }
43
+
44
+ /** npm packages the given items need, deduplicated and in install order. */
45
+ export function npmDependenciesOf(items) {
46
+ return [...new Set(items.flatMap((item) => item.dependencies ?? []))];
47
+ }
48
+
49
+ /** Merges every item's CSS variables into one `{ theme, light }` block. */
50
+ export function cssVarsOf(items) {
51
+ const merged = { theme: {}, light: {} };
52
+ for (const item of items) {
53
+ for (const scope of ["theme", "light"]) {
54
+ Object.assign(merged[scope], item.cssVars?.[scope] ?? {});
55
+ }
56
+ }
57
+ return merged;
58
+ }
package/src/paths.js ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Maps the registry's own layout onto the target project's.
3
+ *
4
+ * Registry paths mirror the `@/` import they resolve to — `components/ui/input.tsx`
5
+ * is `@/components/ui/input` — so one prefix table drives both where a file lands
6
+ * and how the files importing it are rewritten.
7
+ */
8
+
9
+ import { join } from "node:path";
10
+
11
+ // Longest prefix first: `components/ui` must win over `components`.
12
+ const ROOTS = [
13
+ { prefix: "components/ui", key: "ui" },
14
+ { prefix: "components", key: "components" },
15
+ { prefix: "lib", key: "lib" },
16
+ { prefix: "hooks", key: "hooks" },
17
+ ];
18
+
19
+ function split(registryPath) {
20
+ const root = ROOTS.find(
21
+ ({ prefix }) => registryPath === prefix || registryPath.startsWith(`${prefix}/`),
22
+ );
23
+ if (!root) throw new Error(`Registry path "${registryPath}" is outside every known root.`);
24
+ return { key: root.key, rest: registryPath.slice(root.prefix.length).replace(/^\//, "") };
25
+ }
26
+
27
+ /**
28
+ * Absolute path the file should be written to. A `target` pins the file to a
29
+ * fixed spot relative to the project root — that is how assets reach `public/`,
30
+ * which has no alias and no alternative location.
31
+ */
32
+ export function targetPathOf(file, project) {
33
+ if (file.target) return join(project.root, file.target);
34
+ const { key, rest } = split(file.path);
35
+ return join(project.directories[key], rest);
36
+ }
37
+
38
+ /** Path shown to the user — relative, with forward slashes. */
39
+ export function displayPathOf(file, project) {
40
+ return targetPathOf(file, project)
41
+ .slice(project.root.length + 1)
42
+ .replaceAll("\\", "/");
43
+ }
44
+
45
+ const KEY_BY_PREFIX = new Map(ROOTS.map(({ prefix, key }) => [prefix, key]));
46
+
47
+ // One alternation, ordered longest-first, so each specifier is rewritten exactly
48
+ // once. Rewriting root by root would let a later root match an alias an earlier
49
+ // one had just produced.
50
+ const IMPORT_PATTERN = new RegExp(
51
+ `(["'])@/(${ROOTS.map(({ prefix }) => prefix).join("|")})(?=[/"'])`,
52
+ "g",
53
+ );
54
+
55
+ /**
56
+ * Rewrites `@/…` imports to the target project's aliases. Only the roots above
57
+ * are touched, so a project's own `@/utils` or `@/features` imports — should a
58
+ * component ever gain one — are left exactly as they are.
59
+ */
60
+ export function rewriteImports(content, project) {
61
+ return content.replace(
62
+ IMPORT_PATTERN,
63
+ (_match, quote, prefix) => `${quote}${project.aliases[KEY_BY_PREFIX.get(prefix)]}`,
64
+ );
65
+ }
package/src/project.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Reads whatever the target project already tells us about itself.
3
+ *
4
+ * A `components.json` is honoured when present — most projects that would want
5
+ * these components have one, and matching its aliases means the files land where
6
+ * the rest of that project's UI already lives. Everything else is inferred.
7
+ */
8
+
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { isAbsolute, join, resolve } from "node:path";
11
+
12
+ import { parseJsonc } from "./jsonc.js";
13
+
14
+ const DEFAULT_ALIASES = {
15
+ ui: "@/components/ui",
16
+ components: "@/components",
17
+ lib: "@/lib",
18
+ hooks: "@/hooks",
19
+ };
20
+
21
+ const CSS_CANDIDATES = [
22
+ "src/app/globals.css",
23
+ "app/globals.css",
24
+ "src/styles/globals.css",
25
+ "styles/globals.css",
26
+ "src/index.css",
27
+ "src/app.css",
28
+ ];
29
+
30
+ function readJson(path) {
31
+ if (!existsSync(path)) return undefined;
32
+ try {
33
+ return parseJsonc(readFileSync(path, "utf8"));
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Turns an alias like `@/components/ui` into a directory, by running it through
41
+ * the project's `tsconfig` paths. Falls back to the `src/` convention, which is
42
+ * what the mapping almost always says anyway.
43
+ */
44
+ function createAliasResolver(cwd) {
45
+ const tsconfig = readJson(join(cwd, "tsconfig.json")) ?? readJson(join(cwd, "jsconfig.json"));
46
+ const baseUrl = tsconfig?.compilerOptions?.baseUrl ?? ".";
47
+ const paths = tsconfig?.compilerOptions?.paths ?? {};
48
+
49
+ const wildcards = Object.entries(paths)
50
+ .filter(([pattern, targets]) => pattern.endsWith("/*") && targets?.[0]?.endsWith("/*"))
51
+ .map(([pattern, targets]) => [pattern.slice(0, -1), targets[0].slice(0, -1)])
52
+ .sort(([a], [b]) => b.length - a.length);
53
+
54
+ const fallbackRoot = existsSync(join(cwd, "src")) ? "src" : ".";
55
+
56
+ return (alias) => {
57
+ for (const [from, to] of wildcards) {
58
+ if (alias.startsWith(from)) return resolve(cwd, baseUrl, to + alias.slice(from.length));
59
+ }
60
+ return resolve(cwd, fallbackRoot, alias.replace(/^@\//, ""));
61
+ };
62
+ }
63
+
64
+ function findCssFile(cwd, configured) {
65
+ if (configured) {
66
+ const path = isAbsolute(configured) ? configured : join(cwd, configured);
67
+ if (existsSync(path)) return path;
68
+ }
69
+ const found = CSS_CANDIDATES.map((candidate) => join(cwd, candidate)).find((path) =>
70
+ existsSync(path),
71
+ );
72
+ return found;
73
+ }
74
+
75
+ export function loadProject(cwd) {
76
+ const root = resolve(cwd);
77
+ if (!existsSync(join(root, "package.json"))) {
78
+ throw new Error(`No package.json in ${root}. Point --cwd at a project directory.`);
79
+ }
80
+
81
+ const componentsJson = readJson(join(root, "components.json"));
82
+ const aliases = { ...DEFAULT_ALIASES, ...(componentsJson?.aliases ?? {}) };
83
+ const resolveAlias = createAliasResolver(root);
84
+ const packageJson = readJson(join(root, "package.json")) ?? {};
85
+
86
+ return {
87
+ root,
88
+ aliases,
89
+ directories: Object.fromEntries(
90
+ Object.entries(aliases).map(([key, alias]) => [key, resolveAlias(alias)]),
91
+ ),
92
+ cssFile: findCssFile(root, componentsJson?.tailwind?.css),
93
+ installedDependencies: {
94
+ ...(packageJson.dependencies ?? {}),
95
+ ...(packageJson.devDependencies ?? {}),
96
+ ...(packageJson.peerDependencies ?? {}),
97
+ },
98
+ usesComponentsJson: Boolean(componentsJson),
99
+ };
100
+ }