@75neo/ui 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 75Neo
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 ADDED
@@ -0,0 +1,85 @@
1
+ # @75neo/ui
2
+
3
+ The installer and animation stylesheet for [75NeoUI](https://75neo-ui.pages.dev), a component
4
+ library for React and Vue.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npx @75neo/ui@latest init
10
+ npx @75neo/ui@latest add button
11
+ ```
12
+
13
+ `init` detects whether the project is React or Vue, writes a `75neoui.json`, and adds the theme
14
+ tokens and this package's animation stylesheet to your Tailwind entry file. `add` writes component
15
+ source into your project and installs the npm packages it declares.
16
+
17
+ ## Commands
18
+
19
+ | Command | What it does |
20
+ | --------------- | ----------------------------------------------------- |
21
+ | `init` | Set the project up and write `75neoui.json` |
22
+ | `add <items..>` | Add components, resolving their registry dependencies |
23
+ | `add --all` | Add every component |
24
+ | `list` | List the components in the registry |
25
+
26
+ Every command takes `--cwd` and `--registry`. `init` also takes `--framework`, `--css` and
27
+ `--overwrite`; `add` takes `--all` and `--overwrite`. Pass `--no-install` to skip npm installs.
28
+
29
+ ## Programmatic API
30
+
31
+ The package is written in TypeScript and ships type declarations. Every command is also available
32
+ as a function, so a script can drive the installer directly.
33
+
34
+ ```ts
35
+ import { readConfig, resolveItems, writeFiles } from "@75neo/ui";
36
+
37
+ const config = await readConfig(process.cwd());
38
+ const items = await resolveItems(config.registry, config.framework, ["button"]);
39
+ const { written, skipped } = await writeFiles(items, config, {
40
+ cwd: process.cwd(),
41
+ overwrite: false,
42
+ });
43
+ ```
44
+
45
+ Registry responses and `75neoui.json` are validated against the package's own zod schemas rather
46
+ than cast, so a malformed document fails with a message naming the field instead of surfacing
47
+ later as a crash. The schemas are exported too, and `jsonSchemas()` renders them as JSON Schema.
48
+
49
+ ```ts
50
+ import { registryItemSchema, validate } from "@75neo/ui";
51
+
52
+ const item = validate(registryItemSchema, "button.json", await response.json());
53
+ ```
54
+
55
+ ## Stylesheet
56
+
57
+ ```css
58
+ @import "tailwindcss";
59
+ @import "@75neo/ui/animations.css";
60
+ ```
61
+
62
+ The stylesheet carries the keyframes and `--animate-*` entries that overlay components use. Design
63
+ tokens are not here on purpose: `init` writes those into your own stylesheet so you can override
64
+ them.
65
+
66
+ ## Releasing
67
+
68
+ The first publish of a new package must be manual, because npm only lets a trusted publisher be
69
+ configured on a package that already exists.
70
+
71
+ ```sh
72
+ cd packages/ui
73
+ pnpm publish --access public --no-git-checks
74
+ ```
75
+
76
+ After that, configure trusted publishing once, either on the package's access page at npmjs.com or
77
+ with the npm CLI:
78
+
79
+ ```sh
80
+ npm trust github @75neo/ui --repo 75Neo/ui --file release.yml --allow-publish
81
+ ```
82
+
83
+ From then on, publishing a GitHub release named `v<version>` runs `.github/workflows/release.yml`,
84
+ which authenticates over OIDC with no token stored anywhere. The workflow refuses to run when the
85
+ release tag and the package version disagree.
package/animations.css ADDED
@@ -0,0 +1,90 @@
1
+ @theme {
2
+ --animate-panel-in: panel-in 0.15s ease-out;
3
+ --animate-panel-out: panel-out 0.1s ease-in;
4
+ --animate-fade-in: fade-in 0.15s ease-out;
5
+ --animate-fade-out: fade-out 0.1s ease-in;
6
+ --animate-dialog-in: dialog-in 0.15s cubic-bezier(0.16, 1, 0.3, 1);
7
+ --animate-dialog-out: dialog-out 0.1s ease-in;
8
+ --animate-drawer-in: drawer-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
9
+ --animate-drawer-out: drawer-out 0.15s ease-in;
10
+
11
+ @keyframes panel-in {
12
+ from {
13
+ opacity: 0;
14
+ transform: translateY(-0.25rem);
15
+ }
16
+ to {
17
+ opacity: 1;
18
+ transform: translateY(0);
19
+ }
20
+ }
21
+
22
+ @keyframes panel-out {
23
+ from {
24
+ opacity: 1;
25
+ transform: translateY(0);
26
+ }
27
+ to {
28
+ opacity: 0;
29
+ transform: translateY(-0.25rem);
30
+ }
31
+ }
32
+
33
+ @keyframes fade-in {
34
+ from {
35
+ opacity: 0;
36
+ }
37
+ to {
38
+ opacity: 1;
39
+ }
40
+ }
41
+
42
+ @keyframes fade-out {
43
+ from {
44
+ opacity: 1;
45
+ }
46
+ to {
47
+ opacity: 0;
48
+ }
49
+ }
50
+
51
+ @keyframes dialog-in {
52
+ from {
53
+ opacity: 0;
54
+ transform: scale(0.96);
55
+ }
56
+ to {
57
+ opacity: 1;
58
+ transform: scale(1);
59
+ }
60
+ }
61
+
62
+ @keyframes dialog-out {
63
+ from {
64
+ opacity: 1;
65
+ transform: scale(1);
66
+ }
67
+ to {
68
+ opacity: 0;
69
+ transform: scale(0.96);
70
+ }
71
+ }
72
+
73
+ @keyframes drawer-in {
74
+ from {
75
+ translate: var(--drawer-from);
76
+ }
77
+ to {
78
+ translate: 0 0;
79
+ }
80
+ }
81
+
82
+ @keyframes drawer-out {
83
+ from {
84
+ translate: 0 0;
85
+ }
86
+ to {
87
+ translate: var(--drawer-from);
88
+ }
89
+ }
90
+ }
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.mjs ADDED
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ import { C as detectFramework, D as FRAMEWORKS, E as writeConfig, S as detectCssEntry, T as readConfig, _ as CONFIG_FILE, a as resolveItems, c as detectPackageManager, g as withTheme, h as withImport, i as fetchItem, k as ValidationError, l as install, n as collectDependencies, p as writeFiles, r as fetchIndex, t as RegistryError, u as InstallError, v as ConfigError, w as detectPaths, x as configPath, y as DEFAULT_ALIASES } from "./registry-C1lAzYLJ.mjs";
3
+ import { existsSync } from "node:fs";
4
+ import { readFile, writeFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+ import yargs from "yargs";
8
+ import { hideBin } from "yargs/helpers";
9
+ //#region src/cli.ts
10
+ const PACKAGE = "@75neo/ui";
11
+ const ANIMATIONS = `${PACKAGE}/animations.css`;
12
+ const THEME_MARKER = "/* 75NeoUI theme */";
13
+ const green = (text) => `\x1b[32m${text}\x1b[0m`;
14
+ const red = (text) => `\x1b[31m${text}\x1b[0m`;
15
+ const dim = (text) => `\x1b[2m${text}\x1b[0m`;
16
+ const bold = (text) => `\x1b[1m${text}\x1b[0m`;
17
+ async function updateStylesheet(cwd, config, css) {
18
+ const stylesheet = path.join(cwd, config.css);
19
+ const source = await readFile(stylesheet, "utf8");
20
+ const next = withTheme(withImport(source, ANIMATIONS), css, THEME_MARKER);
21
+ if (next === source) return false;
22
+ await writeFile(stylesheet, next);
23
+ return true;
24
+ }
25
+ async function runInit(args) {
26
+ const cwd = path.resolve(args.cwd);
27
+ if (existsSync(configPath(cwd)) && !args.overwrite) throw new ConfigError(`${CONFIG_FILE} already exists. Pass --overwrite to replace it.`);
28
+ const framework = args.framework ?? await detectFramework(cwd);
29
+ if (framework === null || framework === void 0) throw new ConfigError(`Could not tell whether this is a React or Vue project. Pass --framework ${FRAMEWORKS.join(" or --framework ")}.`);
30
+ const css = args.css ?? await detectCssEntry(cwd);
31
+ if (css === null || css === void 0) throw new ConfigError("Could not find a stylesheet importing \"tailwindcss\". Pass --css with the path to it.");
32
+ const config = {
33
+ framework,
34
+ css,
35
+ registry: args.registry ?? "https://75neo-ui.pages.dev/r",
36
+ paths: detectPaths(cwd),
37
+ aliases: DEFAULT_ALIASES
38
+ };
39
+ await writeConfig(cwd, config);
40
+ console.log(`${green("done")} wrote ${CONFIG_FILE}`);
41
+ const theme = await fetchItem(config.registry, framework, "theme");
42
+ if (theme.css === void 0) throw new RegistryError("The theme item carries no CSS, so there are no tokens to write.");
43
+ await updateStylesheet(cwd, config, theme.css);
44
+ console.log(`${green("done")} updated ${css}`);
45
+ if (!args.install) return;
46
+ const manager = detectPackageManager(cwd);
47
+ console.log(dim(`${manager} add ${PACKAGE}`));
48
+ await install([PACKAGE], {
49
+ cwd,
50
+ manager
51
+ });
52
+ }
53
+ async function runAdd(args) {
54
+ const cwd = path.resolve(args.cwd);
55
+ const config = await readConfig(cwd);
56
+ const registry = args.registry ?? config.registry;
57
+ let names = args.items;
58
+ if (args.all) names = (await fetchIndex(registry, config.framework)).items.filter((item) => item.type === "registry:ui").map((item) => item.name);
59
+ if (names.length === 0) throw new RegistryError("Name at least one component, or pass --all.");
60
+ const items = await resolveItems(registry, config.framework, names);
61
+ const { written, skipped } = await writeFiles(items, config, {
62
+ cwd,
63
+ overwrite: args.overwrite
64
+ });
65
+ for (const file of written) console.log(`${green("done")} ${file}`);
66
+ for (const file of skipped) console.log(dim(`kept ${file}, pass --overwrite to replace it`));
67
+ const theme = items.find((item) => item.type === "registry:theme");
68
+ if (theme?.css !== void 0 && await updateStylesheet(cwd, config, theme.css)) console.log(`${green("done")} updated ${config.css}`);
69
+ if (!args.install) return;
70
+ const dependencies = collectDependencies(items);
71
+ if (dependencies.length === 0) return;
72
+ const manager = detectPackageManager(cwd);
73
+ console.log(dim(`${manager} add ${dependencies.join(" ")}`));
74
+ await install(dependencies, {
75
+ cwd,
76
+ manager
77
+ });
78
+ }
79
+ async function runList(args) {
80
+ const cwd = path.resolve(args.cwd);
81
+ const framework = args.framework ?? (await readConfig(cwd)).framework;
82
+ const index = await fetchIndex(args.registry ?? "https://75neo-ui.pages.dev/r", framework);
83
+ for (const item of index.items) {
84
+ if (item.type !== "registry:ui") continue;
85
+ console.log(`${bold(item.name.padEnd(18))}${item.description ?? ""}`);
86
+ }
87
+ }
88
+ function fail(error) {
89
+ const known = error instanceof ConfigError || error instanceof RegistryError || error instanceof InstallError || error instanceof ValidationError;
90
+ const message = error instanceof Error ? known ? error.message : error.stack ?? error.message : String(error);
91
+ console.error(red(message));
92
+ process.exitCode = 1;
93
+ }
94
+ const run = (handler) => (args) => {
95
+ handler(args).catch(fail);
96
+ };
97
+ await yargs(hideBin(process.argv)).scriptName("75neoui").usage("$0 <command> [options]").options({
98
+ cwd: {
99
+ type: "string",
100
+ default: process.cwd(),
101
+ describe: "Project directory to work in"
102
+ },
103
+ registry: {
104
+ type: "string",
105
+ describe: "Registry base URL or local directory"
106
+ }
107
+ }).command("init", "Set the project up for 75NeoUI", (builder) => builder.options({
108
+ framework: {
109
+ type: "string",
110
+ choices: FRAMEWORKS,
111
+ describe: "Framework to install for"
112
+ },
113
+ css: {
114
+ type: "string",
115
+ describe: "Path to the Tailwind entry stylesheet"
116
+ },
117
+ overwrite: {
118
+ type: "boolean",
119
+ default: false,
120
+ describe: `Replace an existing ${CONFIG_FILE}`
121
+ },
122
+ install: {
123
+ type: "boolean",
124
+ default: true,
125
+ describe: "Install npm dependencies"
126
+ }
127
+ }), run(runInit)).command("add [items..]", "Add components to the project", (builder) => builder.positional("items", {
128
+ type: "string",
129
+ array: true,
130
+ default: []
131
+ }).options({
132
+ all: {
133
+ type: "boolean",
134
+ default: false,
135
+ describe: "Add every component"
136
+ },
137
+ overwrite: {
138
+ type: "boolean",
139
+ default: false,
140
+ describe: "Replace files that already exist"
141
+ },
142
+ install: {
143
+ type: "boolean",
144
+ default: true,
145
+ describe: "Install npm dependencies"
146
+ }
147
+ }), run(runAdd)).command("list", "List the components in the registry", (builder) => builder.options({ framework: {
148
+ type: "string",
149
+ choices: FRAMEWORKS,
150
+ describe: "Framework to list"
151
+ } }), run(runList)).demandCommand(1, "Name a command. Try `75neoui --help`.").strict().help().alias("h", "help").version().wrap(Math.min(100, process.stdout.columns ?? 100)).parseAsync();
152
+ //#endregion
153
+ export {};
@@ -0,0 +1,215 @@
1
+ import { z } from "zod";
2
+ //#region src/schema.d.ts
3
+ export declare const FRAMEWORKS: readonly ["react", "vue"];
4
+ export declare const ITEM_TYPES: readonly ["registry:ui", "registry:lib", "registry:theme"];
5
+ export declare const frameworkSchema: z.ZodEnum<{
6
+ react: "react";
7
+ vue: "vue";
8
+ }>;
9
+ export declare const itemTypeSchema: z.ZodEnum<{
10
+ "registry:ui": "registry:ui";
11
+ "registry:lib": "registry:lib";
12
+ "registry:theme": "registry:theme";
13
+ }>;
14
+ export declare const cssBlockSchema: z.ZodRecord<z.ZodString, z.ZodType<CssValue, unknown, z.core.$ZodTypeInternals<CssValue, unknown>>>;
15
+ export declare const registryFileSchema: z.ZodObject<{
16
+ path: z.ZodString;
17
+ type: z.ZodEnum<{
18
+ "registry:ui": "registry:ui";
19
+ "registry:lib": "registry:lib";
20
+ "registry:theme": "registry:theme";
21
+ }>;
22
+ content: z.ZodString;
23
+ }, z.core.$strip>;
24
+ export declare const registryFileEntrySchema: z.ZodObject<{
25
+ type: z.ZodEnum<{
26
+ "registry:ui": "registry:ui";
27
+ "registry:lib": "registry:lib";
28
+ "registry:theme": "registry:theme";
29
+ }>;
30
+ path: z.ZodString;
31
+ }, z.core.$strip>;
32
+ export declare const registryItemSchema: z.ZodObject<{
33
+ name: z.ZodString;
34
+ type: z.ZodEnum<{
35
+ "registry:ui": "registry:ui";
36
+ "registry:lib": "registry:lib";
37
+ "registry:theme": "registry:theme";
38
+ }>;
39
+ title: z.ZodOptional<z.ZodString>;
40
+ description: z.ZodOptional<z.ZodString>;
41
+ dependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
+ registryDependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
43
+ css: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<CssValue, unknown, z.core.$ZodTypeInternals<CssValue, unknown>>>>;
44
+ docs: z.ZodOptional<z.ZodString>;
45
+ files: z.ZodOptional<z.ZodArray<z.ZodObject<{
46
+ path: z.ZodString;
47
+ type: z.ZodEnum<{
48
+ "registry:ui": "registry:ui";
49
+ "registry:lib": "registry:lib";
50
+ "registry:theme": "registry:theme";
51
+ }>;
52
+ content: z.ZodString;
53
+ }, z.core.$strip>>>;
54
+ }, z.core.$strip>;
55
+ export declare const registryIndexEntrySchema: z.ZodObject<{
56
+ name: z.ZodString;
57
+ type: z.ZodEnum<{
58
+ "registry:ui": "registry:ui";
59
+ "registry:lib": "registry:lib";
60
+ "registry:theme": "registry:theme";
61
+ }>;
62
+ title: z.ZodOptional<z.ZodString>;
63
+ description: z.ZodOptional<z.ZodString>;
64
+ }, z.core.$strip>;
65
+ export declare const registryIndexSchema: z.ZodObject<{
66
+ name: z.ZodString;
67
+ homepage: z.ZodOptional<z.ZodString>;
68
+ items: z.ZodArray<z.ZodObject<{
69
+ name: z.ZodString;
70
+ type: z.ZodEnum<{
71
+ "registry:ui": "registry:ui";
72
+ "registry:lib": "registry:lib";
73
+ "registry:theme": "registry:theme";
74
+ }>;
75
+ title: z.ZodOptional<z.ZodString>;
76
+ description: z.ZodOptional<z.ZodString>;
77
+ }, z.core.$strip>>;
78
+ }, z.core.$strip>;
79
+ declare const pathsSchema: z.ZodObject<{
80
+ ui: z.ZodString;
81
+ lib: z.ZodString;
82
+ }, z.core.$strip>;
83
+ export declare const configSchema: z.ZodObject<{
84
+ framework: z.ZodEnum<{
85
+ react: "react";
86
+ vue: "vue";
87
+ }>;
88
+ css: z.ZodString;
89
+ registry: z.ZodString;
90
+ paths: z.ZodObject<{
91
+ ui: z.ZodString;
92
+ lib: z.ZodString;
93
+ }, z.core.$strip>;
94
+ aliases: z.ZodObject<{
95
+ ui: z.ZodString;
96
+ lib: z.ZodString;
97
+ }, z.core.$strip>;
98
+ }, z.core.$strip>;
99
+ type CssValue = string | {
100
+ [selector: string]: CssValue;
101
+ };
102
+ type CssBlock = z.infer<typeof cssBlockSchema>;
103
+ type Framework = z.infer<typeof frameworkSchema>;
104
+ type ItemType = z.infer<typeof itemTypeSchema>;
105
+ type RegistryFile = z.infer<typeof registryFileSchema>;
106
+ type RegistryItem = z.infer<typeof registryItemSchema>;
107
+ type RegistryIndex = z.infer<typeof registryIndexSchema>;
108
+ type RegistryIndexEntry = z.infer<typeof registryIndexEntrySchema>;
109
+ type Paths = z.infer<typeof pathsSchema>;
110
+ type Aliases = Paths;
111
+ type Config = z.infer<typeof configSchema>;
112
+ export declare class ValidationError extends Error {
113
+ readonly source: string;
114
+ readonly issues: readonly z.core.$ZodIssue[];
115
+ constructor(source: string, issues: readonly z.core.$ZodIssue[]);
116
+ }
117
+ export declare function validate<T>(schema: z.ZodType<T>, source: string, value: unknown): T;
118
+ export declare function parseJson(source: string, text: string): unknown;
119
+ export declare const jsonSchemas: () => {
120
+ "registry.json": z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
121
+ name: z.ZodString;
122
+ homepage: z.ZodOptional<z.ZodString>;
123
+ items: z.ZodArray<z.ZodObject<{
124
+ name: z.ZodString;
125
+ type: z.ZodEnum<{
126
+ "registry:ui": "registry:ui";
127
+ "registry:lib": "registry:lib";
128
+ "registry:theme": "registry:theme";
129
+ }>;
130
+ title: z.ZodOptional<z.ZodString>;
131
+ description: z.ZodOptional<z.ZodString>;
132
+ }, z.core.$strip>>;
133
+ }, z.core.$strip>>;
134
+ "registry-item.json": z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
135
+ name: z.ZodString;
136
+ type: z.ZodEnum<{
137
+ "registry:ui": "registry:ui";
138
+ "registry:lib": "registry:lib";
139
+ "registry:theme": "registry:theme";
140
+ }>;
141
+ title: z.ZodOptional<z.ZodString>;
142
+ description: z.ZodOptional<z.ZodString>;
143
+ dependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
144
+ registryDependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
145
+ css: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<CssValue, unknown, z.core.$ZodTypeInternals<CssValue, unknown>>>>;
146
+ docs: z.ZodOptional<z.ZodString>;
147
+ files: z.ZodOptional<z.ZodArray<z.ZodObject<{
148
+ path: z.ZodString;
149
+ type: z.ZodEnum<{
150
+ "registry:ui": "registry:ui";
151
+ "registry:lib": "registry:lib";
152
+ "registry:theme": "registry:theme";
153
+ }>;
154
+ content: z.ZodString;
155
+ }, z.core.$strip>>>;
156
+ }, z.core.$strip>>;
157
+ };
158
+ //#endregion
159
+ //#region src/config.d.ts
160
+ export declare const CONFIG_FILE = "75neoui.json";
161
+ export declare const DEFAULT_REGISTRY = "https://75neo-ui.pages.dev/r";
162
+ export declare const DEFAULT_ALIASES: Aliases;
163
+ export declare class ConfigError extends Error {
164
+ constructor(message: string);
165
+ }
166
+ export declare const configPath: (cwd: string) => string;
167
+ export declare function readConfig(cwd: string): Promise<Config>;
168
+ export declare function writeConfig(cwd: string, config: Config): Promise<void>;
169
+ export declare function detectFramework(cwd: string): Promise<Framework | null>;
170
+ export declare function detectCssEntry(cwd: string): Promise<string | null>;
171
+ export declare const detectPaths: (cwd: string) => Paths;
172
+ //#endregion
173
+ //#region src/css.d.ts
174
+ export declare function serialize(css: CssBlock): string;
175
+ export declare function withImport(source: string, specifier: string): string;
176
+ export declare function withTheme(source: string, css: CssBlock, marker: string): string;
177
+ //#endregion
178
+ //#region src/install.d.ts
179
+ export declare class InstallError extends Error {
180
+ constructor(message: string);
181
+ }
182
+ export declare function destinationFor(file: Pick<RegistryFile, "path">, config: Config): string;
183
+ export declare const rewriteImports: (content: string, config: Config) => string;
184
+ interface WriteOptions {
185
+ readonly cwd: string;
186
+ readonly overwrite: boolean;
187
+ }
188
+ interface WriteResult {
189
+ readonly written: string[];
190
+ readonly skipped: string[];
191
+ }
192
+ export declare function writeFiles(items: readonly RegistryItem[], config: Config, options: WriteOptions): Promise<WriteResult>;
193
+ //#endregion
194
+ //#region src/pm.d.ts
195
+ export declare const PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
196
+ type PackageManager = (typeof PACKAGE_MANAGERS)[number];
197
+ export declare function detectPackageManager(cwd: string): PackageManager;
198
+ interface InstallOptions {
199
+ readonly cwd: string;
200
+ readonly manager: PackageManager;
201
+ readonly dev?: boolean;
202
+ }
203
+ export declare function install(packages: readonly string[], options: InstallOptions): Promise<void>;
204
+ //#endregion
205
+ //#region src/registry.d.ts
206
+ export declare class RegistryError extends Error {
207
+ constructor(message: string);
208
+ }
209
+ export declare const stripNamespace: (name: string) => string;
210
+ export declare function fetchIndex(registry: string, framework: Framework): Promise<RegistryIndex>;
211
+ export declare function fetchItem(registry: string, framework: Framework, name: string): Promise<RegistryItem>;
212
+ export declare function resolveItems(registry: string, framework: Framework, names: readonly string[]): Promise<RegistryItem[]>;
213
+ export declare const collectDependencies: (items: readonly RegistryItem[]) => string[];
214
+ //#endregion
215
+ export type { Aliases, Config, CssBlock, CssValue, Framework, InstallOptions, ItemType, PackageManager, Paths, RegistryFile, RegistryIndex, RegistryIndexEntry, RegistryItem, WriteOptions, WriteResult };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { A as configSchema, B as registryItemSchema, C as detectFramework, D as FRAMEWORKS, E as writeConfig, F as parseJson, I as registryFileEntrySchema, L as registryFileSchema, M as frameworkSchema, N as itemTypeSchema, O as ITEM_TYPES, P as jsonSchemas, R as registryIndexEntrySchema, S as detectCssEntry, T as readConfig, V as validate, _ as CONFIG_FILE, a as resolveItems, b as DEFAULT_REGISTRY, c as detectPackageManager, d as destinationFor, f as rewriteImports, g as withTheme, h as withImport, i as fetchItem, j as cssBlockSchema, k as ValidationError, l as install, m as serialize, n as collectDependencies, o as stripNamespace, p as writeFiles, r as fetchIndex, s as PACKAGE_MANAGERS, t as RegistryError, u as InstallError, v as ConfigError, w as detectPaths, x as configPath, y as DEFAULT_ALIASES, z as registryIndexSchema } from "./registry-C1lAzYLJ.mjs";
2
+ export { CONFIG_FILE, ConfigError, DEFAULT_ALIASES, DEFAULT_REGISTRY, FRAMEWORKS, ITEM_TYPES, InstallError, PACKAGE_MANAGERS, RegistryError, ValidationError, collectDependencies, configPath, configSchema, cssBlockSchema, destinationFor, detectCssEntry, detectFramework, detectPackageManager, detectPaths, fetchIndex, fetchItem, frameworkSchema, install, itemTypeSchema, jsonSchemas, parseJson, readConfig, registryFileEntrySchema, registryFileSchema, registryIndexEntrySchema, registryIndexSchema, registryItemSchema, resolveItems, rewriteImports, serialize, stripNamespace, validate, withImport, withTheme, writeConfig, writeFiles };
@@ -0,0 +1,335 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { z } from "zod";
5
+ import { spawn } from "node:child_process";
6
+ //#region src/schema.ts
7
+ const FRAMEWORKS = ["react", "vue"];
8
+ const ITEM_TYPES = [
9
+ "registry:ui",
10
+ "registry:lib",
11
+ "registry:theme"
12
+ ];
13
+ const frameworkSchema = z.enum(FRAMEWORKS);
14
+ const itemTypeSchema = z.enum(ITEM_TYPES);
15
+ const cssValueSchema = z.lazy(() => z.union([z.string(), z.record(z.string(), cssValueSchema)]));
16
+ const cssBlockSchema = z.record(z.string(), cssValueSchema);
17
+ const registryFilePathSchema = z.string().regex(/^registry\/(?:(?:react|vue)\/ui\/[^/]+|shared\/lib)\/[^/]+$/, { message: "must be registry/<framework>/ui/<item>/<file> or registry/shared/lib/<file>, so the installer knows where to put it" });
18
+ const registryFileSchema = z.object({
19
+ path: registryFilePathSchema,
20
+ type: itemTypeSchema,
21
+ content: z.string()
22
+ });
23
+ const registryFileEntrySchema = registryFileSchema.omit({ content: true });
24
+ const registryItemSchema = z.object({
25
+ name: z.string().min(1),
26
+ type: itemTypeSchema,
27
+ title: z.string().optional(),
28
+ description: z.string().optional(),
29
+ dependencies: z.array(z.string()).optional(),
30
+ registryDependencies: z.array(z.string()).optional(),
31
+ css: cssBlockSchema.optional(),
32
+ docs: z.string().optional(),
33
+ files: z.array(registryFileSchema).optional()
34
+ }).refine((item) => item.type === "registry:theme" || (item.files?.length ?? 0) > 0, {
35
+ message: "only a registry:theme item may ship without files",
36
+ path: ["files"]
37
+ }).refine((item) => item.type !== "registry:ui" || item.css === void 0, {
38
+ message: "component CSS belongs in @75neo/ui, not in a registry item",
39
+ path: ["css"]
40
+ });
41
+ const registryIndexEntrySchema = z.object({
42
+ name: z.string().min(1),
43
+ type: itemTypeSchema,
44
+ title: z.string().optional(),
45
+ description: z.string().optional()
46
+ });
47
+ const registryIndexSchema = z.object({
48
+ name: z.string().min(1),
49
+ homepage: z.string().optional(),
50
+ items: z.array(registryIndexEntrySchema)
51
+ });
52
+ const pathsSchema = z.object({
53
+ ui: z.string().min(1),
54
+ lib: z.string().min(1)
55
+ });
56
+ const configSchema = z.object({
57
+ framework: frameworkSchema,
58
+ css: z.string().min(1),
59
+ registry: z.string().min(1),
60
+ paths: pathsSchema,
61
+ aliases: pathsSchema
62
+ });
63
+ var ValidationError = class extends Error {
64
+ source;
65
+ issues;
66
+ constructor(source, issues) {
67
+ const detail = issues.map((issue) => {
68
+ const location = issue.path.map(String).join(".");
69
+ return location === "" ? issue.message : `${location}: ${issue.message}`;
70
+ }).join("\n ");
71
+ super(`${source} is not valid:\n ${detail}`);
72
+ this.source = source;
73
+ this.issues = issues;
74
+ this.name = "ValidationError";
75
+ }
76
+ };
77
+ function validate(schema, source, value) {
78
+ const result = schema.safeParse(value);
79
+ if (!result.success) throw new ValidationError(source, result.error.issues);
80
+ return result.data;
81
+ }
82
+ function parseJson(source, text) {
83
+ try {
84
+ return JSON.parse(text);
85
+ } catch (error) {
86
+ throw new Error(`${source} is not valid JSON: ${error.message}`);
87
+ }
88
+ }
89
+ const jsonSchemas = () => ({
90
+ "registry.json": z.toJSONSchema(registryIndexSchema, { io: "input" }),
91
+ "registry-item.json": z.toJSONSchema(registryItemSchema, { io: "input" })
92
+ });
93
+ //#endregion
94
+ //#region src/config.ts
95
+ const CONFIG_FILE = "75neoui.json";
96
+ const DEFAULT_REGISTRY = "https://75neo-ui.pages.dev/r";
97
+ const DEFAULT_ALIASES = {
98
+ ui: "@/components/ui",
99
+ lib: "@/lib"
100
+ };
101
+ const SRC_PATHS = {
102
+ ui: "src/components/ui",
103
+ lib: "src/lib"
104
+ };
105
+ const ROOT_PATHS = {
106
+ ui: "components/ui",
107
+ lib: "lib"
108
+ };
109
+ const CSS_CANDIDATES = [
110
+ "src/styles/global.css",
111
+ "src/styles/globals.css",
112
+ "src/app.css",
113
+ "src/index.css",
114
+ "src/style.css",
115
+ "src/assets/css/main.css",
116
+ "app/globals.css",
117
+ "styles/globals.css"
118
+ ];
119
+ var ConfigError = class extends Error {
120
+ constructor(message) {
121
+ super(message);
122
+ this.name = "ConfigError";
123
+ }
124
+ };
125
+ const configPath = (cwd) => path.join(cwd, CONFIG_FILE);
126
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
127
+ async function readConfig(cwd) {
128
+ const file = configPath(cwd);
129
+ if (!existsSync(file)) throw new ConfigError(`No ${CONFIG_FILE} found in ${cwd}. Run \`75neoui init\` first.`);
130
+ const document = parseJson(CONFIG_FILE, await readFile(file, "utf8"));
131
+ const withDefaults = isObject(document) ? {
132
+ registry: DEFAULT_REGISTRY,
133
+ paths: SRC_PATHS,
134
+ aliases: DEFAULT_ALIASES,
135
+ ...document
136
+ } : document;
137
+ return validate(configSchema, CONFIG_FILE, withDefaults);
138
+ }
139
+ async function writeConfig(cwd, config) {
140
+ await writeFile(configPath(cwd), `${JSON.stringify(config, null, 2)}\n`);
141
+ }
142
+ async function detectFramework(cwd) {
143
+ const file = path.join(cwd, "package.json");
144
+ if (!existsSync(file)) return null;
145
+ let document;
146
+ try {
147
+ document = JSON.parse(await readFile(file, "utf8"));
148
+ } catch {
149
+ return null;
150
+ }
151
+ if (!isObject(document)) return null;
152
+ const dependencies = isObject(document["dependencies"]) ? document["dependencies"] : {};
153
+ const devDependencies = isObject(document["devDependencies"]) ? document["devDependencies"] : {};
154
+ const all = {
155
+ ...dependencies,
156
+ ...devDependencies
157
+ };
158
+ if ("vue" in all) return "vue";
159
+ if ("react" in all) return "react";
160
+ return null;
161
+ }
162
+ async function detectCssEntry(cwd) {
163
+ for (const candidate of CSS_CANDIDATES) {
164
+ const file = path.join(cwd, candidate);
165
+ if (!existsSync(file)) continue;
166
+ const source = await readFile(file, "utf8");
167
+ if (source.includes("@import \"tailwindcss\"") || source.includes("@import 'tailwindcss'")) return candidate;
168
+ }
169
+ return null;
170
+ }
171
+ const detectPaths = (cwd) => existsSync(path.join(cwd, "src")) ? SRC_PATHS : ROOT_PATHS;
172
+ //#endregion
173
+ //#region src/css.ts
174
+ function rule(selector, body, indent) {
175
+ const lines = Object.entries(body).flatMap(([key, value]) => typeof value === "string" ? [`${indent} ${key}: ${value};`] : rule(key, value, `${indent} `));
176
+ return [
177
+ `${indent}${selector} {`,
178
+ ...lines,
179
+ `${indent}}`
180
+ ];
181
+ }
182
+ function serialize(css) {
183
+ const sections = Object.entries(css).map(([selector, body]) => {
184
+ if (typeof body === "string") return `${selector}: ${body};`;
185
+ return Object.keys(body).length === 0 ? `${selector};` : rule(selector, body, "").join("\n");
186
+ });
187
+ return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
188
+ }
189
+ function withImport(source, specifier) {
190
+ if (source.includes(`"${specifier}"`) || source.includes(`'${specifier}'`)) return source;
191
+ const lines = source.split("\n");
192
+ const last = lines.reduce((index, line, position) => line.trimStart().startsWith("@import ") ? position : index, -1);
193
+ if (last === -1) return `@import "${specifier}";\n\n${source}`;
194
+ lines.splice(last + 1, 0, `@import "${specifier}";`);
195
+ return lines.join("\n");
196
+ }
197
+ function withTheme(source, css, marker) {
198
+ if (source.includes(marker)) return source;
199
+ return `${source.replace(/\s*$/, "\n")}\n${marker}\n${serialize(css)}`;
200
+ }
201
+ //#endregion
202
+ //#region src/install.ts
203
+ const UI_PREFIX = /^registry\/(?:react|vue)\/ui\//;
204
+ const LIB_PREFIX = /^registry\/shared\/lib\//;
205
+ var InstallError = class extends Error {
206
+ constructor(message) {
207
+ super(message);
208
+ this.name = "InstallError";
209
+ }
210
+ };
211
+ function destinationFor(file, config) {
212
+ if (UI_PREFIX.test(file.path)) return path.join(config.paths.ui, file.path.replace(UI_PREFIX, ""));
213
+ if (LIB_PREFIX.test(file.path)) return path.join(config.paths.lib, file.path.replace(LIB_PREFIX, ""));
214
+ throw new InstallError(`Cannot place ${file.path}; it is outside the known registry layout.`);
215
+ }
216
+ const rewriteImports = (content, config) => content.replaceAll("@/registry/react/ui/", `${config.aliases.ui}/`).replaceAll("@/registry/vue/ui/", `${config.aliases.ui}/`).replaceAll("@/registry/shared/lib/", `${config.aliases.lib}/`);
217
+ async function writeFiles(items, config, options) {
218
+ const written = [];
219
+ const skipped = [];
220
+ for (const item of items) for (const file of item.files ?? []) {
221
+ const relative = destinationFor(file, config);
222
+ const absolute = path.join(options.cwd, relative);
223
+ if (existsSync(absolute) && !options.overwrite) {
224
+ skipped.push(relative);
225
+ continue;
226
+ }
227
+ await mkdir(path.dirname(absolute), { recursive: true });
228
+ await writeFile(absolute, rewriteImports(file.content, config));
229
+ written.push(relative);
230
+ }
231
+ return {
232
+ written,
233
+ skipped
234
+ };
235
+ }
236
+ //#endregion
237
+ //#region src/pm.ts
238
+ const PACKAGE_MANAGERS = [
239
+ "npm",
240
+ "pnpm",
241
+ "yarn",
242
+ "bun"
243
+ ];
244
+ const LOCKFILES = [
245
+ ["pnpm-lock.yaml", "pnpm"],
246
+ ["bun.lock", "bun"],
247
+ ["bun.lockb", "bun"],
248
+ ["yarn.lock", "yarn"],
249
+ ["package-lock.json", "npm"]
250
+ ];
251
+ const ADD = {
252
+ npm: ["install"],
253
+ pnpm: ["add"],
254
+ yarn: ["add"],
255
+ bun: ["add"]
256
+ };
257
+ function detectPackageManager(cwd) {
258
+ let directory = path.resolve(cwd);
259
+ for (;;) {
260
+ for (const [lockfile, manager] of LOCKFILES) if (existsSync(path.join(directory, lockfile))) return manager;
261
+ const parent = path.dirname(directory);
262
+ if (parent === directory) return "npm";
263
+ directory = parent;
264
+ }
265
+ }
266
+ function install(packages, options) {
267
+ if (packages.length === 0) return Promise.resolve();
268
+ const args = [
269
+ ...ADD[options.manager],
270
+ ...options.dev === true ? ["-D"] : [],
271
+ ...packages
272
+ ];
273
+ return new Promise((resolve, reject) => {
274
+ const child = spawn(options.manager, args, {
275
+ cwd: options.cwd,
276
+ stdio: "inherit",
277
+ shell: process.platform === "win32"
278
+ });
279
+ child.on("error", reject);
280
+ child.on("close", (code) => {
281
+ if (code === 0) resolve();
282
+ else reject(/* @__PURE__ */ new Error(`${options.manager} ${args.join(" ")} exited with code ${String(code)}.`));
283
+ });
284
+ });
285
+ }
286
+ //#endregion
287
+ //#region src/registry.ts
288
+ const NAMESPACE = /^@75neo\//;
289
+ var RegistryError = class extends Error {
290
+ constructor(message) {
291
+ super(message);
292
+ this.name = "RegistryError";
293
+ }
294
+ };
295
+ const stripNamespace = (name) => name.replace(NAMESPACE, "");
296
+ const isRemote = (registry) => /^https?:\/\//.test(registry);
297
+ async function read(registry, framework, file) {
298
+ if (isRemote(registry)) {
299
+ const url = `${registry.replace(/\/$/, "")}/${framework}/${file}`;
300
+ const response = await fetch(url);
301
+ if (!response.ok) throw new RegistryError(`${url} returned ${String(response.status)} ${response.statusText}.`);
302
+ return [url, parseJson(url, await response.text())];
303
+ }
304
+ const location = path.resolve(registry, framework, file);
305
+ if (!existsSync(location)) throw new RegistryError(`${location} does not exist.`);
306
+ return [location, parseJson(location, await readFile(location, "utf8"))];
307
+ }
308
+ async function fetchIndex(registry, framework) {
309
+ const [source, document] = await read(registry, framework, "registry.json");
310
+ return validate(registryIndexSchema, source, document);
311
+ }
312
+ async function fetchItem(registry, framework, name) {
313
+ const [source, document] = await read(registry, framework, `${stripNamespace(name)}.json`);
314
+ return validate(registryItemSchema, source, document);
315
+ }
316
+ async function resolveItems(registry, framework, names) {
317
+ const resolved = /* @__PURE__ */ new Map();
318
+ const queue = names.map(stripNamespace);
319
+ while (queue.length > 0) {
320
+ const name = queue.shift();
321
+ if (name === void 0 || resolved.has(name)) continue;
322
+ let item;
323
+ try {
324
+ item = await fetchItem(registry, framework, name);
325
+ } catch (error) {
326
+ throw new RegistryError(`Could not resolve "${name}". ${error.message}`);
327
+ }
328
+ resolved.set(name, item);
329
+ for (const dependency of item.registryDependencies ?? []) queue.push(stripNamespace(dependency));
330
+ }
331
+ return [...resolved.values()];
332
+ }
333
+ const collectDependencies = (items) => [...new Set(items.flatMap((item) => [...item.dependencies ?? []]))].sort();
334
+ //#endregion
335
+ export { configSchema as A, registryItemSchema as B, detectFramework as C, FRAMEWORKS as D, writeConfig as E, parseJson as F, registryFileEntrySchema as I, registryFileSchema as L, frameworkSchema as M, itemTypeSchema as N, ITEM_TYPES as O, jsonSchemas as P, registryIndexEntrySchema as R, detectCssEntry as S, readConfig as T, validate as V, CONFIG_FILE as _, resolveItems as a, DEFAULT_REGISTRY as b, detectPackageManager as c, destinationFor as d, rewriteImports as f, withTheme as g, withImport as h, fetchItem as i, cssBlockSchema as j, ValidationError as k, install as l, serialize as m, collectDependencies as n, stripNamespace as o, writeFiles as p, fetchIndex as r, PACKAGE_MANAGERS as s, RegistryError as t, InstallError as u, ConfigError as v, detectPaths as w, configPath as x, DEFAULT_ALIASES as y, registryIndexSchema as z };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@75neo/ui",
3
+ "version": "1.0.0",
4
+ "description": "The 75NeoUI installer and animation stylesheet",
5
+ "keywords": [
6
+ "75neoui",
7
+ "react",
8
+ "registry",
9
+ "tailwindcss",
10
+ "vue"
11
+ ],
12
+ "homepage": "https://75neo-ui.pages.dev",
13
+ "license": "MIT",
14
+ "author": "Vo Quang Chien <2giosangmitom@gmail.com>",
15
+ "contributors": [
16
+ "Vo Van Duy <nstcrystal@gmail.com>"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/75Neo/ui.git",
21
+ "directory": "packages/ui"
22
+ },
23
+ "bin": {
24
+ "75neoui": "./dist/cli.mjs"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "animations.css"
29
+ ],
30
+ "type": "module",
31
+ "types": "./dist/index.d.mts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.mts",
35
+ "default": "./dist/index.mjs"
36
+ },
37
+ "./animations.css": "./animations.css",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "yargs": "^18.1.0",
45
+ "zod": "^4.5.4"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^26.4.1",
49
+ "@types/yargs": "^17.0.35",
50
+ "tsdown": "^0.23.0",
51
+ "typescript": "~6.0.3",
52
+ "vitest": "^5.0.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=20"
56
+ },
57
+ "scripts": {
58
+ "build": "tsdown",
59
+ "dev": "tsdown --watch",
60
+ "typecheck": "tsc -p tsconfig.test.json",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest"
63
+ }
64
+ }