aplance-ui 1.0.2 → 1.1.1

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/dist/api.d.ts CHANGED
@@ -58,10 +58,10 @@ interface MakeDefaultOptions {
58
58
  utilities?: boolean;
59
59
  cssConfigPlacement?: CssConfigPlacement;
60
60
  }
61
- declare const makeDefaultConfig: (opts: MakeDefaultOptions) => ComponentsConfig;
62
61
  declare const configExists: (cwd: string) => boolean;
63
62
  declare const loadConfig: (cwd: string) => ComponentsConfig;
64
63
  declare const resolvePaths: (cwd: string, cfg: ComponentsConfig) => ResolvedPaths;
64
+ declare const makeDefaultConfig: (opts: MakeDefaultOptions) => ComponentsConfig;
65
65
 
66
66
  type UnitKind = "component" | "hook" | "lib";
67
67
  type UnitGroup = "ui" | "primitives" | null;
@@ -70,6 +70,8 @@ interface RegistryUnit {
70
70
  kind: UnitKind;
71
71
  group: UnitGroup;
72
72
  files: string[];
73
+ /** Source files that should receive a Next.js "use client" directive. */
74
+ clientFiles?: string[];
73
75
  deps: string[];
74
76
  /**
75
77
  * External npm packages (root specifiers, e.g. `react`, `@tanstack/react-table`)
package/dist/api.js CHANGED
@@ -218,7 +218,7 @@ var resolveRegistryRoot = () => {
218
218
  `This indicates a packaging problem - reinstall the aplance-ui CLI.`
219
219
  );
220
220
  };
221
- var EXPECTED_VERSION = 3;
221
+ var EXPECTED_VERSION = 4;
222
222
  var EMPTY_INSTALL_META = {
223
223
  baseDependencies: {},
224
224
  baseDevDependencies: {},
@@ -435,16 +435,23 @@ var remap = (relFile, paths) => {
435
435
  }
436
436
  return relFile;
437
437
  };
438
+ var hasUseClientDirective = (content) => /^\s*["']use client["'];?/.test(content);
439
+ var withUseClientDirective = (content) => hasUseClientDirective(content) ? content : `"use client";
440
+
441
+ ${content}`;
438
442
  var planComponentOps = (opts) => {
439
443
  const ops = [];
440
444
  for (const unit of opts.units) {
445
+ const nextClientFiles = new Set(unit.clientFiles ?? []);
441
446
  for (const relFile of unit.files) {
442
447
  const sourceAbs = join3(opts.registry.root, relFile);
443
448
  const mapped = remap(relFile, opts.paths);
444
449
  const targetAbs = join3(opts.targetSrcDir, mapped);
450
+ const sourceContent = readFileSync4(sourceAbs, "utf8");
451
+ const content = opts.template === "next" && nextClientFiles.has(relFile) ? withUseClientDirective(sourceContent) : sourceContent;
445
452
  ops.push({
446
453
  target: targetAbs,
447
- content: readFileSync4(sourceAbs, "utf8"),
454
+ content,
448
455
  label: mapped
449
456
  });
450
457
  }
@@ -1188,9 +1195,9 @@ var getInlineHostPath = (template, project) => FRAMEWORK_META[template].resolveI
1188
1195
 
1189
1196
  // src/lib/config.ts
1190
1197
  import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
1191
- import { join as join7 } from "path";
1198
+ import { isAbsolute, join as join7 } from "path";
1192
1199
  var CONFIG_FILE_NAME = "components.json";
1193
- var CONFIG_SCHEMA_URL = "https://aplance-ui.dev/schema.json";
1200
+ var CONFIG_SCHEMA_URL = "https://ui.aplance.com/components.schema.json";
1194
1201
  var SUPPORTED_TEMPLATES = [
1195
1202
  "vite-react",
1196
1203
  "next",
@@ -1212,21 +1219,6 @@ var TEMPLATE_DEFAULT_SRCDIR = {
1212
1219
  astro: "src",
1213
1220
  "electron-vite": "src/renderer/src"
1214
1221
  };
1215
- var makeDefaultConfig = (opts) => ({
1216
- $schema: CONFIG_SCHEMA_URL,
1217
- template: opts.template,
1218
- srcDir: opts.srcDir ?? TEMPLATE_DEFAULT_SRCDIR[opts.template],
1219
- alias: opts.alias ?? "@",
1220
- theme: {
1221
- color: opts.color ?? "default",
1222
- radius: opts.radius ?? "default",
1223
- spacing: opts.spacing ?? "default"
1224
- },
1225
- paths: { ...DEFAULT_PATHS },
1226
- cssModules: { pretty: opts.pretty ?? false },
1227
- utilities: { enabled: opts.utilities ?? false },
1228
- cssConfig: { placement: opts.cssConfigPlacement ?? "auto" }
1229
- });
1230
1222
  var configPath = (cwd) => join7(cwd, CONFIG_FILE_NAME);
1231
1223
  var configExists = (cwd) => existsSync7(configPath(cwd));
1232
1224
  var loadConfig = (cwd) => {
@@ -1251,6 +1243,10 @@ var loadConfig = (cwd) => {
1251
1243
  return validateConfig(parsed, p3);
1252
1244
  };
1253
1245
  var resolvePaths = (cwd, cfg) => {
1246
+ assertSafeRelativePath("srcDir", cfg.srcDir);
1247
+ for (const key of ["styles", "components", "hooks", "lib"]) {
1248
+ assertSafeRelativePath(`paths.${key}`, cfg.paths[key]);
1249
+ }
1254
1250
  const srcDir = join7(cwd, cfg.srcDir);
1255
1251
  return {
1256
1252
  srcDir,
@@ -1274,6 +1270,37 @@ var normalizeTheme = (raw) => {
1274
1270
  }
1275
1271
  return { color: "default", radius: "default", spacing: "default" };
1276
1272
  };
1273
+ var isSafeRelativePath = (value) => {
1274
+ if (value.includes("\0") || isAbsolute(value)) return false;
1275
+ return !value.split(/[\\/]+/).some((segment) => segment === "..");
1276
+ };
1277
+ var assertSafeRelativePath = (label, value) => {
1278
+ if (!isSafeRelativePath(value)) {
1279
+ throw new ConfigError(
1280
+ "E_CONFIG_INVALID",
1281
+ `${label} must be a relative path inside the project.`
1282
+ );
1283
+ }
1284
+ };
1285
+ var makeDefaultConfig = (opts) => {
1286
+ const srcDir = opts.srcDir ?? TEMPLATE_DEFAULT_SRCDIR[opts.template];
1287
+ assertSafeRelativePath("srcDir", srcDir);
1288
+ return {
1289
+ $schema: CONFIG_SCHEMA_URL,
1290
+ template: opts.template,
1291
+ srcDir,
1292
+ alias: opts.alias ?? "@",
1293
+ theme: {
1294
+ color: opts.color ?? "default",
1295
+ radius: opts.radius ?? "default",
1296
+ spacing: opts.spacing ?? "default"
1297
+ },
1298
+ paths: { ...DEFAULT_PATHS },
1299
+ cssModules: { pretty: opts.pretty ?? false },
1300
+ utilities: { enabled: opts.utilities ?? false },
1301
+ cssConfig: { placement: opts.cssConfigPlacement ?? "auto" }
1302
+ };
1303
+ };
1277
1304
  var validateConfig = (raw, file) => {
1278
1305
  if (!raw || typeof raw !== "object") {
1279
1306
  throw new ConfigError(
@@ -1291,11 +1318,13 @@ var validateConfig = (raw, file) => {
1291
1318
  `"template" must be one of: ${SUPPORTED_TEMPLATES.join(", ")} (got "${c.template}").`
1292
1319
  );
1293
1320
  must(typeof c.srcDir === "string" && c.srcDir.length > 0, `"srcDir" must be a non-empty string.`);
1321
+ must(isSafeRelativePath(c.srcDir), `"srcDir" must be a relative path inside the project.`);
1294
1322
  must(typeof c.alias === "string" && c.alias.length > 0, `"alias" must be a non-empty string.`);
1295
1323
  must(c.paths && typeof c.paths === "object", `"paths" must be an object.`);
1296
1324
  const ps = c.paths;
1297
1325
  for (const k of ["styles", "components", "hooks", "lib"]) {
1298
1326
  must(typeof ps[k] === "string" && ps[k].length > 0, `"paths.${k}" must be a non-empty string.`);
1327
+ must(isSafeRelativePath(ps[k]), `"paths.${k}" must be a relative path inside "srcDir".`);
1299
1328
  }
1300
1329
  must(c.cssModules && typeof c.cssModules === "object", `"cssModules" must be an object.`);
1301
1330
  must(typeof c.cssModules.pretty === "boolean", `"cssModules.pretty" must be a boolean.`);
@@ -1481,6 +1510,32 @@ var describePlan = (plan) => {
1481
1510
  return lines.join("\n");
1482
1511
  };
1483
1512
 
1513
+ // src/lib/unit-hints.ts
1514
+ var importPath = (cfg, path) => `${cfg.alias}/${path}`.replace(/\/+/g, "/");
1515
+ var getUnitHints = ({ cfg, units }) => {
1516
+ const selected = new Set(units.map((unit) => unit.name));
1517
+ const hints = [];
1518
+ if (selected.has("toast")) {
1519
+ const toastImport = importPath(
1520
+ cfg,
1521
+ `${cfg.paths.components}/ui/toast/toast`
1522
+ );
1523
+ hints.push(
1524
+ [
1525
+ "Toast needs one app-level Toaster mount before toast calls render.",
1526
+ `import { Toaster } from "${toastImport}";`,
1527
+ cfg.template === "next" ? "For Next.js, place it in a small Client Component such as app/providers.tsx." : "Place it once near your app root."
1528
+ ].join("\n")
1529
+ );
1530
+ }
1531
+ if (cfg.template === "next" && units.some((unit) => (unit.clientFiles?.length ?? 0) > 0)) {
1532
+ hints.push(
1533
+ 'Next.js output adds "use client" only to files that need client runtime APIs.'
1534
+ );
1535
+ }
1536
+ return hints;
1537
+ };
1538
+
1484
1539
  // src/commands/init.ts
1485
1540
  var resolveConflictMode = (opts) => {
1486
1541
  if (opts.dryRun) return "dry-run";
@@ -1627,6 +1682,9 @@ var runInit = async (opts) => {
1627
1682
  const registry = loadRegistry();
1628
1683
  const project = detectProject(opts.cwd);
1629
1684
  const { cfg, existed } = await resolveConfig(opts, project, registry);
1685
+ findColor(registry, cfg.theme.color);
1686
+ findRadius(registry, cfg.theme.radius);
1687
+ findSpacing(registry, cfg.theme.spacing);
1630
1688
  const placement = resolveEffectivePlacement(
1631
1689
  cfg.template,
1632
1690
  cfg.cssConfig.placement,
@@ -1683,7 +1741,8 @@ var runInit = async (opts) => {
1683
1741
  registry,
1684
1742
  targetSrcDir: paths.srcDir,
1685
1743
  units,
1686
- paths: cfg.paths
1744
+ paths: cfg.paths,
1745
+ template: cfg.template
1687
1746
  })
1688
1747
  );
1689
1748
  info(`planned ${units.length} unit${units.length === 1 ? "" : "s"} to copy`);
@@ -1748,6 +1807,10 @@ var runInit = async (opts) => {
1748
1807
  `manual install (skipped)`
1749
1808
  );
1750
1809
  }
1810
+ const unitHints = getUnitHints({ cfg, units });
1811
+ if (unitHints.length > 0) {
1812
+ note2(unitHints.join("\n\n"), "usage hints");
1813
+ }
1751
1814
  const steps = [
1752
1815
  `${pc4.cyan("1")} ${pc4.bold("Import styles once at your app entry")}
1753
1816
  import "./${cfg.paths.styles}/globals.css";`
@@ -1805,7 +1868,8 @@ var runAdd = async (opts) => {
1805
1868
  registry,
1806
1869
  targetSrcDir: paths.srcDir,
1807
1870
  units,
1808
- paths: cfg.paths
1871
+ paths: cfg.paths,
1872
+ template: cfg.template
1809
1873
  });
1810
1874
  if (cfg.utilities.enabled) {
1811
1875
  ops.push(planUtilitiesGitignoreOp({ targetStylesDir: paths.stylesDir }));
@@ -1868,21 +1932,31 @@ var runAdd = async (opts) => {
1868
1932
  "manual install (skipped)"
1869
1933
  );
1870
1934
  }
1935
+ const unitHints = getUnitHints({ cfg, units });
1936
+ if (unitHints.length > 0) {
1937
+ note2(unitHints.join("\n\n"), "usage hints");
1938
+ }
1871
1939
  logger.info(summary);
1872
1940
  goodbye(`Added ${units.length} unit${units.length === 1 ? "" : "s"}.`);
1873
1941
  };
1874
1942
  var computeRuntimePlan = (registry, units, project) => {
1875
1943
  const dependencies = {};
1944
+ const skipped = [];
1876
1945
  const meta = registry.installMeta;
1877
1946
  for (const unit of units) {
1878
1947
  for (const pkg of unit.externalDeps) {
1879
1948
  const version = meta.packageVersions[pkg];
1880
1949
  if (!version) continue;
1881
- if (project.declaredDeps.has(pkg)) continue;
1950
+ if (project.declaredDeps.has(pkg)) {
1951
+ if (!skipped.some((entry) => entry.name === pkg)) {
1952
+ skipped.push({ name: pkg, reason: "already-declared" });
1953
+ }
1954
+ continue;
1955
+ }
1882
1956
  dependencies[pkg] = version;
1883
1957
  }
1884
1958
  }
1885
- return { dependencies, devDependencies: {}, skipped: [] };
1959
+ return { dependencies, devDependencies: {}, skipped };
1886
1960
  };
1887
1961
  var pickUnits = async (all) => multiselect2({
1888
1962
  message: `Select units to add ${pc5.dim("(space to toggle, enter to confirm)")}`,