ply-ui-cli 1.1.122 → 1.1.124

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.
@@ -33,26 +33,46 @@ __export(init_exports, {
33
33
  init: () => init
34
34
  });
35
35
  module.exports = __toCommonJS(init_exports);
36
- var import_fs2 = __toESM(require("fs"));
36
+ var import_fs3 = __toESM(require("fs"));
37
37
  var import_path2 = __toESM(require("path"));
38
38
  var import_vendor = require("../vendor");
39
39
  var import_registry = require("../registry");
40
40
 
41
41
  // src/lockfile.ts
42
+ var import_fs2 = __toESM(require("fs"));
43
+
44
+ // src/paths.ts
42
45
  var import_fs = __toESM(require("fs"));
43
46
  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);
47
+ var CONFIG_FILE = "ply-ui.json";
48
+ var CSS_FILE = "ply-ui.css";
49
+ var LEGACY_CSS_FILE = "base-ui.css";
50
+ var LOCKFILE_NAME = "ply-ui-lock.json";
51
+ var LEGACY_LOCKFILE_NAME = "base-ui-lock.json";
52
+ var CURSOR_RULE_REL = ".cursor/rules/ply-ui.mdc";
53
+ var SNIPPETS_REL = ".vscode/ply-ui.code-snippets";
54
+ var DOCS_CURSOR_RULE = "docs/ai/ply-ui.mdc";
55
+ var DOCS_SNIPPETS = "docs/ai/ply-ui.code-snippets";
56
+ function firstExisting(cwd, names) {
57
+ for (const name of names) {
58
+ const resolved = import_path.default.resolve(cwd, name);
59
+ if (import_fs.default.existsSync(resolved)) return resolved;
60
+ }
61
+ return null;
48
62
  }
63
+ function lockfilePath(cwd = process.cwd()) {
64
+ return firstExisting(cwd, [LOCKFILE_NAME, LEGACY_LOCKFILE_NAME]) || import_path.default.resolve(cwd, LOCKFILE_NAME);
65
+ }
66
+
67
+ // src/lockfile.ts
68
+ var LOCKFILE_VERSION = 1;
49
69
  function readLockFile() {
50
70
  const p = lockfilePath();
51
- if (!import_fs.default.existsSync(p)) {
71
+ if (!import_fs2.default.existsSync(p)) {
52
72
  return { version: LOCKFILE_VERSION, components: {} };
53
73
  }
54
74
  try {
55
- const parsed = JSON.parse(import_fs.default.readFileSync(p, "utf8"));
75
+ const parsed = JSON.parse(import_fs2.default.readFileSync(p, "utf8"));
56
76
  if (parsed && typeof parsed === "object" && parsed.components) {
57
77
  return { version: parsed.version ?? LOCKFILE_VERSION, components: parsed.components };
58
78
  }
@@ -61,11 +81,11 @@ function readLockFile() {
61
81
  return { version: LOCKFILE_VERSION, components: {} };
62
82
  }
63
83
  function writeLockFile(lock) {
64
- import_fs.default.writeFileSync(lockfilePath(), JSON.stringify(lock, null, 2) + "\n", "utf8");
84
+ import_fs2.default.writeFileSync(lockfilePath(), JSON.stringify(lock, null, 2) + "\n", "utf8");
65
85
  }
66
86
 
67
87
  // 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';
88
+ 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 `ply-ui.json`, Angular CDK, Tailwind v4 `@source` paths, `ply-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 `--ply-primary` from `ply-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/ply-ui.code-snippets` \u2014 expand prefixes like `ply-card` after the CLI has copied the component in.\n';
69
89
  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
90
 
71
91
  // src/brand.ts
@@ -86,31 +106,46 @@ var BASE_CSS = `/* Ply global styles \u2014 ${import_registry.SITE_URL} */
86
106
  * just to change brand color.
87
107
  */
88
108
  :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);
109
+ --ply-background: #ffffff;
110
+ --ply-foreground: #0f172a;
111
+ --ply-primary: #2563eb;
112
+ --ply-primary-hover: #1d4ed8;
113
+ --ply-primary-active: #1e40af;
114
+ --ply-primary-foreground: #ffffff;
115
+ --ply-muted: #e2e8f0;
116
+ --ply-muted-foreground: #334155;
117
+ --ply-destructive: #dc2626;
118
+ --ply-destructive-hover: #b91c1c;
119
+ --ply-destructive-foreground: #ffffff;
120
+ --ply-border: #cbd5e1;
121
+ --ply-ring: #2563eb;
122
+ --ply-radius: 0.5rem;
123
+ --radius: var(--ply-radius);
124
+ /* Compat for components copied before the Ply rename. */
125
+ --base-background: var(--ply-background);
126
+ --base-foreground: var(--ply-foreground);
127
+ --base-primary: var(--ply-primary);
128
+ --base-primary-hover: var(--ply-primary-hover);
129
+ --base-primary-active: var(--ply-primary-active);
130
+ --base-primary-foreground: var(--ply-primary-foreground);
131
+ --base-muted: var(--ply-muted);
132
+ --base-muted-foreground: var(--ply-muted-foreground);
133
+ --base-destructive: var(--ply-destructive);
134
+ --base-destructive-hover: var(--ply-destructive-hover);
135
+ --base-destructive-foreground: var(--ply-destructive-foreground);
136
+ --base-border: var(--ply-border);
137
+ --base-ring: var(--ply-ring);
138
+ --base-radius: var(--ply-radius);
104
139
  }
105
140
 
106
141
  .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;
142
+ --ply-background: #0f172a;
143
+ --ply-foreground: #f8fafc;
144
+ --ply-muted: #1e293b;
145
+ --ply-muted-foreground: #e2e8f0;
146
+ --ply-border: #334155;
147
+ --ply-primary-foreground: #ffffff;
148
+ --ply-destructive-foreground: #ffffff;
114
149
  }
115
150
 
116
151
  @keyframes fade-in-up {
@@ -204,8 +239,8 @@ async function downloadSprite(name, targetDir) {
204
239
  const res = await fetch(`${import_registry.SITE_URL}/assets/${name}`);
205
240
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
206
241
  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);
242
+ import_fs3.default.mkdirSync(targetDir, { recursive: true });
243
+ import_fs3.default.writeFileSync(import_path2.default.join(targetDir, name), body);
209
244
  return true;
210
245
  } catch (err) {
211
246
  console.warn(`
@@ -214,14 +249,14 @@ async function downloadSprite(name, targetDir) {
214
249
  }
215
250
  }
216
251
  function writeEditorFile(dest, content, label) {
217
- if (import_fs2.default.existsSync(dest)) return;
252
+ if (import_fs3.default.existsSync(dest)) return;
218
253
  try {
219
- import_fs2.default.mkdirSync(import_path2.default.dirname(dest), { recursive: true });
220
- import_fs2.default.writeFileSync(dest, content, "utf8");
254
+ import_fs3.default.mkdirSync(import_path2.default.dirname(dest), { recursive: true });
255
+ import_fs3.default.writeFileSync(dest, content, "utf8");
221
256
  } catch (err) {
222
257
  console.warn(
223
258
  `
224
- \u26A0 Could not write ${label} (${err.message}). Copy it from ${import_registry.SITE_URL}/docs/ai/${import_path2.default.basename(dest)}.`
259
+ \u26A0 Could not write ${label} (${err.message}). Copy it from ${import_registry.SITE_URL}/${label.endsWith(".code-snippets") ? DOCS_SNIPPETS : DOCS_CURSOR_RULE}.`
225
260
  );
226
261
  }
227
262
  }
@@ -255,8 +290,8 @@ async function init(options = {}) {
255
290
  console.log("Initialization cancelled.");
256
291
  return;
257
292
  }
258
- const spinner = (0, import_vendor.ora)("Writing base-ui.json...").start();
259
- const configPath = import_path2.default.resolve(process.cwd(), "base-ui.json");
293
+ const spinner = (0, import_vendor.ora)(`Writing ${CONFIG_FILE}...`).start();
294
+ const configPath = import_path2.default.resolve(process.cwd(), CONFIG_FILE);
260
295
  const config = {
261
296
  $schema: "https://ply-ui.com/schema.json",
262
297
  style: "scss",
@@ -271,41 +306,45 @@ async function init(options = {}) {
271
306
  utils: "src/app/utils"
272
307
  }
273
308
  };
274
- import_fs2.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
309
+ import_fs3.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
275
310
  const lockPath = import_path2.default.resolve(process.cwd(), LOCKFILE_NAME);
276
- if (!import_fs2.default.existsSync(lockPath)) {
311
+ if (!import_fs3.default.existsSync(lockPath)) {
277
312
  writeLockFile(readLockFile());
278
313
  }
279
314
  spinner.text = "Writing global styles...";
280
315
  const stylesPath = import_path2.default.resolve(process.cwd(), response.styles);
281
316
  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';`;
317
+ const cssPath = import_path2.default.join(stylesDir, CSS_FILE);
318
+ const importLine = `@import './${CSS_FILE}';`;
319
+ const legacyImport = `@import './${LEGACY_CSS_FILE}';`;
284
320
  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}
321
+ import_fs3.default.mkdirSync(stylesDir, { recursive: true });
322
+ import_fs3.default.writeFileSync(cssPath, BASE_CSS, "utf8");
323
+ if (import_fs3.default.existsSync(stylesPath)) {
324
+ let stylesContent = import_fs3.default.readFileSync(stylesPath, "utf8");
325
+ if (stylesContent.includes(legacyImport) && !stylesContent.includes(importLine)) {
326
+ stylesContent = stylesContent.replace(legacyImport, importLine);
327
+ import_fs3.default.writeFileSync(stylesPath, stylesContent, "utf8");
328
+ } else if (!stylesContent.includes(importLine)) {
329
+ import_fs3.default.writeFileSync(stylesPath, `${importLine}
291
330
  ${stylesContent}`, "utf8");
292
331
  }
293
332
  } else {
294
- import_fs2.default.writeFileSync(stylesPath, `${importLine}
333
+ import_fs3.default.writeFileSync(stylesPath, `${importLine}
295
334
  `, "utf8");
296
335
  console.warn(`
297
336
  \u26A0 ${response.styles} did not exist \u2014 created it. Make sure it is listed in the "styles" array of angular.json.`);
298
337
  }
299
338
  } catch (error) {
300
339
  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.`);
340
+ \u26A0 Could not update global styles (${error.message}). Add "${importLine}" to ${response.styles} manually and create ${CSS_FILE} next to it.`);
302
341
  }
303
342
  spinner.text = "Setting up icon sprites...";
304
343
  let assetsDir = import_path2.default.resolve(process.cwd(), "src/assets");
305
344
  try {
306
345
  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"));
346
+ if (import_fs3.default.existsSync(angularJsonPath)) {
347
+ const angularJson = JSON.parse(import_fs3.default.readFileSync(angularJsonPath, "utf8"));
309
348
  const picked = pickAngularProject(angularJson);
310
349
  const buildOptions = picked?.project?.architect?.build?.options;
311
350
  if (buildOptions) {
@@ -331,7 +370,7 @@ ${stylesContent}`, "utf8");
331
370
  }
332
371
  }
333
372
  buildOptions.assets = assets;
334
- import_fs2.default.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2), "utf8");
373
+ import_fs3.default.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2), "utf8");
335
374
  }
336
375
  } else {
337
376
  console.warn(`
@@ -350,14 +389,14 @@ ${stylesContent}`, "utf8");
350
389
  }
351
390
  spinner.text = "Writing editor rules and snippets...";
352
391
  writeEditorFile(
353
- import_path2.default.resolve(process.cwd(), ".cursor/rules/base-ui.mdc"),
392
+ import_path2.default.resolve(process.cwd(), CURSOR_RULE_REL),
354
393
  CURSOR_RULE,
355
- ".cursor/rules/base-ui.mdc"
394
+ CURSOR_RULE_REL
356
395
  );
357
396
  writeEditorFile(
358
- import_path2.default.resolve(process.cwd(), ".vscode/base-ui.code-snippets"),
397
+ import_path2.default.resolve(process.cwd(), SNIPPETS_REL),
359
398
  CODE_SNIPPETS,
360
- ".vscode/base-ui.code-snippets"
399
+ SNIPPETS_REL
361
400
  );
362
401
  spinner.succeed(`Project initialized! Add your first component with "${CLI_NPX} add button".`);
363
402
  }
@@ -33,13 +33,13 @@ __export(update_exports, {
33
33
  update: () => update
34
34
  });
35
35
  module.exports = __toCommonJS(update_exports);
36
- var import_fs5 = __toESM(require("fs"));
36
+ var import_fs6 = __toESM(require("fs"));
37
37
  var import_path6 = __toESM(require("path"));
38
38
  var import_vendor = require("../vendor");
39
39
 
40
40
  // src/config.ts
41
- var import_fs = __toESM(require("fs"));
42
- var import_path = __toESM(require("path"));
41
+ var import_fs2 = __toESM(require("fs"));
42
+ var import_path2 = __toESM(require("path"));
43
43
 
44
44
  // src/brand.ts
45
45
  var CLI_BIN = "ply-ui-cli";
@@ -47,44 +47,63 @@ var CLI_NPX = `npx ${CLI_BIN}`;
47
47
  var MCP_PACKAGE = "ply-ui-mcp";
48
48
  var MCP_NPX = `npx -y ${MCP_PACKAGE}`;
49
49
 
50
+ // src/paths.ts
51
+ var import_fs = __toESM(require("fs"));
52
+ var import_path = __toESM(require("path"));
53
+ var CONFIG_FILE = "ply-ui.json";
54
+ var LEGACY_CONFIG_FILE = "base-ui.json";
55
+ var LOCKFILE_NAME = "ply-ui-lock.json";
56
+ var LEGACY_LOCKFILE_NAME = "base-ui-lock.json";
57
+ var SNAPSHOT_ROOT = ".ply-ui/snapshots";
58
+ function firstExisting(cwd, names) {
59
+ for (const name of names) {
60
+ const resolved = import_path.default.resolve(cwd, name);
61
+ if (import_fs.default.existsSync(resolved)) return resolved;
62
+ }
63
+ return null;
64
+ }
65
+ function configFilePath(cwd = process.cwd()) {
66
+ return firstExisting(cwd, [CONFIG_FILE, LEGACY_CONFIG_FILE]);
67
+ }
68
+ function lockfilePath(cwd = process.cwd()) {
69
+ return firstExisting(cwd, [LOCKFILE_NAME, LEGACY_LOCKFILE_NAME]) || import_path.default.resolve(cwd, LOCKFILE_NAME);
70
+ }
71
+
50
72
  // src/config.ts
51
73
  function readConfig() {
52
- const configPath = import_path.default.resolve(process.cwd(), "base-ui.json");
53
- if (!import_fs.default.existsSync(configPath)) {
54
- console.error(`base-ui.json not found. Please run "${CLI_NPX} init" first.`);
74
+ const resolved = configFilePath();
75
+ if (!resolved) {
76
+ console.error(
77
+ `${CONFIG_FILE} not found (also looked for ${LEGACY_CONFIG_FILE}). Please run "${CLI_NPX} init" first.`
78
+ );
55
79
  return null;
56
80
  }
57
81
  try {
58
- const config = JSON.parse(import_fs.default.readFileSync(configPath, "utf8"));
82
+ const config = JSON.parse(import_fs2.default.readFileSync(resolved, "utf8"));
59
83
  if (!config || !config.aliases || !config.aliases.components) {
60
- throw new Error("Missing aliases.components in base-ui.json");
84
+ throw new Error(`Missing aliases.components in ${import_path2.default.basename(resolved)}`);
61
85
  }
62
86
  return config;
63
87
  } catch (err) {
64
- console.error(`Error reading base-ui.json: ${err.message}`);
88
+ console.error(`Error reading ${import_path2.default.basename(resolved)}: ${err.message}`);
65
89
  return null;
66
90
  }
67
91
  }
68
92
 
69
93
  // src/lockfile.ts
70
- var import_fs2 = __toESM(require("fs"));
71
- var import_path2 = __toESM(require("path"));
94
+ var import_fs3 = __toESM(require("fs"));
72
95
  var import_crypto = __toESM(require("crypto"));
73
- var LOCKFILE_NAME = "base-ui-lock.json";
74
96
  var LOCKFILE_VERSION = 1;
75
97
  function hashContent(content) {
76
98
  return import_crypto.default.createHash("sha256").update(content, "utf8").digest("hex");
77
99
  }
78
- function lockfilePath() {
79
- return import_path2.default.resolve(process.cwd(), LOCKFILE_NAME);
80
- }
81
100
  function readLockFile() {
82
101
  const p = lockfilePath();
83
- if (!import_fs2.default.existsSync(p)) {
102
+ if (!import_fs3.default.existsSync(p)) {
84
103
  return { version: LOCKFILE_VERSION, components: {} };
85
104
  }
86
105
  try {
87
- const parsed = JSON.parse(import_fs2.default.readFileSync(p, "utf8"));
106
+ const parsed = JSON.parse(import_fs3.default.readFileSync(p, "utf8"));
88
107
  if (parsed && typeof parsed === "object" && parsed.components) {
89
108
  return { version: parsed.version ?? LOCKFILE_VERSION, components: parsed.components };
90
109
  }
@@ -93,7 +112,7 @@ function readLockFile() {
93
112
  return { version: LOCKFILE_VERSION, components: {} };
94
113
  }
95
114
  function writeLockFile(lock) {
96
- import_fs2.default.writeFileSync(lockfilePath(), JSON.stringify(lock, null, 2) + "\n", "utf8");
115
+ import_fs3.default.writeFileSync(lockfilePath(), JSON.stringify(lock, null, 2) + "\n", "utf8");
97
116
  }
98
117
 
99
118
  // src/path-safety.ts
@@ -109,33 +128,32 @@ var import_registry = require("../registry");
109
128
  var import_sync = require("../sync");
110
129
 
111
130
  // src/snapshot.ts
112
- var import_fs3 = __toESM(require("fs"));
131
+ var import_fs4 = __toESM(require("fs"));
113
132
  var import_path4 = __toESM(require("path"));
114
- var SNAPSHOT_ROOT = ".base-ui/snapshots";
115
- function snapshotPath(component, file) {
116
- return import_path4.default.resolve(process.cwd(), SNAPSHOT_ROOT, component, file);
133
+ function snapshotPath(component, file, root) {
134
+ return import_path4.default.resolve(process.cwd(), root, component, file);
117
135
  }
118
136
  function writeSnapshot(component, file, content) {
119
137
  if (!isSafeFileName(file) || !isSafeFileName(component)) return;
120
- const dest = snapshotPath(component, file);
121
- import_fs3.default.mkdirSync(import_path4.default.dirname(dest), { recursive: true });
122
- import_fs3.default.writeFileSync(dest, content, "utf8");
138
+ const dest = snapshotPath(component, file, SNAPSHOT_ROOT);
139
+ import_fs4.default.mkdirSync(import_path4.default.dirname(dest), { recursive: true });
140
+ import_fs4.default.writeFileSync(dest, content, "utf8");
123
141
  }
124
142
 
125
143
  // src/merge.ts
126
- var import_fs4 = __toESM(require("fs"));
144
+ var import_fs5 = __toESM(require("fs"));
127
145
  var import_os = __toESM(require("os"));
128
146
  var import_path5 = __toESM(require("path"));
129
147
  var import_child_process = require("child_process");
130
148
  function mergeThreeWay(args) {
131
- const dir = import_fs4.default.mkdtempSync(import_path5.default.join(import_os.default.tmpdir(), "base-ui-merge-"));
149
+ const dir = import_fs5.default.mkdtempSync(import_path5.default.join(import_os.default.tmpdir(), "base-ui-merge-"));
132
150
  const ancestorPath = import_path5.default.join(dir, "ancestor");
133
151
  const localPath = import_path5.default.join(dir, "local");
134
152
  const upstreamPath = import_path5.default.join(dir, "upstream");
135
153
  try {
136
- import_fs4.default.writeFileSync(ancestorPath, args.ancestor, "utf8");
137
- import_fs4.default.writeFileSync(localPath, args.local, "utf8");
138
- import_fs4.default.writeFileSync(upstreamPath, args.upstream, "utf8");
154
+ import_fs5.default.writeFileSync(ancestorPath, args.ancestor, "utf8");
155
+ import_fs5.default.writeFileSync(localPath, args.local, "utf8");
156
+ import_fs5.default.writeFileSync(upstreamPath, args.upstream, "utf8");
139
157
  const result = (0, import_child_process.spawnSync)(
140
158
  "git",
141
159
  ["merge-file", "-p", "--diff3", localPath, ancestorPath, upstreamPath],
@@ -151,7 +169,7 @@ function mergeThreeWay(args) {
151
169
  const text = result.stdout ?? "";
152
170
  return { clean: result.status === 0, text };
153
171
  } finally {
154
- import_fs4.default.rmSync(dir, { recursive: true, force: true });
172
+ import_fs5.default.rmSync(dir, { recursive: true, force: true });
155
173
  }
156
174
  }
157
175
  function conflictMarkers(args) {
@@ -253,8 +271,8 @@ async function update(components, options = {}) {
253
271
  console.warn(`\u26A0 Skipping unsafe file path from registry: ${result.name}/${file.path}`);
254
272
  continue;
255
273
  }
256
- import_fs5.default.mkdirSync(import_path6.default.dirname(localPath), { recursive: true });
257
- import_fs5.default.writeFileSync(localPath, file.upstreamContent ?? "", "utf8");
274
+ import_fs6.default.mkdirSync(import_path6.default.dirname(localPath), { recursive: true });
275
+ import_fs6.default.writeFileSync(localPath, file.upstreamContent ?? "", "utf8");
258
276
  lock.components[result.name].files[file.path] = hashContent(file.upstreamContent ?? "");
259
277
  writeSnapshot(result.name, file.path, file.upstreamContent ?? "");
260
278
  lockChanged = true;
@@ -273,8 +291,8 @@ async function update(components, options = {}) {
273
291
  continue;
274
292
  }
275
293
  if (file.status === "upstream-changed" || file.status === "new-upstream") {
276
- import_fs5.default.mkdirSync(import_path6.default.dirname(localPath), { recursive: true });
277
- import_fs5.default.writeFileSync(localPath, file.upstreamContent ?? "", "utf8");
294
+ import_fs6.default.mkdirSync(import_path6.default.dirname(localPath), { recursive: true });
295
+ import_fs6.default.writeFileSync(localPath, file.upstreamContent ?? "", "utf8");
278
296
  lock.components[result.name].files[file.path] = hashContent(file.upstreamContent ?? "");
279
297
  writeSnapshot(result.name, file.path, file.upstreamContent ?? "");
280
298
  lockChanged = true;
@@ -307,7 +325,7 @@ async function update(components, options = {}) {
307
325
  local: file.localContent ?? "",
308
326
  upstream: file.upstreamContent ?? ""
309
327
  });
310
- import_fs5.default.writeFileSync(localPath, merged.text, "utf8");
328
+ import_fs6.default.writeFileSync(localPath, merged.text, "utf8");
311
329
  lock.components[result.name].files[file.path] = hashContent(merged.text);
312
330
  writeSnapshot(result.name, file.path, merged.text);
313
331
  lockChanged = true;
@@ -322,14 +340,14 @@ async function update(components, options = {}) {
322
340
  }
323
341
  }
324
342
  } else if (choice === "upstream") {
325
- import_fs5.default.writeFileSync(localPath, file.upstreamContent ?? "", "utf8");
343
+ import_fs6.default.writeFileSync(localPath, file.upstreamContent ?? "", "utf8");
326
344
  lock.components[result.name].files[file.path] = hashContent(file.upstreamContent ?? "");
327
345
  writeSnapshot(result.name, file.path, file.upstreamContent ?? "");
328
346
  lockChanged = true;
329
347
  conflictsToUpstream++;
330
348
  console.log(` \u2191 ${result.name}/${file.path} (took upstream, your edits were overwritten)`);
331
349
  } else if (choice === "save-aside") {
332
- import_fs5.default.writeFileSync(`${localPath}.upstream`, file.upstreamContent ?? "", "utf8");
350
+ import_fs6.default.writeFileSync(`${localPath}.upstream`, file.upstreamContent ?? "", "utf8");
333
351
  conflictsSavedAside++;
334
352
  console.log(` \u21C4 ${result.name}/${file.path}.upstream (merge by hand \u2014 your file was left untouched)`);
335
353
  } else {
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ function printHelp() {
34
34
  console.log(`Usage: ${CLI_BIN} <command> [options]
35
35
 
36
36
  Commands:
37
- init Initialize your project (base-ui.json, styles, icons, Cursor rules, snippets)
37
+ init Initialize your project (ply-ui.json, styles, icons, Cursor rules, snippets)
38
38
  add [components...] Add components; omit names to pick from the catalog
39
39
  list List all available components (free and pro)
40
40
  diff [components...] Show upstream changes for installed components since you added them
@@ -108,7 +108,7 @@ function parseArgs(argv) {
108
108
  async function main() {
109
109
  const { command, positionals, flags } = parseArgs(process.argv.slice(2));
110
110
  if (flags.version && !command) {
111
- console.log("1.1.122");
111
+ console.log("1.1.124");
112
112
  return;
113
113
  }
114
114
  if (flags.help || !command) {
@@ -158,7 +158,7 @@ async function main() {
158
158
  }
159
159
  case "--version":
160
160
  case "-V":
161
- console.log("1.1.122");
161
+ console.log("1.1.124");
162
162
  return;
163
163
  default:
164
164
  console.error(`Unknown command: ${command}`);
package/dist/sync.js CHANGED
@@ -45,6 +45,13 @@ var import_registry = require("./registry");
45
45
 
46
46
  // src/lockfile.ts
47
47
  var import_crypto = __toESM(require("crypto"));
48
+
49
+ // src/paths.ts
50
+ var LOCKFILE_NAME = "ply-ui-lock.json";
51
+ var SNAPSHOT_ROOT = ".ply-ui/snapshots";
52
+ var LEGACY_SNAPSHOT_ROOT = ".base-ui/snapshots";
53
+
54
+ // src/lockfile.ts
48
55
  function hashContent(content) {
49
56
  return import_crypto.default.createHash("sha256").update(content, "utf8").digest("hex");
50
57
  }
@@ -52,16 +59,18 @@ function hashContent(content) {
52
59
  // src/snapshot.ts
53
60
  var import_fs = __toESM(require("fs"));
54
61
  var import_path = __toESM(require("path"));
55
- var SNAPSHOT_ROOT = ".base-ui/snapshots";
56
- function snapshotPath(component, file) {
57
- return import_path.default.resolve(process.cwd(), SNAPSHOT_ROOT, component, file);
62
+ function snapshotPath(component, file, root) {
63
+ return import_path.default.resolve(process.cwd(), root, component, file);
58
64
  }
59
65
  function readSnapshot(component, file, expectedHash) {
60
- const dest = snapshotPath(component, file);
61
- if (!import_fs.default.existsSync(dest)) return void 0;
62
- const content = import_fs.default.readFileSync(dest, "utf8");
63
- if (expectedHash && hashContent(content) !== expectedHash) return void 0;
64
- return content;
66
+ for (const root of [SNAPSHOT_ROOT, LEGACY_SNAPSHOT_ROOT]) {
67
+ const dest = snapshotPath(component, file, root);
68
+ if (!import_fs.default.existsSync(dest)) continue;
69
+ const content = import_fs.default.readFileSync(dest, "utf8");
70
+ if (expectedHash && hashContent(content) !== expectedHash) continue;
71
+ return content;
72
+ }
73
+ return void 0;
65
74
  }
66
75
 
67
76
  // src/brand.ts
@@ -100,7 +109,7 @@ async function computeComponentDiff(name, componentsRoot, lock, index) {
100
109
  return {
101
110
  name,
102
111
  files: [],
103
- error: `not tracked in ${import_path2.default.basename("base-ui-lock.json")} \u2014 run "${CLI_NPX} add ${name} --overwrite" to start tracking it.`
112
+ error: `not tracked in ${LOCKFILE_NAME} \u2014 run "${CLI_NPX} add ${name} --overwrite" to start tracking it.`
104
113
  };
105
114
  }
106
115
  const indexEntry = index.find((i) => i.name === name);