aplance-ui 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -226,7 +226,7 @@ var resolveRegistryRoot = () => {
226
226
  `This indicates a packaging problem - reinstall the aplance-ui CLI.`
227
227
  );
228
228
  };
229
- var EXPECTED_VERSION = 3;
229
+ var EXPECTED_VERSION = 4;
230
230
  var EMPTY_INSTALL_META = {
231
231
  baseDependencies: {},
232
232
  baseDevDependencies: {},
@@ -442,16 +442,23 @@ var remap = (relFile, paths) => {
442
442
  }
443
443
  return relFile;
444
444
  };
445
+ var hasUseClientDirective = (content) => /^\s*["']use client["'];?/.test(content);
446
+ var withUseClientDirective = (content) => hasUseClientDirective(content) ? content : `"use client";
447
+
448
+ ${content}`;
445
449
  var planComponentOps = (opts) => {
446
450
  const ops = [];
447
451
  for (const unit of opts.units) {
452
+ const nextClientFiles = new Set(unit.clientFiles ?? []);
448
453
  for (const relFile of unit.files) {
449
454
  const sourceAbs = join3(opts.registry.root, relFile);
450
455
  const mapped = remap(relFile, opts.paths);
451
456
  const targetAbs = join3(opts.targetSrcDir, mapped);
457
+ const sourceContent = readFileSync4(sourceAbs, "utf8");
458
+ const content = opts.template === "next" && nextClientFiles.has(relFile) ? withUseClientDirective(sourceContent) : sourceContent;
452
459
  ops.push({
453
460
  target: targetAbs,
454
- content: readFileSync4(sourceAbs, "utf8"),
461
+ content,
455
462
  label: mapped
456
463
  });
457
464
  }
@@ -1195,9 +1202,9 @@ var getInlineHostPath = (template, project) => FRAMEWORK_META[template].resolveI
1195
1202
 
1196
1203
  // src/lib/config.ts
1197
1204
  import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
1198
- import { join as join7 } from "path";
1205
+ import { isAbsolute, join as join7 } from "path";
1199
1206
  var CONFIG_FILE_NAME = "components.json";
1200
- var CONFIG_SCHEMA_URL = "https://aplance-ui.dev/schema.json";
1207
+ var CONFIG_SCHEMA_URL = "https://ui.aplance.com/components.schema.json";
1201
1208
  var SUPPORTED_TEMPLATES = [
1202
1209
  "vite-react",
1203
1210
  "next",
@@ -1219,21 +1226,6 @@ var TEMPLATE_DEFAULT_SRCDIR = {
1219
1226
  astro: "src",
1220
1227
  "electron-vite": "src/renderer/src"
1221
1228
  };
1222
- var makeDefaultConfig = (opts) => ({
1223
- $schema: CONFIG_SCHEMA_URL,
1224
- template: opts.template,
1225
- srcDir: opts.srcDir ?? TEMPLATE_DEFAULT_SRCDIR[opts.template],
1226
- alias: opts.alias ?? "@",
1227
- theme: {
1228
- color: opts.color ?? "default",
1229
- radius: opts.radius ?? "default",
1230
- spacing: opts.spacing ?? "default"
1231
- },
1232
- paths: { ...DEFAULT_PATHS },
1233
- cssModules: { pretty: opts.pretty ?? false },
1234
- utilities: { enabled: opts.utilities ?? false },
1235
- cssConfig: { placement: opts.cssConfigPlacement ?? "auto" }
1236
- });
1237
1229
  var configPath = (cwd) => join7(cwd, CONFIG_FILE_NAME);
1238
1230
  var configExists = (cwd) => existsSync7(configPath(cwd));
1239
1231
  var loadConfig = (cwd) => {
@@ -1258,6 +1250,10 @@ var loadConfig = (cwd) => {
1258
1250
  return validateConfig(parsed, p3);
1259
1251
  };
1260
1252
  var resolvePaths = (cwd, cfg) => {
1253
+ assertSafeRelativePath("srcDir", cfg.srcDir);
1254
+ for (const key of ["styles", "components", "hooks", "lib"]) {
1255
+ assertSafeRelativePath(`paths.${key}`, cfg.paths[key]);
1256
+ }
1261
1257
  const srcDir = join7(cwd, cfg.srcDir);
1262
1258
  return {
1263
1259
  srcDir,
@@ -1281,6 +1277,37 @@ var normalizeTheme = (raw) => {
1281
1277
  }
1282
1278
  return { color: "default", radius: "default", spacing: "default" };
1283
1279
  };
1280
+ var isSafeRelativePath = (value) => {
1281
+ if (value.includes("\0") || isAbsolute(value)) return false;
1282
+ return !value.split(/[\\/]+/).some((segment) => segment === "..");
1283
+ };
1284
+ var assertSafeRelativePath = (label, value) => {
1285
+ if (!isSafeRelativePath(value)) {
1286
+ throw new ConfigError(
1287
+ "E_CONFIG_INVALID",
1288
+ `${label} must be a relative path inside the project.`
1289
+ );
1290
+ }
1291
+ };
1292
+ var makeDefaultConfig = (opts) => {
1293
+ const srcDir = opts.srcDir ?? TEMPLATE_DEFAULT_SRCDIR[opts.template];
1294
+ assertSafeRelativePath("srcDir", srcDir);
1295
+ return {
1296
+ $schema: CONFIG_SCHEMA_URL,
1297
+ template: opts.template,
1298
+ srcDir,
1299
+ alias: opts.alias ?? "@",
1300
+ theme: {
1301
+ color: opts.color ?? "default",
1302
+ radius: opts.radius ?? "default",
1303
+ spacing: opts.spacing ?? "default"
1304
+ },
1305
+ paths: { ...DEFAULT_PATHS },
1306
+ cssModules: { pretty: opts.pretty ?? false },
1307
+ utilities: { enabled: opts.utilities ?? false },
1308
+ cssConfig: { placement: opts.cssConfigPlacement ?? "auto" }
1309
+ };
1310
+ };
1284
1311
  var validateConfig = (raw, file) => {
1285
1312
  if (!raw || typeof raw !== "object") {
1286
1313
  throw new ConfigError(
@@ -1298,11 +1325,13 @@ var validateConfig = (raw, file) => {
1298
1325
  `"template" must be one of: ${SUPPORTED_TEMPLATES.join(", ")} (got "${c.template}").`
1299
1326
  );
1300
1327
  must(typeof c.srcDir === "string" && c.srcDir.length > 0, `"srcDir" must be a non-empty string.`);
1328
+ must(isSafeRelativePath(c.srcDir), `"srcDir" must be a relative path inside the project.`);
1301
1329
  must(typeof c.alias === "string" && c.alias.length > 0, `"alias" must be a non-empty string.`);
1302
1330
  must(c.paths && typeof c.paths === "object", `"paths" must be an object.`);
1303
1331
  const ps = c.paths;
1304
1332
  for (const k of ["styles", "components", "hooks", "lib"]) {
1305
1333
  must(typeof ps[k] === "string" && ps[k].length > 0, `"paths.${k}" must be a non-empty string.`);
1334
+ must(isSafeRelativePath(ps[k]), `"paths.${k}" must be a relative path inside "srcDir".`);
1306
1335
  }
1307
1336
  must(c.cssModules && typeof c.cssModules === "object", `"cssModules" must be an object.`);
1308
1337
  must(typeof c.cssModules.pretty === "boolean", `"cssModules.pretty" must be a boolean.`);
@@ -1488,6 +1517,32 @@ var describePlan = (plan) => {
1488
1517
  return lines.join("\n");
1489
1518
  };
1490
1519
 
1520
+ // src/lib/unit-hints.ts
1521
+ var importPath = (cfg, path) => `${cfg.alias}/${path}`.replace(/\/+/g, "/");
1522
+ var getUnitHints = ({ cfg, units }) => {
1523
+ const selected = new Set(units.map((unit) => unit.name));
1524
+ const hints = [];
1525
+ if (selected.has("toast")) {
1526
+ const toastImport = importPath(
1527
+ cfg,
1528
+ `${cfg.paths.components}/ui/toast/toast`
1529
+ );
1530
+ hints.push(
1531
+ [
1532
+ "Toast needs one app-level Toaster mount before toast calls render.",
1533
+ `import { Toaster } from "${toastImport}";`,
1534
+ 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."
1535
+ ].join("\n")
1536
+ );
1537
+ }
1538
+ if (cfg.template === "next" && units.some((unit) => (unit.clientFiles?.length ?? 0) > 0)) {
1539
+ hints.push(
1540
+ 'Next.js output adds "use client" only to files that need client runtime APIs.'
1541
+ );
1542
+ }
1543
+ return hints;
1544
+ };
1545
+
1491
1546
  // src/commands/init.ts
1492
1547
  var resolveConflictMode = (opts) => {
1493
1548
  if (opts.dryRun) return "dry-run";
@@ -1634,6 +1689,9 @@ var runInit = async (opts) => {
1634
1689
  const registry = loadRegistry();
1635
1690
  const project = detectProject(opts.cwd);
1636
1691
  const { cfg, existed } = await resolveConfig(opts, project, registry);
1692
+ findColor(registry, cfg.theme.color);
1693
+ findRadius(registry, cfg.theme.radius);
1694
+ findSpacing(registry, cfg.theme.spacing);
1637
1695
  const placement = resolveEffectivePlacement(
1638
1696
  cfg.template,
1639
1697
  cfg.cssConfig.placement,
@@ -1690,7 +1748,8 @@ var runInit = async (opts) => {
1690
1748
  registry,
1691
1749
  targetSrcDir: paths.srcDir,
1692
1750
  units,
1693
- paths: cfg.paths
1751
+ paths: cfg.paths,
1752
+ template: cfg.template
1694
1753
  })
1695
1754
  );
1696
1755
  info(`planned ${units.length} unit${units.length === 1 ? "" : "s"} to copy`);
@@ -1755,6 +1814,10 @@ var runInit = async (opts) => {
1755
1814
  `manual install (skipped)`
1756
1815
  );
1757
1816
  }
1817
+ const unitHints = getUnitHints({ cfg, units });
1818
+ if (unitHints.length > 0) {
1819
+ note2(unitHints.join("\n\n"), "usage hints");
1820
+ }
1758
1821
  const steps = [
1759
1822
  `${pc4.cyan("1")} ${pc4.bold("Import styles once at your app entry")}
1760
1823
  import "./${cfg.paths.styles}/globals.css";`
@@ -1812,7 +1875,8 @@ var runAdd = async (opts) => {
1812
1875
  registry,
1813
1876
  targetSrcDir: paths.srcDir,
1814
1877
  units,
1815
- paths: cfg.paths
1878
+ paths: cfg.paths,
1879
+ template: cfg.template
1816
1880
  });
1817
1881
  if (cfg.utilities.enabled) {
1818
1882
  ops.push(planUtilitiesGitignoreOp({ targetStylesDir: paths.stylesDir }));
@@ -1875,21 +1939,31 @@ var runAdd = async (opts) => {
1875
1939
  "manual install (skipped)"
1876
1940
  );
1877
1941
  }
1942
+ const unitHints = getUnitHints({ cfg, units });
1943
+ if (unitHints.length > 0) {
1944
+ note2(unitHints.join("\n\n"), "usage hints");
1945
+ }
1878
1946
  logger.info(summary);
1879
1947
  goodbye(`Added ${units.length} unit${units.length === 1 ? "" : "s"}.`);
1880
1948
  };
1881
1949
  var computeRuntimePlan = (registry, units, project) => {
1882
1950
  const dependencies = {};
1951
+ const skipped = [];
1883
1952
  const meta = registry.installMeta;
1884
1953
  for (const unit of units) {
1885
1954
  for (const pkg2 of unit.externalDeps) {
1886
1955
  const version = meta.packageVersions[pkg2];
1887
1956
  if (!version) continue;
1888
- if (project.declaredDeps.has(pkg2)) continue;
1957
+ if (project.declaredDeps.has(pkg2)) {
1958
+ if (!skipped.some((entry) => entry.name === pkg2)) {
1959
+ skipped.push({ name: pkg2, reason: "already-declared" });
1960
+ }
1961
+ continue;
1962
+ }
1889
1963
  dependencies[pkg2] = version;
1890
1964
  }
1891
1965
  }
1892
- return { dependencies, devDependencies: {}, skipped: [] };
1966
+ return { dependencies, devDependencies: {}, skipped };
1893
1967
  };
1894
1968
  var pickUnits = async (all) => multiselect2({
1895
1969
  message: `Select units to add ${pc5.dim("(space to toggle, enter to confirm)")}`,