ziku 0.21.3 → 0.22.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.
@@ -0,0 +1,3 @@
1
+ import { d as hashFiles, u as hashContent } from "./index.mjs";
2
+
3
+ export { hashFiles };
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import { defineCommand, runMain } from "citty";
5
5
  import { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
7
7
  import { dirname, join, resolve } from "pathe";
8
+ import * as YAML from "yaml";
8
9
  import { parse, stringify } from "yaml";
9
10
  import { z } from "zod";
10
11
  import { downloadTemplate } from "giget";
@@ -17,6 +18,7 @@ import { match } from "ts-pattern";
17
18
  import { execFileSync } from "node:child_process";
18
19
  import { Octokit } from "@octokit/rest";
19
20
  import { createHash } from "node:crypto";
21
+ import * as TOML from "smol-toml";
20
22
  import { z as z$1 } from "zod/v4";
21
23
 
22
24
  //#region rolldown:runtime
@@ -24,7 +26,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
24
26
 
25
27
  //#endregion
26
28
  //#region package.json
27
- var version$2 = "0.21.3";
29
+ var version$2 = "0.22.0";
28
30
 
29
31
  //#endregion
30
32
  //#region src/modules/schemas.ts
@@ -870,7 +872,7 @@ function getEffectivePatterns(_moduleId, modulePatterns, config) {
870
872
  *
871
873
  * 削除条件: ziku が別の UI フレームワーク(ink 等)に移行する場合。
872
874
  */
873
- const version$1 = "0.21.3";
875
+ const version$1 = "0.22.0";
874
876
  /** CLI の開始表示 */
875
877
  function intro(command) {
876
878
  const title = command ? `ziku ${command}` : "ziku";
@@ -2038,7 +2040,7 @@ async function hashFiles(dir, patterns) {
2038
2040
 
2039
2041
  //#endregion
2040
2042
  //#region src/commands/init.ts
2041
- const version = "0.21.3";
2043
+ const version = "0.22.0";
2042
2044
  const initCommand = defineCommand({
2043
2045
  meta: {
2044
2046
  name: "ziku",
@@ -2396,7 +2398,15 @@ function threeWayMerge({ base, local, template, filePath }) {
2396
2398
  const jsonResult = mergeJsonContent(base, local, template);
2397
2399
  if (jsonResult !== null) return jsonResult;
2398
2400
  }
2399
- return textThreeWayMerge(base, local, template);
2401
+ if (filePath && isTomlFile(filePath)) {
2402
+ const tomlResult = mergeTomlContent(base, local, template);
2403
+ if (tomlResult !== null) return tomlResult;
2404
+ }
2405
+ if (filePath && isYamlFile(filePath)) {
2406
+ const yamlResult = mergeYamlContent(base, local, template);
2407
+ if (yamlResult !== null) return yamlResult;
2408
+ }
2409
+ return textThreeWayMerge(base, local, template, filePath);
2400
2410
  }
2401
2411
  /**
2402
2412
  * JSON/JSONC ファイルをキーレベルで 3-way マージする。
@@ -2528,10 +2538,164 @@ function deepEqual(a, b) {
2528
2538
  if (aKeys.length !== bKeys.length) return false;
2529
2539
  return aKeys.every((key) => key in bObj && deepEqual(aObj[key], bObj[key]));
2530
2540
  }
2541
+ /**
2542
+ * TOML ファイルをキーレベルで 3-way マージする。
2543
+ *
2544
+ * 背景: TOML ファイルにコンフリクトマーカーを挿入するとパーサーが壊れるため、
2545
+ * JSON マージと同様にキーレベルで変更を検出し、非コンフリクト部分を自動マージする。
2546
+ * コンフリクトがあるキーはローカル値を採用し、conflictDetails で報告する。
2547
+ *
2548
+ * 制約: smol-toml の stringify はコメントを保持しないため、マージ結果では
2549
+ * ローカルのコメントが失われる。ただし、壊れた TOML を出力するよりも
2550
+ * 正しい TOML を出力することを優先する。
2551
+ *
2552
+ * @returns マージ結果。TOML パースに失敗した場合は null(テキストマージにフォールバック)。
2553
+ */
2554
+ function mergeTomlContent(base, local, template) {
2555
+ let baseObj;
2556
+ let localObj;
2557
+ let templateObj;
2558
+ try {
2559
+ baseObj = TOML.parse(base);
2560
+ localObj = TOML.parse(local);
2561
+ templateObj = TOML.parse(template);
2562
+ } catch {
2563
+ return null;
2564
+ }
2565
+ const templateDiffs = getJsonDiffs(baseObj, templateObj);
2566
+ const localDiffs = getJsonDiffs(baseObj, localObj);
2567
+ const mergedObj = structuredClone(localObj);
2568
+ const conflictDetails = [];
2569
+ for (const diff of templateDiffs) {
2570
+ if (localDiffs.some((ld) => pathsOverlap(ld.path, diff.path))) {
2571
+ const localVal = getValueAtPath(localObj, diff.path);
2572
+ const templateVal = diff.type === "remove" ? void 0 : diff.value;
2573
+ if (deepEqual(localVal, templateVal)) continue;
2574
+ conflictDetails.push({
2575
+ path: diff.path,
2576
+ localValue: localVal,
2577
+ templateValue: templateVal
2578
+ });
2579
+ continue;
2580
+ }
2581
+ if (diff.type === "remove") deleteAtPath(mergedObj, diff.path);
2582
+ else setAtPath(mergedObj, diff.path, diff.value);
2583
+ }
2584
+ return {
2585
+ content: TOML.stringify(mergedObj),
2586
+ hasConflicts: conflictDetails.length > 0,
2587
+ conflictDetails
2588
+ };
2589
+ }
2590
+ /**
2591
+ * YAML ファイルをキーレベルで 3-way マージする。
2592
+ *
2593
+ * 背景: YAML ファイルもインデントベースの構造を持ち、テキストマージで
2594
+ * 壊れることがある。JSON/TOML と同様にキーレベルでマージする。
2595
+ *
2596
+ * @returns マージ結果。YAML パースに失敗した場合は null(テキストマージにフォールバック)。
2597
+ */
2598
+ function mergeYamlContent(base, local, template) {
2599
+ let baseObj;
2600
+ let localObj;
2601
+ let templateObj;
2602
+ try {
2603
+ baseObj = YAML.parse(base);
2604
+ localObj = YAML.parse(local);
2605
+ templateObj = YAML.parse(template);
2606
+ } catch {
2607
+ return null;
2608
+ }
2609
+ if (baseObj == null || localObj == null || templateObj == null) return null;
2610
+ if (typeof baseObj !== "object" || typeof localObj !== "object" || typeof templateObj !== "object") return null;
2611
+ const templateDiffs = getJsonDiffs(baseObj, templateObj);
2612
+ const localDiffs = getJsonDiffs(baseObj, localObj);
2613
+ const mergedObj = structuredClone(localObj);
2614
+ const conflictDetails = [];
2615
+ for (const diff of templateDiffs) {
2616
+ if (localDiffs.some((ld) => pathsOverlap(ld.path, diff.path))) {
2617
+ const localVal = getValueAtPath(localObj, diff.path);
2618
+ const templateVal = diff.type === "remove" ? void 0 : diff.value;
2619
+ if (deepEqual(localVal, templateVal)) continue;
2620
+ conflictDetails.push({
2621
+ path: diff.path,
2622
+ localValue: localVal,
2623
+ templateValue: templateVal
2624
+ });
2625
+ continue;
2626
+ }
2627
+ if (diff.type === "remove") deleteAtPath(mergedObj, diff.path);
2628
+ else setAtPath(mergedObj, diff.path, diff.value);
2629
+ }
2630
+ return {
2631
+ content: YAML.stringify(mergedObj),
2632
+ hasConflicts: conflictDetails.length > 0,
2633
+ conflictDetails
2634
+ };
2635
+ }
2636
+ /**
2637
+ * ネストされたオブジェクトのパスに値を設定する。
2638
+ * 中間オブジェクトが存在しない場合は自動的に作成する。
2639
+ */
2640
+ function setAtPath(obj, path, value) {
2641
+ let current = obj;
2642
+ for (let i = 0; i < path.length - 1; i++) {
2643
+ const key = path[i];
2644
+ if (current == null || typeof current !== "object") return;
2645
+ const record = current;
2646
+ if (!(key in record) || record[key] == null || typeof record[key] !== "object") record[key] = {};
2647
+ current = record[key];
2648
+ }
2649
+ if (current != null && typeof current === "object") current[path[path.length - 1]] = value;
2650
+ }
2651
+ /**
2652
+ * ネストされたオブジェクトのパスにあるキーを削除する。
2653
+ */
2654
+ function deleteAtPath(obj, path) {
2655
+ let current = obj;
2656
+ for (let i = 0; i < path.length - 1; i++) {
2657
+ const key = path[i];
2658
+ if (current == null || typeof current !== "object") return;
2659
+ current = current[key];
2660
+ }
2661
+ if (current != null && typeof current === "object") delete current[path[path.length - 1]];
2662
+ }
2531
2663
  function isJsonFile(filePath) {
2532
2664
  const lower = filePath.toLowerCase();
2533
2665
  return lower.endsWith(".json") || lower.endsWith(".jsonc");
2534
2666
  }
2667
+ function isTomlFile(filePath) {
2668
+ return filePath.toLowerCase().endsWith(".toml");
2669
+ }
2670
+ function isYamlFile(filePath) {
2671
+ const lower = filePath.toLowerCase();
2672
+ return lower.endsWith(".yml") || lower.endsWith(".yaml");
2673
+ }
2674
+ /**
2675
+ * 構造ファイル(TOML/YAML)のマージ結果をパースして妥当性を検証する。
2676
+ *
2677
+ * 背景: テキストベースの diff/patch は行レベルでマージするため、
2678
+ * fuzz factor でパッチが「成功」しても、TOML のセクション重複や
2679
+ * YAML のインデント崩れ等、構造的に壊れた出力を生むことがある。
2680
+ * git の merge がこのような破損を出さないのに対し、patch ベースの
2681
+ * マージはこの検証が必要。パース失敗時はコンフリクトマーカーに
2682
+ * フォールバックすることで、壊れたファイルの生成を防ぐ。
2683
+ */
2684
+ function validateStructuredContent(content, filePath) {
2685
+ if (isTomlFile(filePath)) try {
2686
+ TOML.parse(content);
2687
+ return true;
2688
+ } catch {
2689
+ return false;
2690
+ }
2691
+ if (isYamlFile(filePath)) try {
2692
+ YAML.parse(content);
2693
+ return true;
2694
+ } catch {
2695
+ return false;
2696
+ }
2697
+ return true;
2698
+ }
2535
2699
  /**
2536
2700
  * テキストファイルの 3-way マージ。fuzz factor によるパッチ適用と
2537
2701
  * hunk 単位のコンフリクトマーカーで、従来のファイル全体マーカーを改善。
@@ -2539,25 +2703,36 @@ function isJsonFile(filePath) {
2539
2703
  * 背景: TOML 等の構造ファイルにファイル全体のコンフリクトマーカーを入れると
2540
2704
  * パーサーが壊れる。hunk 単位にすることで影響範囲を最小化する。
2541
2705
  *
2706
+ * filePath が渡された場合、パッチ適用後に構造ファイルの妥当性を検証する。
2707
+ * fuzz factor でパッチが「成功」しても、TOML のセクション重複等で
2708
+ * 壊れたファイルが生成されることがあるため、パース検証で検出して
2709
+ * コンフリクトマーカーにフォールバックする。
2710
+ *
2542
2711
  * 戦略:
2543
- * 1. 標準パッチ適用(fuzz=0
2544
- * 2. fuzz factor を上げてリトライ(fuzz=2
2712
+ * 1. 標準パッチ適用(fuzz=0)+ 構造検証
2713
+ * 2. fuzz factor を上げてリトライ(fuzz=2)+ 構造検証
2545
2714
  * 3. 失敗時: hunk 単位で適用を試み、失敗した hunk のみにマーカーを付与
2546
2715
  */
2547
- function textThreeWayMerge(base, local, template) {
2716
+ function textThreeWayMerge(base, local, template, filePath) {
2548
2717
  const patch = createPatch("file", base, template);
2549
2718
  const result = applyPatch(local, patch);
2550
- if (typeof result === "string") return {
2551
- content: result,
2552
- hasConflicts: false,
2553
- conflictDetails: []
2554
- };
2719
+ if (typeof result === "string") {
2720
+ if (filePath && !validateStructuredContent(result, filePath)) return mergeWithPerHunkMarkers(base, local, template);
2721
+ return {
2722
+ content: result,
2723
+ hasConflicts: false,
2724
+ conflictDetails: []
2725
+ };
2726
+ }
2555
2727
  const resultFuzzy = applyPatch(local, patch, { fuzzFactor: 2 });
2556
- if (typeof resultFuzzy === "string") return {
2557
- content: resultFuzzy,
2558
- hasConflicts: false,
2559
- conflictDetails: []
2560
- };
2728
+ if (typeof resultFuzzy === "string") {
2729
+ if (filePath && !validateStructuredContent(resultFuzzy, filePath)) return mergeWithPerHunkMarkers(base, local, template);
2730
+ return {
2731
+ content: resultFuzzy,
2732
+ hasConflicts: false,
2733
+ conflictDetails: []
2734
+ };
2735
+ }
2561
2736
  return mergeWithPerHunkMarkers(base, local, template);
2562
2737
  }
2563
2738
  /**
@@ -3364,10 +3539,10 @@ const pushCommand = defineCommand({
3364
3539
  const mergedContents = /* @__PURE__ */ new Map();
3365
3540
  let pushableFilePaths = /* @__PURE__ */ new Set();
3366
3541
  {
3367
- const { hashFiles: hashFiles$1 } = await import("./hash-CjblHutQ.mjs");
3368
- const { classifyFiles: classifyFiles$1 } = await import("./merge-CPx9nRrL.mjs");
3369
- const { getModuleById: getModuleById$1 } = await import("./modules-DlNMFNLz.mjs");
3370
- const { getEffectivePatterns: getEffectivePatterns$1 } = await import("./patterns-CxgZLnrY.mjs");
3542
+ const { hashFiles: hashFiles$1 } = await import("./hash-BeQ4-IoO.mjs");
3543
+ const { classifyFiles: classifyFiles$1 } = await import("./merge-DtLUiQDW.mjs");
3544
+ const { getModuleById: getModuleById$1 } = await import("./modules-BN0Qb_wy.mjs");
3545
+ const { getEffectivePatterns: getEffectivePatterns$1 } = await import("./patterns-BKEQ73qt.mjs");
3371
3546
  const allPatterns = [];
3372
3547
  for (const moduleId of effectiveModuleIds) {
3373
3548
  const mod = getModuleById$1(moduleId, moduleList);
@@ -3390,14 +3565,14 @@ const pushCommand = defineCommand({
3390
3565
  for (const file of classification.autoUpdate) log.message(` ${pc$1.dim("↓")} ${pc$1.dim(file)}`);
3391
3566
  }
3392
3567
  if (classification.conflicts.length > 0) {
3393
- const { threeWayMerge: threeWayMerge$1, asBaseContent: asBaseContent$1, asLocalContent: asLocalContent$1, asTemplateContent: asTemplateContent$1 } = await import("./merge-CPx9nRrL.mjs");
3568
+ const { threeWayMerge: threeWayMerge$1, asBaseContent: asBaseContent$1, asLocalContent: asLocalContent$1, asTemplateContent: asTemplateContent$1 } = await import("./merge-DtLUiQDW.mjs");
3394
3569
  const baseInfo = config.baseRef ? `since ${pc$1.bold(config.baseRef.slice(0, 7))} (your last sync)` : "since your last pull/init";
3395
3570
  log.warn(`Template updated ${baseInfo} — ${classification.conflicts.length} conflict(s) detected, attempting auto-merge...`);
3396
3571
  let baseTemplateDir;
3397
3572
  let baseCleanup;
3398
3573
  if (config.baseRef) try {
3399
3574
  log.info(`Downloading base version (${config.baseRef.slice(0, 7)}...) for merge...`);
3400
- const { downloadTemplateToTemp: downloadBase } = await import("./template-CPgSJ67F.mjs");
3575
+ const { downloadTemplateToTemp: downloadBase } = await import("./template-BmUA_WdM.mjs");
3401
3576
  const baseResult = await downloadBase(targetDir, `gh:${config.source.owner}/${config.source.repo}#${config.baseRef}`);
3402
3577
  baseTemplateDir = baseResult.templateDir;
3403
3578
  baseCleanup = baseResult.cleanup;
@@ -3822,4 +3997,4 @@ async function run() {
3822
3997
  run();
3823
3998
 
3824
3999
  //#endregion
3825
- export { addPatternToModulesFileWithCreate as C, saveModulesFile as D, modulesFileExists as E, addPatternToModulesFile as S, loadModulesFile as T, matchesPatterns as _, hasConflictMarkers as a, getModuleById as b, hashContent as c, buildTemplateSource as d, copyFile as f, getEffectivePatterns as g, writeFileWithStrategy as h, classifyFiles as i, hashFiles as l, fetchTemplates as m, asLocalContent as n, mergeJsonContent as o, downloadTemplateToTemp as p, asTemplateContent as r, threeWayMerge as s, asBaseContent as t, TEMPLATE_SOURCE as u, resolvePatterns as v, getModulesFilePath as w, getPatternsByModuleIds as x, defaultModules as y };
4000
+ export { getPatternsByModuleIds as C, loadModulesFile as D, getModulesFilePath as E, modulesFileExists as O, getModuleById as S, addPatternToModulesFileWithCreate as T, writeFileWithStrategy as _, hasConflictMarkers as a, resolvePatterns as b, mergeYamlContent as c, hashFiles as d, TEMPLATE_SOURCE as f, fetchTemplates as g, downloadTemplateToTemp as h, classifyFiles as i, saveModulesFile as k, threeWayMerge as l, copyFile as m, asLocalContent as n, mergeJsonContent as o, buildTemplateSource as p, asTemplateContent as r, mergeTomlContent as s, asBaseContent as t, hashContent as u, getEffectivePatterns as v, addPatternToModulesFile as w, defaultModules as x, matchesPatterns as y };
@@ -0,0 +1,3 @@
1
+ import { a as hasConflictMarkers, c as mergeYamlContent, i as classifyFiles, l as threeWayMerge, n as asLocalContent, o as mergeJsonContent, r as asTemplateContent, s as mergeTomlContent, t as asBaseContent } from "./index.mjs";
2
+
3
+ export { asBaseContent, asLocalContent, asTemplateContent, classifyFiles, threeWayMerge };
@@ -0,0 +1,3 @@
1
+ import { C as getPatternsByModuleIds, D as loadModulesFile, E as getModulesFilePath, O as modulesFileExists, S as getModuleById, T as addPatternToModulesFileWithCreate, k as saveModulesFile, w as addPatternToModulesFile, x as defaultModules } from "./index.mjs";
2
+
3
+ export { getModuleById };
@@ -0,0 +1,3 @@
1
+ import { b as resolvePatterns, v as getEffectivePatterns, y as matchesPatterns } from "./index.mjs";
2
+
3
+ export { getEffectivePatterns };
@@ -0,0 +1,3 @@
1
+ import { _ as writeFileWithStrategy, f as TEMPLATE_SOURCE, g as fetchTemplates, h as downloadTemplateToTemp, m as copyFile, p as buildTemplateSource } from "./index.mjs";
2
+
3
+ export { downloadTemplateToTemp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ziku",
3
- "version": "0.21.3",
3
+ "version": "0.22.0",
4
4
  "description": "Interactive CLI to scaffold development environment templates",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,6 +30,7 @@
30
30
  "jsonc-parser": "^3.3.1",
31
31
  "pathe": "^2.0.3",
32
32
  "picocolors": "^1.1.1",
33
+ "smol-toml": "^1.6.0",
33
34
  "tinyglobby": "^0.2.15",
34
35
  "ts-pattern": "^5.9.0",
35
36
  "yaml": "^2.8.2",
@@ -1,3 +0,0 @@
1
- import { c as hashContent, l as hashFiles } from "./index.mjs";
2
-
3
- export { hashFiles };
@@ -1,3 +0,0 @@
1
- import { a as hasConflictMarkers, i as classifyFiles, n as asLocalContent, o as mergeJsonContent, r as asTemplateContent, s as threeWayMerge, t as asBaseContent } from "./index.mjs";
2
-
3
- export { asBaseContent, asLocalContent, asTemplateContent, classifyFiles, threeWayMerge };
@@ -1,3 +0,0 @@
1
- import { C as addPatternToModulesFileWithCreate, D as saveModulesFile, E as modulesFileExists, S as addPatternToModulesFile, T as loadModulesFile, b as getModuleById, w as getModulesFilePath, x as getPatternsByModuleIds, y as defaultModules } from "./index.mjs";
2
-
3
- export { getModuleById };
@@ -1,3 +0,0 @@
1
- import { _ as matchesPatterns, g as getEffectivePatterns, v as resolvePatterns } from "./index.mjs";
2
-
3
- export { getEffectivePatterns };
@@ -1,3 +0,0 @@
1
- import { d as buildTemplateSource, f as copyFile, h as writeFileWithStrategy, m as fetchTemplates, p as downloadTemplateToTemp, u as TEMPLATE_SOURCE } from "./index.mjs";
2
-
3
- export { downloadTemplateToTemp };