@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 +21 -0
- package/README.md +85 -0
- package/animations.css +90 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +153 -0
- package/dist/index.d.mts +215 -0
- package/dist/index.mjs +2 -0
- package/dist/registry-C1lAzYLJ.mjs +335 -0
- package/package.json +64 -0
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 {};
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|