ply-ui-cli 1.1.118

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.
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/commands/init.ts
31
+ var init_exports = {};
32
+ __export(init_exports, {
33
+ init: () => init
34
+ });
35
+ module.exports = __toCommonJS(init_exports);
36
+ var import_fs2 = __toESM(require("fs"));
37
+ var import_path2 = __toESM(require("path"));
38
+ var import_vendor = require("../vendor");
39
+ var import_registry = require("../registry");
40
+
41
+ // src/lockfile.ts
42
+ var import_fs = __toESM(require("fs"));
43
+ var import_path = __toESM(require("path"));
44
+ var LOCKFILE_NAME = "base-ui-lock.json";
45
+ var LOCKFILE_VERSION = 1;
46
+ function lockfilePath() {
47
+ return import_path.default.resolve(process.cwd(), LOCKFILE_NAME);
48
+ }
49
+ function readLockFile() {
50
+ const p = lockfilePath();
51
+ if (!import_fs.default.existsSync(p)) {
52
+ return { version: LOCKFILE_VERSION, components: {} };
53
+ }
54
+ try {
55
+ const parsed = JSON.parse(import_fs.default.readFileSync(p, "utf8"));
56
+ if (parsed && typeof parsed === "object" && parsed.components) {
57
+ return { version: parsed.version ?? LOCKFILE_VERSION, components: parsed.components };
58
+ }
59
+ } catch {
60
+ }
61
+ return { version: LOCKFILE_VERSION, components: {} };
62
+ }
63
+ function writeLockFile(lock) {
64
+ import_fs.default.writeFileSync(lockfilePath(), JSON.stringify(lock, null, 2) + "\n", "utf8");
65
+ }
66
+
67
+ // src/templates/editor-kit.generated.ts
68
+ var CURSOR_RULE = '---\ndescription: Ply \u2014 standalone, zoneless, Tailwind v4 copy-in components\nalwaysApply: true\n---\n\n# Ply coding rules\n\nYou are generating Angular for a project that uses **Ply** at https://ply-ui.com \u2014 copy-in components, not an npm UI kit. Not MUI Base UI (React).\n\n## Install, don\'t invent\n\n- Install with `npx ply-ui-cli add <name> --yes`. Source lands in `src/app/components/<name>/`. Humans can run `npx ply-ui-cli add` with no name to pick from the catalog. Never use `add --yes` without a name.\n- Import from that local path. Never `from \'base-ui\'` / `@base-ui/*`.\n- Prefer an existing registry item over a custom control. Check `npx ply-ui-cli list`, https://ply-ui.com/llms.txt, or MCP (`npx -y ply-ui-mcp`) before building a dialog, table, combobox, or form control.\n- If components look unstyled or icons are missing, run `npx ply-ui-cli doctor` (read-only). It checks `base-ui.json`, Angular CDK, Tailwind v4 `@source` paths, `base-ui.css`, and icon sprites.\n- Free items need no license. Pro items need `PLY_LICENSE_KEY` \u2014 do not scrape keys or paste Pro source from memory.\n\n## Angular 22 shape\n\n- New components: `standalone: true`, `changeDetection: ChangeDetectionStrategy.OnPush`.\n- Public APIs: `input()`, `output()`, `model()` \u2014 not `@Input()` / `@Output()`.\n- Templates: `@if` / `@for` / `@empty` \u2014 not `*ngIf` / `*ngFor`.\n- Prefer zoneless. Do not add `zone.js`. After async work, write to signals (or signal-backed CVA) so the view updates without Zone.\n- App selectors stay `app-`. Only Ply selectors use the `ply-` prefix.\n\n## Selectors and forms\n\n- Components: `<ply-card>`, `<ply-dialog>` is not a tag \u2014 open modals with `inject(DialogService).open(MyComponent)`.\n- Directives: `[ply-button]`, `[ply-stroked-button]`, `[ply-input]`, `[ply-textarea]`.\n- Native fields: `<input ply-input [(ngModel)]="\u2026">` or Angular 22 `[formField]` + `ply-input`.\n- `data-table` cells: project `<ng-template plyTableCell="columnKey" let-row let-value="value">`. Combobox options: `plyComboboxOption`. Custom select: `plySelectOption`. Do not fork those components to render a badge or a row action.\n- Chrome strings (empty states, Close, paginator): `provideBaseUiI18n({ close: \'Fermer\' })` in `app.config.ts`. Per-instance `emptyMessage` / `emptyText` still override. Not ngx-translate.\n- Icon sprites: `provideBaseUI({ iconPath: \'assets/icons.svg\' })` in `app.config.ts`. Per-instance `[path]` / `[filledPath]` / `[size]` still win. `w-*`/`h-*` classes keep class-based sizing.\n- Tests: use the copied `*.harness.ts` for data-table, custom-select, and dialog. `TestbedHarnessEnvironment.loader(fixture)`; dialogs need `documentRootLoader` (overlay on `document.body`). Do not invent query helpers for those three.\n- CVA hosts (`ply-combobox`, `ply-tags-input`, `ply-toggle`, `ply-custom-select`, `ply-file-upload`, `ply-mention-input`, `ply-password-input`) bind `[(ngModel)]` / `[formField]` on the host, not on an inner native input.\n- Do not nest composite controls inside `ply-input-group` \u2014 only `[ply-input]`, `[ply-textarea]`, and `ply-mention-input` are projected. Use `ply-password-input` for password fields (it already includes a group).\n- Confirm destructive actions with `inject(DialogService).confirm({ title, destructive: true })` \u2014 do not copy a one-off confirm dialog.\n\n## Tailwind v4\n\n- Style with utilities. Pair light and `dark:` variants (`bg-white dark:bg-slate-800`). Dark mode is the `dark` class on `<html>`.\n- Prefer pixel arbitrary values in new UI (`p-[16px]`, `gap-[8px]`) so spacing does not depend on a custom theme.\n- `color="primary"` reads `--base-primary` from `base-ui.css`. Theme Studio Copy CSS writes the same tokens. Unmigrated widgets still follow `--color-blue-*`.\n- Button and input `class` values merge via `cn()`. `class="rounded-full"` and `class="absolute \u2026"` override defaults. Existing `utility!` still works.\n- `<ply-icon>` outlines: `stroke-*`. Filled icons: `[filled]="true"` and `text-*` / `fill-*`.\n\n## Async, overlays, SSR\n\n- `DialogService.open()` is a no-op during SSR. `DialogService.confirm()` emits `false` on the server. Toast and dialog hosts append via `DOCUMENT`.\n- Drawers need `provideAnimationsAsync()`.\n- Close popovers/drawers yourself on in-panel navigation (`popover.close()`, `drawer.closed.emit()`).\n- Overlay menus (dropdown, context) use `a11y-keyboard` (roving tabindex, Home/End, typeahead). Combobox, custom-select, and command-palette use `aria-activedescendant` \u2014 do not switch those to the menu helper.\n- Do not put `ply-toast-container` in templates; the toast service owns the overlay.\n\n## Agents\n\n- MCP optional: `.cursor/mcp.json` \u2192 `npx -y ply-ui-mcp` with `PLY_CWD` = this app root.\n- Facts: https://ply-ui.com/llms.txt \xB7 catalog: https://ply-ui.com/docs/ai/components.md\n- Usage snippets (VS Code / Cursor): `.vscode/base-ui.code-snippets` \u2014 expand prefixes like `ply-card` after the CLI has copied the component in.\n';
69
+ var CODE_SNIPPETS = '{\n "Ply table": {\n "prefix": "ply-table",\n "scope": "html",\n "body": [\n "<div class=\\"relative w-full overflow-auto\\">",\n " <table ply-table>",\n " <caption ply-table-caption>${1:A list of recent invoices.}</caption>",\n " <thead ply-table-header>",\n " <tr ply-table-row>",\n " <th ply-table-head>${2:Invoice}</th>",\n " <th ply-table-head>${3:Status}</th>",\n " </tr>",\n " </thead>",\n " <tbody ply-table-body>",\n " <tr ply-table-row>",\n " <td ply-table-cell>$0</td>",\n " <td ply-table-cell></td>",\n " </tr>",\n " </tbody>",\n " </table>",\n "</div>"\n ],\n "description": "Ply free HTML table. Install: npx ply-ui-cli add table --yes"\n },\n "Ply card": {\n "prefix": "ply-card",\n "scope": "html",\n "body": [\n "<ply-card>",\n " <ply-card-header class=\\"flex items-center justify-between gap-[8px]\\">",\n " <h2 class=\\"m-0! text-base font-semibold text-slate-900 dark:text-white\\">${1:Title}</h2>",\n " </ply-card-header>",\n " <ply-card-body class=\\"p-[16px]\\">",\n " $0",\n " </ply-card-body>",\n "</ply-card>"\n ],\n "description": "Ply card usage. Install: npx ply-ui-cli add card --yes"\n },\n "Ply button": {\n "prefix": "ply-button",\n "scope": "html",\n "body": [\n "<button type=\\"button\\" ply-button color=\\"${1:primary}\\">${2:Save}</button>"\n ],\n "description": "Ply filled button directive. Install: npx ply-ui-cli add button --yes"\n },\n "Ply stroked button": {\n "prefix": "ply-stroked-button",\n "scope": "html",\n "body": [\n "<button type=\\"button\\" ply-stroked-button color=\\"${1:default}\\">${2:Cancel}</button>"\n ],\n "description": "Ply stroked button directive. Install: npx ply-ui-cli add button --yes"\n },\n "Ply dialog open": {\n "prefix": "ply-dialog",\n "scope": "typescript",\n "body": [\n "this.dialog.open(${1:MyDialogComponent}).subscribe((result) => {",\n " if (!result) return;",\n " $0",\n "});"\n ],\n "description": "Open a Base UI dialog. Install: npx ply-ui-cli add dialog --yes"\n },\n "Ply dialog confirm": {\n "prefix": "ply-confirm",\n "scope": "typescript",\n "body": [\n "this.dialog.confirm({",\n " title: \'${1:Delete this item?}\',",\n " description: \'${2:This cannot be undone.}\',",\n " destructive: true",\n "}).subscribe((ok) => {",\n " if (!ok) return;",\n " $0",\n "});"\n ],\n "description": "Confirm dialog. Install: npx ply-ui-cli add dialog --yes"\n },\n "Ply password input": {\n "prefix": "ply-password",\n "scope": "html",\n "body": [\n "<ply-password-input label=\\"${1:Password}\\" [(ngModel)]=\\"${2:password}\\" [showStrength]=\\"true\\">",\n " <ply-error>${3:Password is required}</ply-error>",\n "</ply-password-input>"\n ],\n "description": "Password field with show/hide. Install: npx ply-ui-cli add password-input --yes"\n },\n "Ply input group": {\n "prefix": "ply-input",\n "scope": "html",\n "body": [\n "<ply-input-group>",\n " <ply-label>${1:Label}</ply-label>",\n " <input ply-input [(ngModel)]=\\"${2:value}\\" autocomplete=\\"off\\" />",\n "</ply-input-group>"\n ],\n "description": "Native input with ply-input. Install: npx ply-ui-cli add input --yes"\n },\n "Ply textarea": {\n "prefix": "ply-textarea",\n "scope": "html",\n "body": [\n "<ply-input-group>",\n " <ply-label>${1:Notes}</ply-label>",\n " <textarea ply-textarea rows=\\"4\\" [(ngModel)]=\\"${2:notes}\\"></textarea>",\n "</ply-input-group>"\n ],\n "description": "Native textarea with ply-textarea. Install: npx ply-ui-cli add input --yes"\n },\n "Ply combobox": {\n "prefix": "ply-combobox",\n "scope": "html",\n "body": [\n "<ply-combobox",\n " label=\\"${1:Choice}\\"",\n " [options]=\\"${2:options}\\"",\n " [(ngModel)]=\\"${3:value}\\">",\n " <ng-template plyComboboxOption let-option>",\n " $0",\n " </ng-template>",\n "</ply-combobox>"\n ],\n "description": "Searchable select with optional option template. Bind ngModel on the host. Install: npx ply-ui-cli add combobox --yes"\n },\n "Ply select": {\n "prefix": "ply-select",\n "scope": "html",\n "body": [\n "<ply-custom-select",\n " label=\\"${1:Choice}\\"",\n " [options]=\\"${2:options}\\"",\n " [(ngModel)]=\\"${3:value}\\">",\n " <ng-template plySelectOption let-label=\\"label\\">",\n " $0",\n " </ng-template>",\n "</ply-custom-select>"\n ],\n "description": "Custom select CVA host with optional option template. Install: npx ply-ui-cli add custom-select --yes"\n },\n "Ply toggle": {\n "prefix": "ply-toggle",\n "scope": "html",\n "body": [\n "<div class=\\"flex items-center justify-between gap-[12px] rounded-lg border border-slate-200 px-[16px] py-[12px] dark:border-slate-700\\">",\n " <div>",\n " <p class=\\"m-0! text-sm font-medium text-slate-900 dark:text-white\\">${1:Product emails}</p>",\n " <p class=\\"mb-0! mt-[2px] text-xs text-slate-500\\">${2:Occasional tips.}</p>",\n " </div>",\n " <ply-toggle [(ngModel)]=\\"${3:enabled}\\"></ply-toggle>",\n "</div>"\n ],\n "description": "Toggle CVA host. Install: npx ply-ui-cli add toggle --yes"\n },\n "Ply tags input": {\n "prefix": "ply-tags-input",\n "scope": "html",\n "body": [\n "<ply-label>${1:Tags}</ply-label>",\n "<ply-tags-input class=\\"mt-[4px]\\" [(ngModel)]=\\"${2:tags}\\" placeholder=\\"${3:Add a tag\u2026}\\"></ply-tags-input>"\n ],\n "description": "Chip tags CVA host. Install: npx ply-ui-cli add tags-input --yes"\n },\n "Ply checkbox": {\n "prefix": "ply-checkbox",\n "scope": "html",\n "body": [\n "<ply-checkbox [(ngModel)]=\\"${1:checked}\\">${2:Remember me}</ply-checkbox>"\n ],\n "description": "Checkbox. Install: npx ply-ui-cli add checkbox --yes"\n },\n "Ply chat": {\n "prefix": "ply-chat",\n "scope": "html",\n "body": [\n "<ply-chat",\n " [messages]=\\"${1:messages}()\\"",\n " [streaming]=\\"${2:streaming}()\\"",\n " placeholder=\\"${3:Ask\u2026}\\"",\n " (send)=\\"${4:onSend}($event)\\"",\n " (stop)=\\"${5:stop}()\\">",\n "</ply-chat>"\n ],\n "description": "AI chat kit. Install: npx ply-ui-cli add chat --yes"\n },\n "Ply data table": {\n "prefix": "ply-data-table",\n "scope": "html",\n "body": [\n "<ply-data-table",\n " [columns]=\\"${1:columns}\\"",\n " [data]=\\"${2:rows}\\"",\n " [loading]=\\"${3:loading}()\\"",\n " serverSide",\n " sortable",\n " pageable",\n " selectable",\n " bordered",\n " hoverable",\n " [pageSize]=\\"${4:10}\\"",\n " [totalItems]=\\"${5:total}\\"",\n " [(selected)]=\\"${6:selected}\\"",\n " (pageChange)=\\"${7:fetch}($event)\\"",\n " (sortChange)=\\"${8:onSort}($event)\\">",\n " <ng-template plyTableCell=\\"${9:status}\\" let-value=\\"value\\">",\n " $0",\n " </ng-template>",\n "</ply-data-table>"\n ],\n "description": "Pro data table with optional cell templates. Install: npx ply-ui-cli add data-table --yes"\n },\n "Ply tabs": {\n "prefix": "ply-tabs",\n "scope": "html",\n "body": [\n "<ply-tabs [defaultTab]=\\"${1:0}\\">",\n " <ply-tab label=\\"${2:Overview}\\"><ply-tab-body>$0</ply-tab-body></ply-tab>",\n " <ply-tab label=\\"${3:Activity}\\"><ply-tab-body></ply-tab-body></ply-tab>",\n "</ply-tabs>"\n ],\n "description": "Tabs. Install: npx ply-ui-cli add tabs --yes"\n },\n "Ply alert": {\n "prefix": "ply-alert",\n "scope": "html",\n "body": [\n "<ply-alert color=\\"${1:info}\\">${2:Saved.}</ply-alert>"\n ],\n "description": "Alert. Install: npx ply-ui-cli add alert --yes"\n },\n "Ply badge": {\n "prefix": "ply-badge",\n "scope": "html",\n "body": [\n "<ply-badge color=\\"${1:primary-light}\\" size=\\"sm\\">${2:New}</ply-badge>"\n ],\n "description": "Badge. Install: npx ply-ui-cli add badge --yes"\n },\n "Ply skeleton": {\n "prefix": "ply-skeleton",\n "scope": "html",\n "body": [\n "<ply-skeleton variant=\\"${1:rectangular}\\" width=\\"${2:100%}\\" height=\\"${3:16rem}\\"></ply-skeleton>"\n ],\n "description": "Skeleton placeholder. Install: npx ply-ui-cli add skeleton --yes"\n },\n "Ply empty state": {\n "prefix": "ply-empty-state",\n "scope": "html",\n "body": [\n "<ply-empty-state title=\\"${1:Nothing here}\\" description=\\"${2:Try a different filter.}\\"></ply-empty-state>"\n ],\n "description": "Empty state. Install: npx ply-ui-cli add empty-state --yes"\n },\n "Ply toast": {\n "prefix": "ply-toast",\n "scope": "typescript",\n "body": [\n "this.toast.success(\'${1:Saved}\', { duration: ${2:2500} });"\n ],\n "description": "Toast via ToastService. Install: npx ply-ui-cli add toast --yes. Do not add ply-toast-container to templates."\n },\n "Ply icon": {\n "prefix": "ply-icon",\n "scope": "html",\n "body": [\n "<ply-icon name=\\"${1:chevron-right}\\" class=\\"h-4 w-4 stroke-slate-500 dark:stroke-slate-300\\"></ply-icon>"\n ],\n "description": "Outline icon (stroke-*). Filled: [filled]=\\"true\\" + text-*. Install: npx ply-ui-cli add icon --yes"\n },\n "Ply standalone component": {\n "prefix": "ply-standalone",\n "scope": "typescript",\n "body": [\n "import { ChangeDetectionStrategy, Component, input, output } from \'@angular/core\';",\n "",\n "@Component({",\n " selector: \'app-${1:feature}\',",\n " changeDetection: ChangeDetectionStrategy.OnPush,",\n " template: `",\n " <ply-card>",\n " <ply-card-body class=\\"p-[16px]\\">$0</ply-card-body>",\n " </ply-card>",\n " `,",\n "})",\n "export class ${2:Feature}Component {",\n " readonly title = input(\'${3:Title}\');",\n " readonly saved = output<void>();",\n "}"\n ],\n "description": "Standalone OnPush component with signal input/output and a Ply card"\n },\n "Ply i18n": {\n "prefix": "ply-i18n",\n "scope": "typescript",\n "body": [\n "provideBaseUiI18n({",\n " close: \'${1:Fermer}\',",\n " noDataAvailable: \'${2:Aucune donn\xE9e}\',",\n " noResults: \'${3:Aucun r\xE9sultat}\'",\n "})"\n ],\n "description": "Chrome-string dictionary for empty states, paginator, and dialog close. Install: npx ply-ui-cli add i18n --yes"\n },\n "Ply config": {\n "prefix": "ply-config",\n "scope": "typescript",\n "body": [\n "provideBaseUI({",\n " iconPath: \'${1:assets/icons.svg}\',",\n " filledIconPath: \'${2:assets/icons-filled.svg}\',",\n " defaultSize: ${3:20}",\n "})"\n ],\n "description": "Library defaults for icon sprites and default icon size. Install: npx ply-ui-cli add config --yes"\n },\n "Ply harness": {\n "prefix": "ply-harness",\n "scope": "typescript",\n "body": [\n "import { TestbedHarnessEnvironment } from \'@angular/cdk/testing/testbed\';",\n "import { DataTableHarness } from \'./components/data-table/data-table.harness\';",\n "import { DialogHarness } from \'./components/dialog/dialog.harness\';",\n "",\n "const loader = TestbedHarnessEnvironment.loader(fixture);",\n "const table = await loader.getHarness(DataTableHarness);",\n "expect(await table.getRowCount()).toBe(${1:3});",\n "",\n "const dialogs = TestbedHarnessEnvironment.documentRootLoader(fixture);",\n "const dialog = await dialogs.getHarness(DialogHarness);"\n ],\n "description": "CDK ComponentHarness for copied Base UI data-table. Dialog overlays use documentRootLoader. Copied with the component \u2014 not a separate registry item."\n }\n}\n';
70
+
71
+ // src/brand.ts
72
+ var CLI_BIN = "ply-ui-cli";
73
+ var CLI_NPX = `npx ${CLI_BIN}`;
74
+ var MCP_PACKAGE = "ply-ui-mcp";
75
+ var MCP_NPX = `npx -y ${MCP_PACKAGE}`;
76
+
77
+ // src/commands/init.ts
78
+ var BASE_CSS = `/* Ply global styles \u2014 ${import_registry.SITE_URL} */
79
+
80
+ /* Overlay positioning for dialogs, dropdowns and tooltips (Angular CDK). */
81
+ @import '@angular/cdk/overlay-prebuilt.css';
82
+
83
+ /*
84
+ * Semantic tokens. Buttons, inputs, and Theme Studio read these.
85
+ * Override in :root (or paste a Theme Studio export) \u2014 do not fork components
86
+ * just to change brand color.
87
+ */
88
+ :root {
89
+ --base-background: #ffffff;
90
+ --base-foreground: #0f172a;
91
+ --base-primary: #2563eb;
92
+ --base-primary-hover: #1d4ed8;
93
+ --base-primary-active: #1e40af;
94
+ --base-primary-foreground: #ffffff;
95
+ --base-muted: #e2e8f0;
96
+ --base-muted-foreground: #334155;
97
+ --base-destructive: #dc2626;
98
+ --base-destructive-hover: #b91c1c;
99
+ --base-destructive-foreground: #ffffff;
100
+ --base-border: #cbd5e1;
101
+ --base-ring: #2563eb;
102
+ --base-radius: 0.5rem;
103
+ --radius: var(--base-radius);
104
+ }
105
+
106
+ .dark {
107
+ --base-background: #0f172a;
108
+ --base-foreground: #f8fafc;
109
+ --base-muted: #1e293b;
110
+ --base-muted-foreground: #e2e8f0;
111
+ --base-border: #334155;
112
+ --base-primary-foreground: #ffffff;
113
+ --base-destructive-foreground: #ffffff;
114
+ }
115
+
116
+ @keyframes fade-in-up {
117
+ from {
118
+ opacity: 0;
119
+ transform: translateY(10px);
120
+ }
121
+
122
+ to {
123
+ opacity: 1;
124
+ transform: translateY(0);
125
+ }
126
+ }
127
+
128
+ .animate-fade-in-up {
129
+ animation: fade-in-up 0.4s ease-out forwards;
130
+ }
131
+
132
+ @keyframes fadeInDown {
133
+ from {
134
+ opacity: 0;
135
+ transform: translateY(-20px);
136
+ }
137
+
138
+ to {
139
+ opacity: 1;
140
+ transform: translateY(0);
141
+ }
142
+ }
143
+
144
+ @keyframes fadeInUp {
145
+ from {
146
+ opacity: 0;
147
+ transform: translateY(20px);
148
+ }
149
+
150
+ to {
151
+ opacity: 1;
152
+ transform: translateY(0);
153
+ }
154
+ }
155
+
156
+ @keyframes shimmer {
157
+ 0% {
158
+ transform: translateX(-100%);
159
+ }
160
+
161
+ 100% {
162
+ transform: translateX(100%);
163
+ }
164
+ }
165
+
166
+ .animate-shimmer {
167
+ animation: shimmer 2s linear infinite;
168
+ }
169
+
170
+ /* Chrome autofill fix for dark mode */
171
+ input:-webkit-autofill,
172
+ input:-webkit-autofill:hover,
173
+ input:-webkit-autofill:focus,
174
+ input:-webkit-autofill:active {
175
+ -webkit-background-clip: text;
176
+ -webkit-text-fill-color: #334155 !important; /* slate-700 */
177
+ transition: background-color 5000s ease-in-out 0s;
178
+ box-shadow: inset 0 0 20px 20px #ffffff;
179
+ }
180
+
181
+ .dark input:-webkit-autofill,
182
+ .dark input:-webkit-autofill:hover,
183
+ .dark input:-webkit-autofill:focus,
184
+ .dark input:-webkit-autofill:active {
185
+ -webkit-text-fill-color: #e2e8f0 !important; /* slate-200 */
186
+ -webkit-box-shadow: 0 0 0px 1000px #1e293b inset !important; /* slate-800 */
187
+ }
188
+
189
+ /* Hide the datalist arrow in Chrome; leave native date/time pickers visible. */
190
+ input[list]::-webkit-calendar-picker-indicator {
191
+ display: none !important;
192
+ }
193
+ `;
194
+ var ICON_SPRITES = ["icons.svg", "icons-filled.svg"];
195
+ function pickAngularProject(angularJson) {
196
+ const projects = angularJson?.projects || {};
197
+ const names = Object.keys(projects);
198
+ if (!names.length) return null;
199
+ const appName = names.find((n) => projects[n].projectType === "application" && projects[n].architect?.build) || names.find((n) => projects[n].architect?.build) || names[0];
200
+ return { name: appName, project: projects[appName] };
201
+ }
202
+ async function downloadSprite(name, targetDir) {
203
+ try {
204
+ const res = await fetch(`${import_registry.SITE_URL}/assets/${name}`);
205
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
206
+ const body = Buffer.from(await res.arrayBuffer());
207
+ import_fs2.default.mkdirSync(targetDir, { recursive: true });
208
+ import_fs2.default.writeFileSync(import_path2.default.join(targetDir, name), body);
209
+ return true;
210
+ } catch (err) {
211
+ console.warn(`
212
+ \u26A0 Could not download ${name} (${err.message}). Download it manually from ${import_registry.SITE_URL}/assets/${name} into ${targetDir}.`);
213
+ return false;
214
+ }
215
+ }
216
+ function writeEditorFile(dest, content, label) {
217
+ if (import_fs2.default.existsSync(dest)) return;
218
+ try {
219
+ import_fs2.default.mkdirSync(import_path2.default.dirname(dest), { recursive: true });
220
+ import_fs2.default.writeFileSync(dest, content, "utf8");
221
+ } catch (err) {
222
+ console.warn(
223
+ `
224
+ \u26A0 Could not write ${label} (${err.message}). Copy it from ${import_registry.SITE_URL}/docs/ai/${import_path2.default.basename(dest)}.`
225
+ );
226
+ }
227
+ }
228
+ var INIT_DEFAULTS = {
229
+ componentsAlias: "src/app/components",
230
+ styles: "src/styles.scss",
231
+ tailwind: "src/tailwind.css"
232
+ };
233
+ async function init(options = {}) {
234
+ const response = options.yes ? { ...INIT_DEFAULTS } : await (0, import_vendor.prompts)([
235
+ {
236
+ type: "text",
237
+ name: "componentsAlias",
238
+ message: "Configure the import alias for components:",
239
+ initial: INIT_DEFAULTS.componentsAlias
240
+ },
241
+ {
242
+ type: "text",
243
+ name: "styles",
244
+ message: "Where is your global CSS file?",
245
+ initial: INIT_DEFAULTS.styles
246
+ },
247
+ {
248
+ type: "text",
249
+ name: "tailwind",
250
+ message: "Path to your Tailwind CSS entry file:",
251
+ initial: INIT_DEFAULTS.tailwind
252
+ }
253
+ ]);
254
+ if (!response.componentsAlias) {
255
+ console.log("Initialization cancelled.");
256
+ return;
257
+ }
258
+ const spinner = (0, import_vendor.ora)("Writing base-ui.json...").start();
259
+ const configPath = import_path2.default.resolve(process.cwd(), "base-ui.json");
260
+ const config = {
261
+ $schema: "https://ply-ui.com/schema.json",
262
+ style: "scss",
263
+ tailwind: {
264
+ config: response.tailwind,
265
+ css: response.styles,
266
+ baseColor: "slate",
267
+ cssVariables: true
268
+ },
269
+ aliases: {
270
+ components: response.componentsAlias,
271
+ utils: "src/app/utils"
272
+ }
273
+ };
274
+ import_fs2.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
275
+ const lockPath = import_path2.default.resolve(process.cwd(), LOCKFILE_NAME);
276
+ if (!import_fs2.default.existsSync(lockPath)) {
277
+ writeLockFile(readLockFile());
278
+ }
279
+ spinner.text = "Writing global styles...";
280
+ const stylesPath = import_path2.default.resolve(process.cwd(), response.styles);
281
+ const stylesDir = import_path2.default.dirname(stylesPath);
282
+ const baseCssPath = import_path2.default.join(stylesDir, "base-ui.css");
283
+ const importLine = `@import './base-ui.css';`;
284
+ try {
285
+ import_fs2.default.mkdirSync(stylesDir, { recursive: true });
286
+ import_fs2.default.writeFileSync(baseCssPath, BASE_CSS, "utf8");
287
+ if (import_fs2.default.existsSync(stylesPath)) {
288
+ const stylesContent = import_fs2.default.readFileSync(stylesPath, "utf8");
289
+ if (!stylesContent.includes(importLine)) {
290
+ import_fs2.default.writeFileSync(stylesPath, `${importLine}
291
+ ${stylesContent}`, "utf8");
292
+ }
293
+ } else {
294
+ import_fs2.default.writeFileSync(stylesPath, `${importLine}
295
+ `, "utf8");
296
+ console.warn(`
297
+ \u26A0 ${response.styles} did not exist \u2014 created it. Make sure it is listed in the "styles" array of angular.json.`);
298
+ }
299
+ } catch (error) {
300
+ console.warn(`
301
+ \u26A0 Could not update global styles (${error.message}). Add "${importLine}" to ${response.styles} manually and create base-ui.css next to it.`);
302
+ }
303
+ spinner.text = "Setting up icon sprites...";
304
+ let assetsDir = import_path2.default.resolve(process.cwd(), "src/assets");
305
+ try {
306
+ const angularJsonPath = import_path2.default.resolve(process.cwd(), "angular.json");
307
+ if (import_fs2.default.existsSync(angularJsonPath)) {
308
+ const angularJson = JSON.parse(import_fs2.default.readFileSync(angularJsonPath, "utf8"));
309
+ const picked = pickAngularProject(angularJson);
310
+ const buildOptions = picked?.project?.architect?.build?.options;
311
+ if (buildOptions) {
312
+ const assets = buildOptions.assets || [];
313
+ const hasSrcAssets = assets.some(
314
+ (a) => a === "src/assets" || typeof a === "object" && a?.input === "src/assets"
315
+ );
316
+ const publicEntry = assets.find(
317
+ (a) => a === "public" || typeof a === "object" && a?.input === "public"
318
+ );
319
+ if (hasSrcAssets) {
320
+ assetsDir = import_path2.default.resolve(process.cwd(), "src/assets");
321
+ } else if (publicEntry) {
322
+ assetsDir = import_path2.default.resolve(process.cwd(), "public/assets");
323
+ } else {
324
+ assetsDir = import_path2.default.resolve(process.cwd(), "src/assets");
325
+ for (const sprite of ICON_SPRITES) {
326
+ const exists = assets.some(
327
+ (a) => typeof a === "object" && a?.glob === sprite && a?.input === "src/assets"
328
+ );
329
+ if (!exists) {
330
+ assets.push({ glob: sprite, input: "src/assets", output: "/assets" });
331
+ }
332
+ }
333
+ buildOptions.assets = assets;
334
+ import_fs2.default.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2), "utf8");
335
+ }
336
+ } else {
337
+ console.warn(`
338
+ \u26A0 Could not find build options in angular.json \u2014 serve the icon sprites at /assets/icons.svg and /assets/icons-filled.svg yourself.`);
339
+ }
340
+ } else {
341
+ console.warn("\n\u26A0 No angular.json found \u2014 is this an Angular workspace? Continuing anyway.");
342
+ }
343
+ } catch (error) {
344
+ console.warn(`
345
+ \u26A0 Could not update angular.json (${error.message}). Make sure the icon sprites are served at /assets/icons.svg and /assets/icons-filled.svg.`);
346
+ }
347
+ spinner.text = "Downloading icon sprites...";
348
+ for (const sprite of ICON_SPRITES) {
349
+ await downloadSprite(sprite, assetsDir);
350
+ }
351
+ spinner.text = "Writing editor rules and snippets...";
352
+ writeEditorFile(
353
+ import_path2.default.resolve(process.cwd(), ".cursor/rules/base-ui.mdc"),
354
+ CURSOR_RULE,
355
+ ".cursor/rules/base-ui.mdc"
356
+ );
357
+ writeEditorFile(
358
+ import_path2.default.resolve(process.cwd(), ".vscode/base-ui.code-snippets"),
359
+ CODE_SNIPPETS,
360
+ ".vscode/base-ui.code-snippets"
361
+ );
362
+ spinner.succeed(`Project initialized! Add your first component with "${CLI_NPX} add button".`);
363
+ }
364
+ // Annotate the CommonJS export names for ESM import in node:
365
+ 0 && (module.exports = {
366
+ init
367
+ });
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/commands/list.ts
21
+ var list_exports = {};
22
+ __export(list_exports, {
23
+ list: () => list
24
+ });
25
+ module.exports = __toCommonJS(list_exports);
26
+ var import_vendor = require("../vendor");
27
+ var import_registry = require("../registry");
28
+
29
+ // src/brand.ts
30
+ var CLI_BIN = "ply-ui-cli";
31
+ var CLI_NPX = `npx ${CLI_BIN}`;
32
+ var MCP_PACKAGE = "ply-ui-mcp";
33
+ var MCP_NPX = `npx -y ${MCP_PACKAGE}`;
34
+
35
+ // src/categories.ts
36
+ var CATEGORY_TITLES = {
37
+ application: "Applications",
38
+ "page-layout": "Page layouts",
39
+ block: "Blocks",
40
+ widget: "Widgets",
41
+ shell: "Shells",
42
+ component: "Components"
43
+ };
44
+ var CATEGORY_ORDER = [
45
+ "application",
46
+ "page-layout",
47
+ "block",
48
+ "widget",
49
+ "shell",
50
+ "component"
51
+ ];
52
+
53
+ // src/commands/list.ts
54
+ async function list() {
55
+ const spinner = (0, import_vendor.ora)("Loading registry index...").start();
56
+ let index;
57
+ try {
58
+ index = await (0, import_registry.fetchIndex)();
59
+ spinner.stop();
60
+ } catch (err) {
61
+ spinner.fail(err.message);
62
+ process.exitCode = 1;
63
+ return;
64
+ }
65
+ const printColumns = (names) => {
66
+ if (names.length === 0) return;
67
+ const width = Math.max(...names.map((n) => n.length)) + 2;
68
+ const perRow = Math.max(1, Math.floor(80 / width));
69
+ for (let i = 0; i < names.length; i += perRow) {
70
+ console.log(" " + names.slice(i, i + perRow).map((n) => n.padEnd(width)).join(""));
71
+ }
72
+ };
73
+ const printTier = (tier, label, footnote) => {
74
+ const items = index.filter((i) => i.tier === tier);
75
+ console.log(`
76
+ ${label} (${items.length}) \u2014 ${footnote}
77
+ `);
78
+ for (const category of CATEGORY_ORDER) {
79
+ const names = items.filter((i) => (i.category || "component") === category).map((i) => i.name).sort();
80
+ if (!names.length) continue;
81
+ console.log(` ${CATEGORY_TITLES[category] || category} (${names.length})`);
82
+ printColumns(names);
83
+ console.log("");
84
+ }
85
+ const known = new Set(CATEGORY_ORDER);
86
+ const other = items.filter((i) => i.category && !known.has(i.category)).map((i) => i.name).sort();
87
+ if (other.length) {
88
+ console.log(` Other (${other.length})`);
89
+ printColumns(other);
90
+ console.log("");
91
+ }
92
+ };
93
+ printTier("free", "Free components", `${CLI_NPX} add <name>`);
94
+ printTier("pro", "Pro components", `require a license (${import_registry.SITE_URL}/pricing)`);
95
+ }
96
+ // Annotate the CommonJS export names for ESM import in node:
97
+ 0 && (module.exports = {
98
+ list
99
+ });