@axis-ds/mcp 1.4.7 → 1.4.8

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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +315 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,7 @@ Claude Desktop 또는 기타 MCP 호환 클라이언트에서 설정:
42
42
  - `targetDir` 는 이미 있는 프로젝트 루트입니다(상대 경로는 서버의 현재 디렉터리 기준). `axis.config.json` 이 있으면 그 `componentsDir` · `agenticDir` 에, 없으면 `components/ui` · `components/agentic` 에 설치하고 그 사실을 알립니다. 프로젝트 밖으로 나가는 경로는 아무것도 쓰지 않고 실패합니다.
43
43
  - 내용이 다른 기존 파일은 덮어쓰지 않고 건너뛰며 응답 첫머리에 건너뛴 수를 적습니다. `overwrite: true` 일 때만 덮어씁니다.
44
44
  - 의존성은 설치하지 않고 lockfile 에 맞는 명령(`pnpm add` · `npm install` · `yarn add`)만 안내합니다. lockfile 이 없으면 `package.json` 의 `packageManager` 를 봅니다(1.4.2 부터). `axis.apply_template` 의 「필요한 의존성」 · `axis.list_brands` · `axis.get_brand` 의 설치 안내도 1.4.2 부터 같은 방식으로 `targetDir` 의 매니저를 따릅니다(1.4.1 까지는 `pnpm` 고정).
45
+ - `axis.apply_template` 은 `targetDir` 에 이미 있는 `package.json` · `axis.config.json`(템플릿 루트의 것)을 덮어쓰지 않고 CLI `axis template apply` 와 같은 규칙으로 합칩니다(WI-0088). `packageManager` 등 사용자 필드가 남고, 결과의 파일 목록에 「합치기」로 표시합니다. 파일을 쓰는 도중 실패하면 건드린 파일을 되돌리고 오류로 응답합니다. 템플릿 경로가 `targetDir` 밖(절대 경로 · `..` · 밖을 가리키는 심볼릭 링크 경유)이면 `axis.apply_template` · `axis.diff_template` 은 아무 파일도 쓰거나 읽지 않고 오류로 응답합니다.
45
46
 
46
47
  ## 토큰 도구
47
48
 
package/dist/index.js CHANGED
@@ -58,8 +58,8 @@ function listTokens(category) {
58
58
  }
59
59
  return tokens;
60
60
  }
61
- function getToken(path11) {
62
- const q = path11.trim().toLowerCase();
61
+ function getToken(path12) {
62
+ const q = path12.trim().toLowerCase();
63
63
  return loadTokens().tokens.find(
64
64
  (t) => t.path.toLowerCase() === q || (t.aliases ?? []).some((a) => a.toLowerCase() === q) || t.cssVariable.toLowerCase() === q
65
65
  );
@@ -1020,6 +1020,9 @@ function dependencyInstallGuide(baseDirInput, name, range) {
1020
1020
  ...base
1021
1021
  };
1022
1022
  }
1023
+ function installCommandPhrase(installCommand, rootDeclared) {
1024
+ return rootDeclared && packageManagerOf(installCommand) === "npm" ? `\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8\uC5D0\uC11C ${installCommand}` : installCommand;
1025
+ }
1023
1026
  function isPlainObject(value) {
1024
1027
  return typeof value === "object" && value !== null && !Array.isArray(value);
1025
1028
  }
@@ -1195,20 +1198,20 @@ function splitPackageSpec(spec) {
1195
1198
  if (parts.length < n || parts.slice(0, n).some((p) => p === "" || p === "." || p === "..")) return void 0;
1196
1199
  return { name: parts.slice(0, n).join("/"), rest: parts.slice(n).join("/") };
1197
1200
  }
1198
- function resolveExportTarget(value) {
1201
+ function resolveExportTarget(value, conditions = CSS_EXPORT_CONDITIONS) {
1199
1202
  if (typeof value === "string") return value;
1200
1203
  if (Array.isArray(value)) {
1201
1204
  for (const v of value) {
1202
- const r = resolveExportTarget(v);
1205
+ const r = resolveExportTarget(v, conditions);
1203
1206
  if (r !== void 0) return r;
1204
1207
  }
1205
1208
  return void 0;
1206
1209
  }
1207
1210
  if (isPlainObject(value)) {
1208
1211
  for (const [key, v] of Object.entries(value)) {
1209
- if (!CSS_EXPORT_CONDITIONS.has(key)) continue;
1212
+ if (!conditions.has(key)) continue;
1210
1213
  if (v === null) return void 0;
1211
- const r = resolveExportTarget(v);
1214
+ const r = resolveExportTarget(v, conditions);
1212
1215
  if (r !== void 0) return r;
1213
1216
  }
1214
1217
  }
@@ -1217,10 +1220,10 @@ function resolveExportTarget(value) {
1217
1220
  function cssFileCandidates(p) {
1218
1221
  return [p, `${p}.css`, path3.join(p, "index.css")];
1219
1222
  }
1220
- function exportsTarget(exportsField, subpath) {
1223
+ function exportsTarget(exportsField, subpath, conditions = CSS_EXPORT_CONDITIONS) {
1221
1224
  const map = isPlainObject(exportsField) && Object.keys(exportsField).some((k) => k.startsWith(".")) ? exportsField : { ".": exportsField };
1222
1225
  if (Object.prototype.hasOwnProperty.call(map, subpath)) {
1223
- const t = resolveExportTarget(map[subpath]);
1226
+ const t = resolveExportTarget(map[subpath], conditions);
1224
1227
  if (t === void 0) return void 0;
1225
1228
  return t.includes("*") || !t.startsWith("./") ? "?" : t;
1226
1229
  }
@@ -1228,6 +1231,9 @@ function exportsTarget(exportsField, subpath) {
1228
1231
  return void 0;
1229
1232
  }
1230
1233
  function resolveCssPackageImport(fromDir, spec) {
1234
+ return resolvePackageSubpath(fromDir, spec, CSS_EXPORT_CONDITIONS, cssFileCandidates);
1235
+ }
1236
+ function resolvePackageSubpath(fromDir, spec, conditions, candidates) {
1231
1237
  const parsed = splitPackageSpec(spec);
1232
1238
  if (parsed === void 0 || parsed.rest === "") return { kind: "unknown" };
1233
1239
  const subpath = `./${parsed.rest}`;
@@ -1244,16 +1250,16 @@ function resolveCssPackageImport(fromDir, spec) {
1244
1250
  }
1245
1251
  if (manifest !== void 0 && manifest.exports) {
1246
1252
  const version = exactVersionOf(manifest.version);
1247
- const target = exportsTarget(manifest.exports, subpath);
1253
+ const target = exportsTarget(manifest.exports, subpath, conditions);
1248
1254
  if (target === "?") return { kind: "unknown" };
1249
1255
  if (target === void 0) {
1250
1256
  return version !== void 0 ? { kind: "not-exported", pkgDir, subpath, version } : { kind: "not-exported", pkgDir, subpath };
1251
1257
  }
1252
- const file = cssFileCandidates(path3.join(pkgDir, target)).find(isFile);
1258
+ const file = candidates(path3.join(pkgDir, target)).find(isFile);
1253
1259
  if (file !== void 0) return { kind: "resolved", pkgDir, file };
1254
1260
  return version !== void 0 ? { kind: "target-missing", pkgDir, subpath, target, version } : { kind: "target-missing", pkgDir, subpath, target };
1255
1261
  }
1256
- const bare = cssFileCandidates(path3.join(pkgDir, parsed.rest)).find(isFile);
1262
+ const bare = candidates(path3.join(pkgDir, parsed.rest)).find(isFile);
1257
1263
  if (bare !== void 0) return { kind: "resolved", pkgDir, file: bare };
1258
1264
  }
1259
1265
  const parent = path3.dirname(dir);
@@ -1304,6 +1310,25 @@ function cssPackageLineResolution(baseDir, lineDir, spec, otherDirs = []) {
1304
1310
  }
1305
1311
  return { kind: "ok" };
1306
1312
  }
1313
+ var JS_REQUIRE_CONDITIONS = /* @__PURE__ */ new Set(["require", "node", "module-sync", "default"]);
1314
+ var JS_IMPORT_CONDITIONS = /* @__PURE__ */ new Set(["import", "node", "module-sync", "default"]);
1315
+ function jsFileCandidates(p) {
1316
+ return [p, `${p}.js`, `${p}.json`, `${p}.node`, path3.join(p, "index.js"), path3.join(p, "index.json"), path3.join(p, "index.node")];
1317
+ }
1318
+ function tailwindConfigImportResolution(baseDir, spec, refs) {
1319
+ if (isPnpProject(baseDir)) return { kind: "ok" };
1320
+ const blocked = (conditions) => {
1321
+ const r = resolvePackageSubpath(baseDir, spec, conditions, jsFileCandidates);
1322
+ return r.kind === "not-exported" ? r : void 0;
1323
+ };
1324
+ const viaRequire = refs.require || refs.import ? blocked(JS_REQUIRE_CONDITIONS) : void 0;
1325
+ if (refs.require && viaRequire !== void 0) return viaRequire;
1326
+ if (refs.import && viaRequire !== void 0) {
1327
+ const viaImport = blocked(JS_IMPORT_CONDITIONS);
1328
+ if (viaImport !== void 0) return viaImport;
1329
+ }
1330
+ return { kind: "ok" };
1331
+ }
1307
1332
  function findPnpUnpluggedPackages(baseDirInput, name, range) {
1308
1333
  const root = pnpProjectRoot(baseDirInput);
1309
1334
  if (root === void 0) return [];
@@ -1328,6 +1353,17 @@ function findPnpUnpluggedPackages(baseDirInput, name, range) {
1328
1353
  }
1329
1354
  return found;
1330
1355
  }
1356
+ var PNP_REAL_DIR_RANGE = /^(?:workspace|portal|link):/;
1357
+ function pnpComponentSourceHidden(baseDirInput, name, range) {
1358
+ if (!isPnpProject(baseDirInput)) return false;
1359
+ const r = range.trim();
1360
+ if (PNP_REAL_DIR_RANGE.test(r)) return false;
1361
+ const probe = versionSatisfiesRange("0.0.0", r) === void 0 ? "*" : r;
1362
+ return findPnpUnpluggedPackages(baseDirInput, name, probe).length === 0;
1363
+ }
1364
+ function pnpUnplugSetupPhrase(name) {
1365
+ return `Plug'n'Play \uB8E8\uD2B8(.pnp.cjs \uAC00 \uC788\uB294 \uACF3) package.json \uC758 dependenciesMeta \uC5D0 "${name}": { "unplugged": true } \uB97C \uB450\uACE0 yarn install`;
1366
+ }
1331
1367
  function parsePartialVersion(token) {
1332
1368
  const m = /^v?(\d+|[xX*])(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(token.trim());
1333
1369
  if (!m) return void 0;
@@ -1765,18 +1801,19 @@ function packageNameOfSpec(spec) {
1765
1801
  function displayRel(baseDir, p) {
1766
1802
  return relative(baseDir, p).split(sep).join("/");
1767
1803
  }
1768
- function packageLineShadowIssue(baseDir, expanded, origin, spec) {
1804
+ function packageLineShadowIssue(baseDir, expanded, origin, spec, opts = {}) {
1769
1805
  const name = packageNameOfSpec(spec);
1770
1806
  const lineDir = dirname2(origin.path);
1771
1807
  const others = () => [...new Set(expanded.files.map((f) => dirname2(f.path)))].filter((d) => d !== lineDir);
1772
1808
  const r = cssPackageLineResolution(baseDir, lineDir, spec, others);
1773
1809
  const head = `${spec} import \uAC00 \uC788\uC9C0\uB9CC \uADF8 \uC904\uC774 \uC788\uB294 \uD30C\uC77C \uC704\uCE58\uC5D0\uC11C`;
1774
1810
  const label = (pkgDir, version) => `${displayRel(baseDir, pkgDir)}(${version ?? "\uBC84\uC804 \uBAA8\uB984"})`;
1811
+ if (opts.tailwind3 === true && r.kind !== "shadow-not-exported") return void 0;
1775
1812
  switch (r.kind) {
1776
1813
  case "ok":
1777
1814
  return void 0;
1778
1815
  case "shadow-not-exported": {
1779
- const current = r.version !== void 0 && (compareExactVersions(r.version, TAILWIND4_MIN_AXIS_VERSION) ?? -1) >= 0;
1816
+ const current = opts.tailwind3 === true || r.version !== void 0 && (compareExactVersions(r.version, TAILWIND4_MIN_AXIS_VERSION) ?? -1) >= 0;
1780
1817
  const todo = current ? "\uB2E4\uC2DC \uC124\uCE58\uD558\uC138\uC694" : `${TAILWIND4_MIN_AXIS_VERSION} \uC774\uC0C1\uC73C\uB85C \uB9DE\uCD94\uC138\uC694`;
1781
1818
  return `${head} \uBA3C\uC800 \uC7A1\uD788\uB294 ${label(r.pkgDir, r.version)}\uAC00 ${r.subpath} \uB97C \uB0B4\uBCF4\uB0B4\uC9C0 \uC54A\uC544 \uD574\uC11D\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2192 \uADF8 \uC911\uCCA9 node_modules \uC758 ${name} \uB97C \uC9C0\uC6B0\uAC70\uB098 ${todo}`;
1782
1819
  }
@@ -1819,13 +1856,20 @@ function judgeGlobalsCss(baseDir, selection, candidate, expanded) {
1819
1856
  const origin = evidenceOrigin(expanded, (f) => findTokensCssImport(f) === imported);
1820
1857
  via = cssImportViaNote(viaFiles([origin]));
1821
1858
  const tailwind4 = (detectTailwind(baseDir, pkg).major ?? 0) >= 4;
1822
- const issue = tailwind4 && origin !== void 0 && !isOutsideEntry(origin) ? tokensCssUnresolvedIssue(
1859
+ const inside = origin !== void 0 && !isOutsideEntry(origin);
1860
+ const issue = tailwind4 && inside ? tokensCssUnresolvedIssue(
1823
1861
  baseDir,
1824
1862
  pkg,
1825
1863
  imported,
1826
1864
  void 0,
1827
1865
  () => packageLineShadowIssue(baseDir, expanded, origin, imported)
1828
- ) : tokensCssUnresolvedIssue(baseDir, pkg, imported, origin !== void 0 ? origin.cssDir : outsideCssDir(selection));
1866
+ ) : tokensCssUnresolvedIssue(
1867
+ baseDir,
1868
+ pkg,
1869
+ imported,
1870
+ origin !== void 0 ? origin.cssDir : outsideCssDir(selection),
1871
+ inside ? () => packageLineShadowIssue(baseDir, expanded, origin, imported, { tailwind3: true }) : void 0
1872
+ );
1829
1873
  if (issue !== void 0) unresolved = `${candidate}\uC5D0 ${issue}`;
1830
1874
  else pass = `${candidate}\uC5D0 AXIS CSS \uBCC0\uC218\uAC00 \uC788\uC2B5\uB2C8\uB2E4 (${imported} import)`;
1831
1875
  } else if (AXIS_VARIABLE_DEFINITION.test(stripCssComments(expanded.evidence))) {
@@ -2029,6 +2073,8 @@ function stripJsComments(src) {
2029
2073
  }
2030
2074
  var TOKENS_TAILWIND3_PRESET = "@axis-ds/tokens/tailwind";
2031
2075
  var PRESET_REFERENCE = /(?:\brequire\s*\(\s*|\bimport\s*\(\s*|\bfrom\s*|\bimport\s+)(["'])@axis-ds\/tokens\/tailwind\1/;
2076
+ var PRESET_REQUIRE_REFERENCE = /\brequire\s*\(\s*(["'])@axis-ds\/tokens\/tailwind\1/;
2077
+ var PRESET_IMPORT_REFERENCE = /(?:\bimport\s*\(\s*|\bfrom\s*|\bimport\s+)(["'])@axis-ds\/tokens\/tailwind\1/;
2032
2078
  function hasTokensPresetReference(source) {
2033
2079
  return PRESET_REFERENCE.test(stripJsComments(source));
2034
2080
  }
@@ -2056,7 +2102,7 @@ function hasBareCjsPresetRequire(code) {
2056
2102
  function isCommonJsConfig(file, source) {
2057
2103
  return extname(file) === ".cjs" || /\bmodule\.exports\b/.test(source);
2058
2104
  }
2059
- function presetTokensUnresolvedMessage(baseDir, pkg, file, bareCjs) {
2105
+ function presetTokensUnresolvedMessage(baseDir, pkg, file, bareCjs, code) {
2060
2106
  const r = tokensCssResolution(baseDir, pkg, TOKENS_PACKAGE_NAME);
2061
2107
  const head = `${file}\uC5D0 AXIS preset \uCC38\uC870\uAC00 \uC788\uC9C0\uB9CC ${TOKENS_PACKAGE_NAME} \uAC00`;
2062
2108
  if (r.kind === "not-installed") {
@@ -2065,6 +2111,14 @@ function presetTokensUnresolvedMessage(baseDir, pkg, file, bareCjs) {
2065
2111
  if (r.kind === "pnp-root-only" && !bareCjs) {
2066
2112
  return `${head} ${PNP_ROOT_ONLY_NOTE}(\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8 \uC120\uC5B8 ${r.range}) \u2192 \uC571 package.json \uC5D0 ${TOKENS_PACKAGE_NAME} \uB97C \uC120\uC5B8\uD558\uC138\uC694`;
2067
2113
  }
2114
+ const e = tailwindConfigImportResolution(baseDir, TOKENS_TAILWIND3_PRESET, {
2115
+ require: PRESET_REQUIRE_REFERENCE.test(code),
2116
+ import: PRESET_IMPORT_REFERENCE.test(code)
2117
+ });
2118
+ if (e.kind === "not-exported") {
2119
+ const label = `${displayRel(baseDir, e.pkgDir)}(${e.version ?? "\uBC84\uC804 \uBAA8\uB984"})`;
2120
+ return `${file}\uC5D0 AXIS preset \uCC38\uC870\uAC00 \uC788\uC9C0\uB9CC \uBA3C\uC800 \uC7A1\uD788\uB294 ${label}\uAC00 ${e.subpath} \uB97C \uB0B4\uBCF4\uB0B4\uC9C0 \uC54A\uC544 \uD574\uC11D\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2192 \uADF8 node_modules \uC758 ${TOKENS_PACKAGE_NAME} \uB97C \uC9C0\uC6B0\uAC70\uB098 \uB2E4\uC2DC \uC124\uCE58\uD558\uC138\uC694`;
2121
+ }
2068
2122
  return void 0;
2069
2123
  }
2070
2124
  function packageVersionGate(baseDirInput, pkg, name, min, options = {}) {
@@ -2119,7 +2173,8 @@ function tailwind4Issues(input, pkg, versions, baseDir) {
2119
2173
  const r = resolutions.get(name);
2120
2174
  if (r.kind === "undeclared" || r.kind === "app-lockfile") continue;
2121
2175
  if (hasComponentTailwindImport(evidence, name) || hasComponentSource(evidence, name)) continue;
2122
- if (componentCssResolvable(r)) missing.push(name);
2176
+ if (r.kind === "app" && pnpComponentSourceHidden(baseDir, name, r.range)) unresolvedMissing.push(componentCssPnpMissingIssue(name));
2177
+ else if (componentCssResolvable(r)) missing.push(name);
2123
2178
  else unresolvedMissing.push(componentCssMissingIssue(baseDir, name, r));
2124
2179
  }
2125
2180
  if (missing.length > 0) {
@@ -2135,12 +2190,17 @@ function tailwind4Issues(input, pkg, versions, baseDir) {
2135
2190
  }
2136
2191
  for (const name of TAILWIND4_COMPONENT_PACKAGES) {
2137
2192
  if (!hasComponentTailwindImport(evidence, name)) continue;
2138
- const unresolved = componentCssPresentIssue(baseDir, name, resolutions.get(name));
2193
+ const r = resolutions.get(name);
2194
+ const unresolved = componentCssPresentIssue(baseDir, name, r);
2139
2195
  const spec = `${name}/tailwind.css`;
2140
2196
  if (unresolved !== void 0) {
2141
2197
  issues.push(unresolved);
2142
2198
  continue;
2143
2199
  }
2200
+ if (r.kind === "app" && pnpComponentSourceHidden(baseDir, name, r.range)) {
2201
+ issues.push(componentCssPnpPresentIssue(name));
2202
+ continue;
2203
+ }
2144
2204
  const shadow = input.lineIssue === void 0 ? false : input.lineIssue(spec);
2145
2205
  let lineProblem;
2146
2206
  if (shadow !== false) lineProblem = shadow;
@@ -2159,6 +2219,13 @@ function componentInstallPhrase(baseDir, name, range) {
2159
2219
  const g = dependencyInstallGuide(baseDir, name, range);
2160
2220
  return g.kind === "install-at-root" ? `\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8\uC5D0\uC11C ${g.installCommand}` : g.installCommand;
2161
2221
  }
2222
+ var PNP_ZIP_SOURCE_NOTE = `Plug'n'Play \uC5D0\uC11C @source "./dist" \uAC00 .yarn/cache zip \uC548\uC744 \uAC00\uB9AC\uCF1C \uCEF4\uD3EC\uB10C\uD2B8 \uD074\uB798\uC2A4\uAC00 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`;
2223
+ function componentCssPnpMissingIssue(name) {
2224
+ return `${name} \uC758 tailwind.css \uB294 ${PNP_ZIP_SOURCE_NOTE} \u2192 ${pnpUnplugSetupPhrase(name)} \uB4A4 ${componentTailwindImportLine(name)}`;
2225
+ }
2226
+ function componentCssPnpPresentIssue(name) {
2227
+ return `${name}/tailwind.css import \uAC00 \uC788\uC9C0\uB9CC ${PNP_ZIP_SOURCE_NOTE} \u2192 ${pnpUnplugSetupPhrase(name)} \uD558\uC138\uC694`;
2228
+ }
2162
2229
  function componentCssMissingIssue(baseDir, name, r) {
2163
2230
  const line = componentTailwindImportLine(name);
2164
2231
  switch (r.kind) {
@@ -2254,13 +2321,13 @@ function checkTailwindSetup(baseDirInput) {
2254
2321
  };
2255
2322
  }
2256
2323
  for (const file of TAILWIND_CONFIG_FILES) {
2257
- const path11 = join2(baseDir, file);
2258
- if (!existsSync3(path11)) continue;
2259
- const content = readFileSync4(path11, "utf8");
2324
+ const path12 = join2(baseDir, file);
2325
+ if (!existsSync3(path12)) continue;
2326
+ const content = readFileSync4(path12, "utf8");
2260
2327
  if (hasTokensPresetReference(content)) {
2261
2328
  const code = stripJsComments(content);
2262
2329
  const bareCjs = isCommonJsConfig(file, code) && hasBareCjsPresetRequire(code);
2263
- const unresolved = presetTokensUnresolvedMessage(baseDir, pkg, file, bareCjs);
2330
+ const unresolved = presetTokensUnresolvedMessage(baseDir, pkg, file, bareCjs, code);
2264
2331
  if (unresolved !== void 0) return { name: "tailwind.config", severity: "warn", message: unresolved };
2265
2332
  if (bareCjs) {
2266
2333
  const gate = packageVersionGate(baseDir, pkg, "@axis-ds/tokens", TOKENS_CJS_PRESET_FIXED_VERSION, { appOnlyInPnp: true });
@@ -3690,11 +3757,11 @@ async function handleGetComponent(params, deps = {}) {
3690
3757
  return { component, source: describeOrigin(client, latest.version), notices };
3691
3758
  }
3692
3759
  function handleGetToken(params) {
3693
- const { path: path11 } = params;
3694
- if (!path11 || path11.trim() === "") {
3760
+ const { path: path12 } = params;
3761
+ if (!path12 || path12.trim() === "") {
3695
3762
  throw new Error("\uD1A0\uD070 \uACBD\uB85C(path)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
3696
3763
  }
3697
- const token = getToken(path11);
3764
+ const token = getToken(path12);
3698
3765
  if (!token) {
3699
3766
  return null;
3700
3767
  }
@@ -3775,9 +3842,9 @@ function cssUsage(token) {
3775
3842
  const shadcnColor = !token.cssVariable.startsWith("--axis-") && token.value.startsWith("hsl(");
3776
3843
  return shadcnColor ? `hsl(var(${token.cssVariable}))` : `var(${token.cssVariable})`;
3777
3844
  }
3778
- function formatTokenNotFound(path11) {
3845
+ function formatTokenNotFound(path12) {
3779
3846
  return [
3780
- `\uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: \`${path11}\``,
3847
+ `\uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: \`${path12}\``,
3781
3848
  "",
3782
3849
  "- \uACBD\uB85C\uB294 `axis.list_tokens` \uB85C \uD655\uC778\uD558\uC138\uC694. \uACBD\uB85C \xB7 tokens \uC774\uB984 \uAE30\uBC18 \uACBD\uB85C(\uC608: `control.h.md`) \xB7 CSS \uBCC0\uC218 \uC774\uB984(\uC608: `--axis-control-h-md`)\uC744 \uBC1B\uC2B5\uB2C8\uB2E4.",
3783
3850
  "- \uBE0C\uB79C\uB4DC \uAC12\uC740 `axis.list_brands` \xB7 `axis.get_brand` \uB85C \uC870\uD68C\uD558\uC138\uC694."
@@ -5475,7 +5542,7 @@ function formatSaveResult(result) {
5475
5542
  // src/tools/template.ts
5476
5543
  import fetch from "node-fetch";
5477
5544
  import fs6 from "fs-extra";
5478
- import path9 from "path";
5545
+ import path10 from "path";
5479
5546
 
5480
5547
  // src/tools/brand-core.ts
5481
5548
  import { createHash as createHash2 } from "crypto";
@@ -6035,11 +6102,15 @@ function checkDependencies(baseDirInput) {
6035
6102
  if (missingRecommended.length > 0) problems.push(`\uAD8C\uC7A5 \uC758\uC874\uC131 \uB204\uB77D: ${missingRecommended.join(", ")}`);
6036
6103
  if (pnpRootRecommended.length > 0) problems.push(`\uAD8C\uC7A5 \uC758\uC874\uC131 ${pnpRootRecommended.join(", ")} \uAC00 ${PNP_ROOT_ONLY_NOTE} \u2192 \uC571 package.json \uC5D0 \uC120\uC5B8\uD558\uC138\uC694`);
6037
6104
  if (pnpRootAxis.length > 0) problems.push(`${pnpRootAxis.join(", ")} \uAC00 ${PNP_ROOT_ONLY_NOTE} \u2192 \uC571 package.json \uC5D0 \uC120\uC5B8\uD558\uC138\uC694`);
6105
+ const installCommand = detectInstallCommand(baseDir);
6038
6106
  if (notInstalledOther.length > 0) {
6039
- problems.push(`\uC120\uC5B8\uB410\uC9C0\uB9CC \uC124\uCE58\uB418\uC9C0 \uC54A\uC740 \uC758\uC874\uC131: ${notInstalledOther.join(", ")} \u2192 ${detectInstallCommand(baseDir)}`);
6107
+ const phrase = installCommandPhrase(installCommand, notInstalledOther.some((d) => fromRoot(d)));
6108
+ problems.push(`\uC120\uC5B8\uB410\uC9C0\uB9CC \uC124\uCE58\uB418\uC9C0 \uC54A\uC740 \uC758\uC874\uC131: ${notInstalledOther.join(", ")} \u2192 ${phrase}`);
6040
6109
  }
6041
6110
  if (notInstalledRoot.length > 0) {
6042
- problems.push(`\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8\uC5D0 \uC120\uC5B8\uB3FC \uC788\uC9C0\uB9CC \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: ${notInstalledRoot.join(", ")} \u2192 ${detectInstallCommand(baseDir)}`);
6111
+ problems.push(
6112
+ `\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8\uC5D0 \uC120\uC5B8\uB3FC \uC788\uC9C0\uB9CC \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: ${notInstalledRoot.join(", ")} \u2192 ${installCommandPhrase(installCommand, true)}`
6113
+ );
6043
6114
  }
6044
6115
  if (problems.length > 0) {
6045
6116
  return { name: "dependencies", severity: "warn", message: problems.join(" / ") + (rootNote ? ` (${rootNote})` : "") };
@@ -6052,6 +6123,192 @@ function checkDependencies(baseDirInput) {
6052
6123
  };
6053
6124
  }
6054
6125
 
6126
+ // ../axis-registry-core/src/template/apply-files.ts
6127
+ import { existsSync as existsSync9, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync10, readdirSync as readdirSync3, realpathSync as realpathSync4, rmdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
6128
+ import path9 from "path";
6129
+ var TemplatePathError = class extends Error {
6130
+ constructor(relPath, reason) {
6131
+ super(`\uD15C\uD50C\uB9BF \uACBD\uB85C '${relPath}' \uAC00 \uB300\uC0C1 \uB514\uB809\uD130\uB9AC \uBC16\uC744 \uAC00\uB9AC\uCF1C(${reason}) \uC911\uB2E8\uD588\uC2B5\uB2C8\uB2E4. \uC544\uBB34 \uD30C\uC77C\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4`);
6132
+ this.relPath = relPath;
6133
+ this.reason = reason;
6134
+ this.name = "TemplatePathError";
6135
+ }
6136
+ };
6137
+ function realpathOfExisting(p) {
6138
+ const rest = [];
6139
+ let cur = p;
6140
+ for (; ; ) {
6141
+ try {
6142
+ lstatSync3(cur);
6143
+ break;
6144
+ } catch {
6145
+ const parent = path9.dirname(cur);
6146
+ if (parent === cur) break;
6147
+ rest.unshift(path9.basename(cur));
6148
+ cur = parent;
6149
+ }
6150
+ }
6151
+ try {
6152
+ return path9.join(realpathSync4(cur), ...rest);
6153
+ } catch {
6154
+ return void 0;
6155
+ }
6156
+ }
6157
+ function isInside(root, target) {
6158
+ const rel2 = path9.relative(root, target);
6159
+ return rel2 !== "" && !rel2.startsWith("..") && !path9.isAbsolute(rel2);
6160
+ }
6161
+ function resolveTemplatePath(baseDir, relPath) {
6162
+ if (typeof relPath !== "string" || relPath === "" || relPath.includes("\0")) {
6163
+ throw new TemplatePathError(String(relPath), "\uBE48 \uACBD\uB85C \uB610\uB294 \uC798\uBABB\uB41C \uBB38\uC790");
6164
+ }
6165
+ if (path9.isAbsolute(relPath) || path9.win32.isAbsolute(relPath)) throw new TemplatePathError(relPath, "\uC808\uB300 \uACBD\uB85C");
6166
+ const base = path9.resolve(baseDir);
6167
+ const dest = path9.resolve(base, relPath);
6168
+ if (!isInside(base, dest)) throw new TemplatePathError(relPath, "\uC0C1\uC704 \uB514\uB809\uD130\uB9AC\uB85C \uB098\uAC10");
6169
+ const baseReal = realpathOfExisting(base);
6170
+ const destReal = realpathOfExisting(dest);
6171
+ if (!baseReal || !destReal || !isInside(baseReal, destReal)) {
6172
+ throw new TemplatePathError(relPath, "\uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uAC00 \uB300\uC0C1 \uB514\uB809\uD130\uB9AC \uBC16\uC744 \uAC00\uB9AC\uD0B4");
6173
+ }
6174
+ return dest;
6175
+ }
6176
+ function assertTemplatePaths(baseDir, relPaths) {
6177
+ for (const p of relPaths) resolveTemplatePath(baseDir, p);
6178
+ }
6179
+ var PACKAGE_JSON_MERGED_MAPS = [
6180
+ "scripts",
6181
+ "dependencies",
6182
+ "devDependencies",
6183
+ "peerDependencies",
6184
+ "optionalDependencies"
6185
+ ];
6186
+ function isPlainObject3(v) {
6187
+ return typeof v === "object" && v !== null && !Array.isArray(v);
6188
+ }
6189
+ function parseObject(text) {
6190
+ try {
6191
+ const v = JSON.parse(text);
6192
+ return isPlainObject3(v) ? v : void 0;
6193
+ } catch {
6194
+ return void 0;
6195
+ }
6196
+ }
6197
+ function mergePackageJson(user, template) {
6198
+ const maps = new Set(PACKAGE_JSON_MERGED_MAPS);
6199
+ const out = {};
6200
+ for (const [key, value] of Object.entries(user)) {
6201
+ const tpl = template[key];
6202
+ if (maps.has(key) && isPlainObject3(tpl)) {
6203
+ out[key] = isPlainObject3(value) ? { ...value, ...tpl } : tpl;
6204
+ } else {
6205
+ out[key] = value;
6206
+ }
6207
+ }
6208
+ for (const [key, value] of Object.entries(template)) {
6209
+ if (!(key in out)) out[key] = value;
6210
+ }
6211
+ return out;
6212
+ }
6213
+ function mergeAxisConfigJson(user, template) {
6214
+ return { ...user, ...template };
6215
+ }
6216
+ function detectJsonIndent(text) {
6217
+ const m = /^([ \t]+)\S/m.exec(text);
6218
+ if (!m) return 2;
6219
+ return m[1].startsWith(" ") ? " " : m[1].length;
6220
+ }
6221
+ function serializeLike(before, value) {
6222
+ const crlf = before.includes("\r\n");
6223
+ const trailing = /\r?\n$/.test(before);
6224
+ let text = JSON.stringify(value, null, detectJsonIndent(before));
6225
+ if (trailing) text += "\n";
6226
+ return crlf ? text.replace(/\n/g, "\r\n") : text;
6227
+ }
6228
+ var MERGERS = {
6229
+ "package.json": mergePackageJson,
6230
+ "axis.config.json": mergeAxisConfigJson
6231
+ };
6232
+ function planTemplateFileContent(relPath, templateContent, before) {
6233
+ if (before === void 0) return { action: "created", content: templateContent };
6234
+ const merger = MERGERS[relPath.replace(/^\.\//, "")];
6235
+ if (!merger) return { action: "overwritten", content: templateContent };
6236
+ const user = parseObject(before);
6237
+ const template = parseObject(templateContent);
6238
+ if (!user || !template) {
6239
+ return {
6240
+ action: "overwritten",
6241
+ content: templateContent,
6242
+ note: `\uAE30\uC874 ${relPath} \uC744 JSON \uAC1D\uCCB4\uB85C \uC77D\uC744 \uC218 \uC5C6\uC5B4 \uD15C\uD50C\uB9BF \uB0B4\uC6A9\uC73C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4`
6243
+ };
6244
+ }
6245
+ return { action: "merged", content: serializeLike(before, merger(user, template)) };
6246
+ }
6247
+ function planTemplateFiles(baseDir, files, extraPaths = []) {
6248
+ const dests = files.map((file) => resolveTemplatePath(baseDir, file.path));
6249
+ assertTemplatePaths(baseDir, extraPaths);
6250
+ return files.map((file, i) => {
6251
+ const dest = dests[i];
6252
+ const before = existsSync9(dest) ? readFileSync10(dest, "utf8") : void 0;
6253
+ return { path: file.path, dest, before, ...planTemplateFileContent(file.path, file.content, before) };
6254
+ });
6255
+ }
6256
+ var TemplateApplyError = class extends Error {
6257
+ constructor(message, restored, unrestored) {
6258
+ super(message);
6259
+ this.restored = restored;
6260
+ this.unrestored = unrestored;
6261
+ this.name = "TemplateApplyError";
6262
+ }
6263
+ };
6264
+ function writeTemplateFiles(plan) {
6265
+ const done = [];
6266
+ const madeDirs = [];
6267
+ try {
6268
+ for (const item of plan) {
6269
+ const dir = path9.dirname(item.dest);
6270
+ if (!existsSync9(dir)) {
6271
+ const fresh = [];
6272
+ for (let d = dir; !existsSync9(d); d = path9.dirname(d)) fresh.unshift(d);
6273
+ mkdirSync2(dir, { recursive: true });
6274
+ madeDirs.push(...fresh);
6275
+ }
6276
+ writeFileSync2(item.dest, item.content);
6277
+ done.push(item);
6278
+ }
6279
+ } catch (err) {
6280
+ const restored = [];
6281
+ const unrestored = [];
6282
+ const failed = plan[done.length];
6283
+ const targets = failed ? [...done, failed] : done;
6284
+ for (const item of targets.reverse()) {
6285
+ try {
6286
+ if (item.before === void 0) {
6287
+ if (existsSync9(item.dest)) unlinkSync(item.dest);
6288
+ } else {
6289
+ writeFileSync2(item.dest, item.before);
6290
+ }
6291
+ restored.push(item.path);
6292
+ } catch {
6293
+ unrestored.push(item.path);
6294
+ }
6295
+ }
6296
+ for (const d of madeDirs.reverse()) {
6297
+ try {
6298
+ if (existsSync9(d) && readdirSync3(d).length === 0) rmdirSync(d);
6299
+ } catch {
6300
+ }
6301
+ }
6302
+ const reason = err instanceof Error ? err.message : String(err);
6303
+ const tail = unrestored.length > 0 ? ` \uB418\uB3CC\uB9AC\uC9C0 \uBABB\uD55C \uD30C\uC77C: ${unrestored.join(", ")}` : "";
6304
+ throw new TemplateApplyError(
6305
+ `\uD15C\uD50C\uB9BF \uD30C\uC77C\uC744 \uC4F0\uB294 \uB3C4\uC911 \uC2E4\uD328\uD574 \uAC74\uB4DC\uB9B0 \uD30C\uC77C ${restored.length}\uAC1C\uB97C \uC6D0\uB798\uB300\uB85C \uB418\uB3CC\uB838\uC2B5\uB2C8\uB2E4(${reason}).${tail}`,
6306
+ restored.reverse(),
6307
+ unrestored
6308
+ );
6309
+ }
6310
+ }
6311
+
6055
6312
  // src/site.ts
6056
6313
  var AXIS_SITE_URL2 = "https://axis-design-system.pages.dev";
6057
6314
 
@@ -6105,24 +6362,18 @@ async function handleGetTemplate(params) {
6105
6362
  }
6106
6363
  async function handleApplyTemplate(params) {
6107
6364
  const detail = await fetchTemplateDetail(params.name);
6108
- const baseDir = path9.resolve(params.targetDir);
6365
+ const baseDir = path10.resolve(params.targetDir);
6109
6366
  const installCommand = detectInstallCommand(baseDir);
6110
- const files = [];
6111
- for (const file of detail.files) {
6112
- const dest = path9.join(baseDir, file.path);
6113
- const exists = await fs6.pathExists(dest);
6114
- if (!params.dryRun) {
6115
- await fs6.ensureDir(path9.dirname(dest));
6116
- await fs6.writeFile(dest, file.content);
6117
- }
6118
- files.push({
6119
- path: file.path,
6120
- action: exists ? "overwritten" : "created"
6121
- });
6122
- }
6367
+ const plan = planTemplateFiles(baseDir, detail.files, (detail.postInstall ?? []).map((p) => p.file));
6368
+ if (!params.dryRun) writeTemplateFiles(plan);
6369
+ const files = plan.map((item) => ({
6370
+ path: item.path,
6371
+ action: item.action,
6372
+ ...item.note ? { note: item.note } : {}
6373
+ }));
6123
6374
  if (!params.dryRun && detail.postInstall?.length) {
6124
6375
  for (const patch of detail.postInstall) {
6125
- const target = path9.join(baseDir, patch.file);
6376
+ const target = resolveTemplatePath(baseDir, patch.file);
6126
6377
  if (!await fs6.pathExists(target)) continue;
6127
6378
  if (patch.type === "replace" && patch.search && patch.value) {
6128
6379
  const content = await fs6.readFile(target, "utf-8");
@@ -6150,10 +6401,11 @@ async function handleApplyTemplate(params) {
6150
6401
  }
6151
6402
  async function handleDiffTemplate(params) {
6152
6403
  const detail = await fetchTemplateDetail(params.name);
6153
- const baseDir = path9.resolve(params.targetDir || ".");
6404
+ const baseDir = path10.resolve(params.targetDir || ".");
6154
6405
  const entries = [];
6155
- for (const file of detail.files) {
6156
- const localPath = path9.join(baseDir, file.path);
6406
+ const localPaths = detail.files.map((file) => resolveTemplatePath(baseDir, file.path));
6407
+ for (const [i, file] of detail.files.entries()) {
6408
+ const localPath = localPaths[i];
6157
6409
  if (!await fs6.pathExists(localPath)) {
6158
6410
  entries.push({ path: file.path, status: "added" });
6159
6411
  continue;
@@ -6241,9 +6493,9 @@ function brandActivationResult(check) {
6241
6493
  };
6242
6494
  }
6243
6495
  async function handleCheckProject(params) {
6244
- const baseDir = path9.resolve(params.targetDir || ".");
6496
+ const baseDir = path10.resolve(params.targetDir || ".");
6245
6497
  const results = [];
6246
- const configExists = await fs6.pathExists(path9.join(baseDir, "axis.config.json"));
6498
+ const configExists = await fs6.pathExists(path10.join(baseDir, "axis.config.json"));
6247
6499
  results.push({
6248
6500
  name: "axis.config.json",
6249
6501
  severity: configExists ? "pass" : "fail",
@@ -6260,7 +6512,7 @@ async function handleCheckProject(params) {
6260
6512
  ];
6261
6513
  let utilsFound = false;
6262
6514
  for (const candidate of utilsCandidates) {
6263
- const fullPath = path9.join(baseDir, candidate);
6515
+ const fullPath = path10.join(baseDir, candidate);
6264
6516
  if (await fs6.pathExists(fullPath)) {
6265
6517
  const content2 = await fs6.readFile(fullPath, "utf-8");
6266
6518
  const hasCn = content2.includes("function cn");
@@ -6351,9 +6603,15 @@ function formatApplyTemplateResult(result) {
6351
6603
  `);
6352
6604
  if (result.files.length > 0) {
6353
6605
  lines.push("### \uD30C\uC77C", "");
6606
+ const label = {
6607
+ created: "\uC0DD\uC131",
6608
+ overwritten: "\uB36E\uC5B4\uC4F0\uAE30",
6609
+ merged: "\uD569\uCE58\uAE30: \uAE30\uC874 \uD544\uB4DC\uB97C \uB450\uACE0 \uD15C\uD50C\uB9BF \uD0A4\uB97C \uD569\uCE68"
6610
+ };
6354
6611
  for (const f of result.files) {
6355
6612
  const icon = f.action === "created" ? "+" : "~";
6356
- lines.push(`- ${icon} ${f.path} (${f.action === "created" ? "\uC0DD\uC131" : "\uB36E\uC5B4\uC4F0\uAE30"})`);
6613
+ lines.push(`- ${icon} ${f.path} (${label[f.action]})`);
6614
+ if (f.note) lines.push(` - ! ${f.note}`);
6357
6615
  }
6358
6616
  }
6359
6617
  const deps = Object.entries(result.dependencies);
@@ -6405,10 +6663,10 @@ function formatCheckProjectResult(result) {
6405
6663
  }
6406
6664
 
6407
6665
  // src/tools/brand.ts
6408
- import path10 from "path";
6666
+ import path11 from "path";
6409
6667
  var TOKENS_INSTALL_COMMAND = addCommandFor("pnpm install", { [TOKENS_PACKAGE]: `^${TOKENS_DEPENDENCY_MIN_VERSION}` });
6410
6668
  function baseDirOf(targetDir) {
6411
- return path10.resolve(targetDir || process.cwd());
6669
+ return path11.resolve(targetDir || process.cwd());
6412
6670
  }
6413
6671
  function tokensInstallGuideFor(targetDir) {
6414
6672
  return dependencyInstallGuide(baseDirOf(targetDir), TOKENS_PACKAGE, `^${TOKENS_DEPENDENCY_MIN_VERSION}`);
@@ -6696,10 +6954,10 @@ server.tool(
6696
6954
  {
6697
6955
  path: z.string().describe("\uD1A0\uD070 \uACBD\uB85C (\uC608: colors.primary, spacing.4, control.h.md) \uB610\uB294 CSS \uBCC0\uC218 \uC774\uB984 (\uC608: --axis-control-h-md)")
6698
6956
  },
6699
- async ({ path: path11 }) => {
6957
+ async ({ path: path12 }) => {
6700
6958
  try {
6701
- const token = handleGetToken({ path: path11 });
6702
- const formatted = formatTokenDetail(token, path11);
6959
+ const token = handleGetToken({ path: path12 });
6960
+ const formatted = formatTokenDetail(token, path12);
6703
6961
  return {
6704
6962
  content: [
6705
6963
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-ds/mcp",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "type": "module",
5
5
  "description": "AXIS Design System - MCP Server for AI-assisted component discovery",
6
6
  "bin": {