@tbsten/mir-core 0.0.2-alpha02 → 0.0.2-alpha03

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 (120) hide show
  1. package/dist/__tests__/errors.test.d.ts +1 -0
  2. package/dist/__tests__/errors.test.js +51 -0
  3. package/dist/__tests__/errors.test.js.map +1 -0
  4. package/dist/__tests__/helpers/string-helpers.test.d.ts +1 -0
  5. package/dist/__tests__/helpers/string-helpers.test.js +61 -0
  6. package/dist/__tests__/helpers/string-helpers.test.js.map +1 -0
  7. package/dist/__tests__/hooks.test.d.ts +1 -0
  8. package/dist/__tests__/hooks.test.js +125 -0
  9. package/dist/__tests__/hooks.test.js.map +1 -0
  10. package/dist/__tests__/i18n.test.d.ts +1 -0
  11. package/dist/__tests__/i18n.test.js +51 -0
  12. package/dist/__tests__/i18n.test.js.map +1 -0
  13. package/dist/__tests__/remote-registry.test.d.ts +1 -0
  14. package/dist/__tests__/remote-registry.test.js +194 -0
  15. package/dist/__tests__/remote-registry.test.js.map +1 -0
  16. package/dist/__tests__/safe-yaml-parser.test.d.ts +1 -0
  17. package/dist/__tests__/safe-yaml-parser.test.js +159 -0
  18. package/dist/__tests__/safe-yaml-parser.test.js.map +1 -0
  19. package/dist/__tests__/snapshots/error-messages.snapshot.test.d.ts +1 -0
  20. package/dist/__tests__/snapshots/error-messages.snapshot.test.js +52 -0
  21. package/dist/__tests__/snapshots/error-messages.snapshot.test.js.map +1 -0
  22. package/dist/__tests__/snapshots/snippet-schema-output.snapshot.test.d.ts +1 -0
  23. package/dist/__tests__/snapshots/snippet-schema-output.snapshot.test.js +93 -0
  24. package/dist/__tests__/snapshots/snippet-schema-output.snapshot.test.js.map +1 -0
  25. package/dist/__tests__/snapshots/template-output.snapshot.test.d.ts +1 -0
  26. package/dist/__tests__/snapshots/template-output.snapshot.test.js +60 -0
  27. package/dist/__tests__/snapshots/template-output.snapshot.test.js.map +1 -0
  28. package/dist/__tests__/snippet-schema.test.d.ts +1 -0
  29. package/dist/__tests__/snippet-schema.test.js +137 -0
  30. package/dist/__tests__/snippet-schema.test.js.map +1 -0
  31. package/dist/__tests__/symlink-checker.test.d.ts +1 -0
  32. package/dist/__tests__/symlink-checker.test.js +81 -0
  33. package/dist/__tests__/symlink-checker.test.js.map +1 -0
  34. package/dist/__tests__/template-engine.test.d.ts +1 -0
  35. package/dist/__tests__/template-engine.test.js +175 -0
  36. package/dist/__tests__/template-engine.test.js.map +1 -0
  37. package/dist/__tests__/validate-name.test.d.ts +1 -0
  38. package/dist/__tests__/validate-name.test.js +49 -0
  39. package/dist/__tests__/validate-name.test.js.map +1 -0
  40. package/dist/errors.d.ts +31 -0
  41. package/dist/errors.js +68 -0
  42. package/dist/errors.js.map +1 -0
  43. package/dist/helpers/index.d.ts +5 -0
  44. package/dist/helpers/index.js +28 -0
  45. package/dist/helpers/index.js.map +1 -0
  46. package/dist/helpers/string-helpers.d.ts +15 -0
  47. package/dist/helpers/string-helpers.js +63 -0
  48. package/dist/helpers/string-helpers.js.map +1 -0
  49. package/dist/hooks.d.ts +9 -0
  50. package/dist/hooks.js +48 -0
  51. package/dist/hooks.js.map +1 -0
  52. package/dist/i18n/index.d.ts +6 -0
  53. package/dist/i18n/index.js +20 -0
  54. package/dist/i18n/index.js.map +1 -0
  55. package/dist/i18n/locales/en.d.ts +2 -0
  56. package/dist/i18n/locales/en.js +84 -0
  57. package/dist/i18n/locales/en.js.map +1 -0
  58. package/dist/i18n/locales/ja.d.ts +2 -0
  59. package/dist/i18n/locales/ja.js +84 -0
  60. package/dist/i18n/locales/ja.js.map +1 -0
  61. package/dist/i18n/types.d.ts +72 -0
  62. package/dist/i18n/types.js +2 -0
  63. package/dist/i18n/types.js.map +1 -0
  64. package/dist/index.d.ts +17 -0
  65. package/dist/index.js +23 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/lib/symlink-checker.d.ts +15 -0
  68. package/dist/lib/symlink-checker.js +54 -0
  69. package/dist/lib/symlink-checker.js.map +1 -0
  70. package/dist/registry.d.ts +8 -0
  71. package/dist/registry.js +86 -0
  72. package/dist/registry.js.map +1 -0
  73. package/dist/remote-registry.d.ts +51 -0
  74. package/dist/remote-registry.js +181 -0
  75. package/dist/remote-registry.js.map +1 -0
  76. package/dist/safe-yaml-parser.d.ts +26 -0
  77. package/{src/safe-yaml-parser.ts → dist/safe-yaml-parser.js} +28 -34
  78. package/dist/safe-yaml-parser.js.map +1 -0
  79. package/dist/snippet-schema.d.ts +38 -0
  80. package/dist/snippet-schema.js +63 -0
  81. package/dist/snippet-schema.js.map +1 -0
  82. package/dist/template-engine.d.ts +5 -0
  83. package/dist/template-engine.js +130 -0
  84. package/dist/template-engine.js.map +1 -0
  85. package/dist/validate-name.d.ts +1 -0
  86. package/dist/validate-name.js +9 -0
  87. package/dist/validate-name.js.map +1 -0
  88. package/package.json +6 -1
  89. package/src/__tests__/errors.test.ts +0 -71
  90. package/src/__tests__/helpers/string-helpers.test.ts +0 -100
  91. package/src/__tests__/hooks.test.ts +0 -145
  92. package/src/__tests__/i18n.test.ts +0 -60
  93. package/src/__tests__/remote-registry.test.ts +0 -265
  94. package/src/__tests__/safe-yaml-parser.test.ts +0 -187
  95. package/src/__tests__/snapshots/__snapshots__/error-messages.snapshot.test.ts.snap +0 -27
  96. package/src/__tests__/snapshots/__snapshots__/snippet-schema-output.snapshot.test.ts.snap +0 -78
  97. package/src/__tests__/snapshots/__snapshots__/template-output.snapshot.test.ts.snap +0 -45
  98. package/src/__tests__/snapshots/error-messages.snapshot.test.ts +0 -76
  99. package/src/__tests__/snapshots/snippet-schema-output.snapshot.test.ts +0 -98
  100. package/src/__tests__/snapshots/template-output.snapshot.test.ts +0 -73
  101. package/src/__tests__/snippet-schema.test.ts +0 -180
  102. package/src/__tests__/symlink-checker.test.ts +0 -95
  103. package/src/__tests__/template-engine.test.ts +0 -240
  104. package/src/__tests__/validate-name.test.ts +0 -61
  105. package/src/errors.ts +0 -82
  106. package/src/helpers/index.ts +0 -34
  107. package/src/helpers/string-helpers.ts +0 -76
  108. package/src/hooks.ts +0 -63
  109. package/src/i18n/index.ts +0 -32
  110. package/src/i18n/locales/en.ts +0 -96
  111. package/src/i18n/locales/ja.ts +0 -96
  112. package/src/i18n/types.ts +0 -96
  113. package/src/index.ts +0 -86
  114. package/src/lib/symlink-checker.ts +0 -62
  115. package/src/registry.ts +0 -120
  116. package/src/remote-registry.ts +0 -260
  117. package/src/snippet-schema.ts +0 -117
  118. package/src/template-engine.ts +0 -147
  119. package/src/validate-name.ts +0 -12
  120. package/tsconfig.json +0 -17
@@ -1,180 +0,0 @@
1
- /**
2
- * mir-core: snippet-schema の unit テスト
3
- */
4
- import { describe, it, expect } from "vitest";
5
- import {
6
- parseSnippetYaml,
7
- serializeSnippetYaml,
8
- validateSnippetDefinition,
9
- ValidationError,
10
- } from "../index.js";
11
-
12
- // TODO: 現時点では理想の挙動をテストケースとして記述。後で有効化する。
13
-
14
- describe("parseSnippetYaml", () => {
15
- it("最小限の YAML をパースできる", () => {
16
- const def = parseSnippetYaml("name: my-snippet\n");
17
- expect(def.name).toBe("my-snippet");
18
- });
19
-
20
- it("変数付き YAML をパースできる", () => {
21
- const yaml = `
22
- name: react-hook
23
- description: React カスタムフック
24
- variables:
25
- name:
26
- description: フック名
27
- schema:
28
- type: string
29
- `;
30
- const def = parseSnippetYaml(yaml);
31
- expect(def.name).toBe("react-hook");
32
- expect(def.variables?.name?.description).toBe("フック名");
33
- expect(def.variables?.name?.schema?.type).toBe("string");
34
- });
35
-
36
- it("hooks 付き YAML をパースできる", () => {
37
- const yaml = `
38
- name: test-snippet
39
- hooks:
40
- before-install:
41
- - echo: "Installing..."
42
- after-install:
43
- - echo: "Done!"
44
- `;
45
- const def = parseSnippetYaml(yaml);
46
- expect(def.hooks?.["before-install"]).toHaveLength(1);
47
- expect(def.hooks?.["after-install"]).toHaveLength(1);
48
- });
49
-
50
- it("dependencies 付き YAML をパースできる", () => {
51
- const yaml = `
52
- name: react-component
53
- description: React component
54
- dependencies:
55
- - react-hook
56
- - typescript-setup
57
- `;
58
- const def = parseSnippetYaml(yaml);
59
- expect(def.dependencies).toEqual(["react-hook", "typescript-setup"]);
60
- });
61
-
62
- it("不正な YAML でエラー", () => {
63
- expect(() => parseSnippetYaml("not a yaml object: [")).toThrow();
64
- });
65
-
66
- it("name がない YAML でエラー", () => {
67
- expect(() => parseSnippetYaml("description: no name\n")).toThrow(
68
- ValidationError,
69
- );
70
- });
71
- });
72
-
73
- describe("serializeSnippetYaml", () => {
74
- it("SnippetDefinition を YAML 文字列に変換する", () => {
75
- const yaml = serializeSnippetYaml({ name: "test" });
76
- expect(yaml).toContain("name: test");
77
- });
78
-
79
- it("パースと逆変換で元に戻る", () => {
80
- const original = { name: "test", description: "desc" };
81
- const yaml = serializeSnippetYaml(original);
82
- const parsed = parseSnippetYaml(yaml);
83
- expect(parsed.name).toBe(original.name);
84
- expect(parsed.description).toBe(original.description);
85
- });
86
- });
87
-
88
- describe("validateSnippetDefinition", () => {
89
- it("最小限の定義はバリデーション通過", () => {
90
- expect(() => validateSnippetDefinition({ name: "valid" })).not.toThrow();
91
- });
92
-
93
- it("name が空だとエラー", () => {
94
- expect(() => validateSnippetDefinition({ name: "" })).toThrow(
95
- ValidationError,
96
- );
97
- });
98
-
99
- it("suggests が配列でないとエラー", () => {
100
- expect(() =>
101
- validateSnippetDefinition({
102
- name: "test",
103
- variables: {
104
- key: { suggests: "not-array" as unknown as string[] },
105
- },
106
- }),
107
- ).toThrow(ValidationError);
108
- });
109
-
110
- it("schema.type が不正だとエラー", () => {
111
- expect(() =>
112
- validateSnippetDefinition({
113
- name: "test",
114
- variables: {
115
- key: {
116
- schema: { type: "invalid" as "string" },
117
- },
118
- },
119
- }),
120
- ).toThrow(ValidationError);
121
- });
122
-
123
- it("正しい schema.type は通過 (string, number, boolean)", () => {
124
- expect(() =>
125
- validateSnippetDefinition({
126
- name: "test",
127
- variables: {
128
- a: { schema: { type: "string" } },
129
- b: { schema: { type: "number" } },
130
- c: { schema: { type: "boolean" } },
131
- },
132
- }),
133
- ).not.toThrow();
134
- });
135
-
136
- it("dependencies が配列の場合は通過", () => {
137
- expect(() =>
138
- validateSnippetDefinition({
139
- name: "react-component",
140
- dependencies: ["react-hook"],
141
- }),
142
- ).not.toThrow();
143
- });
144
-
145
- it("dependencies に複数要素を持つ場合は通過", () => {
146
- expect(() =>
147
- validateSnippetDefinition({
148
- name: "test",
149
- dependencies: ["dep1", "dep2", "dep3"],
150
- }),
151
- ).not.toThrow();
152
- });
153
-
154
- it("dependencies が配列でないとエラー", () => {
155
- expect(() =>
156
- validateSnippetDefinition({
157
- name: "test",
158
- dependencies: "not-array" as unknown as string[],
159
- }),
160
- ).toThrow(ValidationError);
161
- });
162
-
163
- it("dependencies の要素が文字列でないとエラー", () => {
164
- expect(() =>
165
- validateSnippetDefinition({
166
- name: "test",
167
- dependencies: ["valid-dep", 123 as unknown as string],
168
- }),
169
- ).toThrow(ValidationError);
170
- });
171
-
172
- it("dependencies の要素に不正な名前があるとエラー", () => {
173
- expect(() =>
174
- validateSnippetDefinition({
175
- name: "test",
176
- dependencies: ["-invalid-dep-name"],
177
- }),
178
- ).toThrow(ValidationError);
179
- });
180
- });
@@ -1,95 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { isSymbolicLink, findSymlinksInDirectory } from "../lib/symlink-checker.js";
6
-
7
- let tmpDir: string;
8
-
9
- beforeEach(() => {
10
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "mir-symlink-test-"));
11
- });
12
-
13
- afterEach(() => {
14
- fs.rmSync(tmpDir, { recursive: true, force: true });
15
- });
16
-
17
- describe("isSymbolicLink", () => {
18
- it("通常のファイルに対して false を返す", () => {
19
- const filePath = path.join(tmpDir, "regular.txt");
20
- fs.writeFileSync(filePath, "content");
21
- expect(isSymbolicLink(filePath)).toBe(false);
22
- });
23
-
24
- it("ディレクトリに対して false を返す", () => {
25
- const dirPath = path.join(tmpDir, "subdir");
26
- fs.mkdirSync(dirPath);
27
- expect(isSymbolicLink(dirPath)).toBe(false);
28
- });
29
-
30
- it("シンボリックリンクに対して true を返す", () => {
31
- const targetPath = path.join(tmpDir, "target.txt");
32
- const linkPath = path.join(tmpDir, "link.txt");
33
- fs.writeFileSync(targetPath, "content");
34
- fs.symlinkSync(targetPath, linkPath);
35
- expect(isSymbolicLink(linkPath)).toBe(true);
36
- });
37
-
38
- it("存在しないパスに対して false を返す", () => {
39
- expect(isSymbolicLink(path.join(tmpDir, "nonexistent"))).toBe(false);
40
- });
41
- });
42
-
43
- describe("findSymlinksInDirectory", () => {
44
- it("シンボリックリンクがない場合 hasSymlinks=false を返す", () => {
45
- fs.writeFileSync(path.join(tmpDir, "index.ts"), "content");
46
- const result = findSymlinksInDirectory(tmpDir);
47
- expect(result.hasSymlinks).toBe(false);
48
- expect(result.symlinkPaths).toHaveLength(0);
49
- });
50
-
51
- it("シンボリックリンクを検出する", () => {
52
- const targetPath = path.join(tmpDir, "target.txt");
53
- const linkPath = path.join(tmpDir, "link.txt");
54
- fs.writeFileSync(targetPath, "content");
55
- fs.symlinkSync(targetPath, linkPath);
56
-
57
- const result = findSymlinksInDirectory(tmpDir);
58
- expect(result.hasSymlinks).toBe(true);
59
- expect(result.symlinkPaths).toContain("link.txt");
60
- });
61
-
62
- it("サブディレクトリ内のシンボリックリンクを検出する", () => {
63
- const subDir = path.join(tmpDir, "src");
64
- fs.mkdirSync(subDir);
65
- const targetPath = path.join(tmpDir, "target.txt");
66
- const linkPath = path.join(subDir, "link.txt");
67
- fs.writeFileSync(targetPath, "content");
68
- fs.symlinkSync(targetPath, linkPath);
69
-
70
- const result = findSymlinksInDirectory(tmpDir);
71
- expect(result.hasSymlinks).toBe(true);
72
- expect(result.symlinkPaths.some((p) => p.includes("link.txt"))).toBe(true);
73
- });
74
-
75
- it("複数のシンボリックリンクをすべて検出する", () => {
76
- const target1 = path.join(tmpDir, "target1.txt");
77
- const target2 = path.join(tmpDir, "target2.txt");
78
- const link1 = path.join(tmpDir, "link1.txt");
79
- const link2 = path.join(tmpDir, "link2.txt");
80
- fs.writeFileSync(target1, "content1");
81
- fs.writeFileSync(target2, "content2");
82
- fs.symlinkSync(target1, link1);
83
- fs.symlinkSync(target2, link2);
84
-
85
- const result = findSymlinksInDirectory(tmpDir);
86
- expect(result.hasSymlinks).toBe(true);
87
- expect(result.symlinkPaths).toHaveLength(2);
88
- });
89
-
90
- it("存在しないディレクトリに対して空の結果を返す", () => {
91
- const result = findSymlinksInDirectory(path.join(tmpDir, "nonexistent"));
92
- expect(result.hasSymlinks).toBe(false);
93
- expect(result.symlinkPaths).toHaveLength(0);
94
- });
95
- });
@@ -1,240 +0,0 @@
1
- /**
2
- * mir-core: テンプレートエンジンの unit テスト
3
- */
4
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
5
- import fs from "node:fs";
6
- import os from "node:os";
7
- import path from "node:path";
8
- import {
9
- expandTemplate,
10
- expandPath,
11
- extractVariables,
12
- extractVariablesFromDirectory,
13
- } from "../index.js";
14
-
15
- // TODO: 現時点では理想の挙動をテストケースとして記述。後で有効化する。
16
-
17
- describe("expandTemplate", () => {
18
- it("変数を展開する", () => {
19
- expect(expandTemplate("Hello, {{ name }}!", { name: "World" })).toBe(
20
- "Hello, World!",
21
- );
22
- });
23
-
24
- it("複数変数を展開する", () => {
25
- const result = expandTemplate(
26
- "{{ greeting }}, {{ name }}!",
27
- { greeting: "Hi", name: "User" },
28
- );
29
- expect(result).toBe("Hi, User!");
30
- });
31
-
32
- it("変数が未定義の場合は空文字に展開", () => {
33
- expect(expandTemplate("Hello, {{ name }}!", {})).toBe("Hello, !");
34
- });
35
-
36
- it("変数を含まないテンプレートはそのまま返す", () => {
37
- expect(expandTemplate("plain text", {})).toBe("plain text");
38
- });
39
-
40
- it("空テンプレートは空文字を返す", () => {
41
- expect(expandTemplate("", {})).toBe("");
42
- });
43
-
44
- it("Handlebars の #if ブロックを展開する", () => {
45
- const template = "{{#if enabled}}ON{{else}}OFF{{/if}}";
46
- expect(expandTemplate(template, { enabled: true })).toBe("ON");
47
- expect(expandTemplate(template, { enabled: false })).toBe("OFF");
48
- });
49
-
50
- it("noEscape モードで HTML タグがエスケープされない", () => {
51
- expect(expandTemplate("{{ html }}", { html: "<div>test</div>" })).toBe(
52
- "<div>test</div>",
53
- );
54
- });
55
- });
56
-
57
- describe("expandPath", () => {
58
- it("パス内の変数を展開する", () => {
59
- expect(expandPath("{{ name }}.ts", { name: "useAuth" })).toBe("useAuth.ts");
60
- });
61
-
62
- it("ネストしたパスの変数を展開する", () => {
63
- const result = expandPath("{{ dir }}/{{ name }}.ts", {
64
- dir: "hooks",
65
- name: "useAuth",
66
- });
67
- expect(result).toMatch(/hooks[/\\]useAuth\.ts/);
68
- });
69
-
70
- it("パス区切りを正規化する", () => {
71
- const result = expandPath("a//b/c", {});
72
- expect(result).not.toContain("//");
73
- });
74
- });
75
-
76
- describe("extractVariables", () => {
77
- it("単一変数を抽出する", () => {
78
- expect(extractVariables("{{ name }}")).toContain("name");
79
- });
80
-
81
- it("複数変数を抽出する", () => {
82
- const vars = extractVariables("{{ name }} {{ description }}");
83
- expect(vars).toContain("name");
84
- expect(vars).toContain("description");
85
- });
86
-
87
- it("重複変数は1つにまとめる", () => {
88
- const vars = extractVariables("{{ name }} {{ name }}");
89
- expect(vars.filter((v) => v === "name")).toHaveLength(1);
90
- });
91
-
92
- it("変数がない場合は空配列", () => {
93
- expect(extractVariables("no variables")).toEqual([]);
94
- });
95
-
96
- it("#if ブロック内の変数を抽出する", () => {
97
- const vars = extractVariables("{{#if enabled}}{{ name }}{{/if}}");
98
- expect(vars).toContain("enabled");
99
- expect(vars).toContain("name");
100
- });
101
- });
102
-
103
- describe("expandTemplate with helpers", () => {
104
- it("lowercase ヘルパー", () => {
105
- expect(expandTemplate("{{lowercase name}}", { name: "HELLO" })).toBe("hello");
106
- });
107
-
108
- it("uppercase ヘルパー", () => {
109
- expect(expandTemplate("{{uppercase name}}", { name: "hello" })).toBe("HELLO");
110
- });
111
-
112
- it("replace ヘルパー", () => {
113
- expect(
114
- expandTemplate('{{replace package "/" "."}}', { package: "com/example/app" }),
115
- ).toBe("com.example.app");
116
- });
117
-
118
- it("camelCase ヘルパー", () => {
119
- expect(expandTemplate("{{camelCase name}}", { name: "my-component" })).toBe(
120
- "myComponent",
121
- );
122
- });
123
-
124
- it("pascalCase ヘルパー", () => {
125
- expect(expandTemplate("{{pascalCase name}}", { name: "my-component" })).toBe(
126
- "MyComponent",
127
- );
128
- });
129
-
130
- it("snakeCase ヘルパー", () => {
131
- expect(expandTemplate("{{snakeCase name}}", { name: "myComponent" })).toBe(
132
- "my_component",
133
- );
134
- });
135
-
136
- it("kebabCase ヘルパー", () => {
137
- expect(expandTemplate("{{kebabCase name}}", { name: "MyComponent" })).toBe(
138
- "my-component",
139
- );
140
- });
141
-
142
- it("capitalize ヘルパー", () => {
143
- expect(expandTemplate("{{capitalize name}}", { name: "hello" })).toBe("Hello");
144
- });
145
-
146
- it("uncapitalize ヘルパー", () => {
147
- expect(expandTemplate("{{uncapitalize name}}", { name: "Hello" })).toBe("hello");
148
- });
149
-
150
- it("trim ヘルパー", () => {
151
- expect(expandTemplate("{{trim name}}", { name: " hello " })).toBe("hello");
152
- });
153
-
154
- it("サブ式(ネスト): lowercase(replace(...))", () => {
155
- expect(
156
- expandTemplate('{{lowercase (replace name "/" ".")}}', {
157
- name: "COM/EXAMPLE/APP",
158
- }),
159
- ).toBe("com.example.app");
160
- });
161
-
162
- it("サブ式(ネスト): pascalCase(replace(...))", () => {
163
- expect(
164
- expandTemplate('{{pascalCase (replace pkg "/" "-")}}', {
165
- pkg: "my/cool/component",
166
- }),
167
- ).toBe("MyCoolComponent");
168
- });
169
- });
170
-
171
- describe("extractVariables with helpers", () => {
172
- it("ヘルパー名を変数に含めない", () => {
173
- const vars = extractVariables("{{lowercase name}}");
174
- expect(vars).toContain("name");
175
- expect(vars).not.toContain("lowercase");
176
- });
177
-
178
- it("replace ヘルパーで変数のみ抽出", () => {
179
- const vars = extractVariables('{{replace package "/" "."}}');
180
- expect(vars).toContain("package");
181
- expect(vars).not.toContain("replace");
182
- });
183
-
184
- it("サブ式でもヘルパー名を除外", () => {
185
- const vars = extractVariables('{{lowercase (replace name "/" ".")}}');
186
- expect(vars).toContain("name");
187
- expect(vars).not.toContain("lowercase");
188
- expect(vars).not.toContain("replace");
189
- });
190
-
191
- it("ヘルパーと通常変数が混在", () => {
192
- const vars = extractVariables("{{name}} {{lowercase title}}");
193
- expect(vars).toContain("name");
194
- expect(vars).toContain("title");
195
- expect(vars).not.toContain("lowercase");
196
- });
197
-
198
- it("組み込みヘルパー (if/each) も変数に含めない", () => {
199
- const vars = extractVariables("{{#if enabled}}{{name}}{{/if}}");
200
- expect(vars).toContain("enabled");
201
- expect(vars).toContain("name");
202
- expect(vars).not.toContain("if");
203
- });
204
- });
205
-
206
- describe("extractVariablesFromDirectory", () => {
207
- let tmpDir: string;
208
-
209
- beforeEach(() => {
210
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "mir-core-test-"));
211
- });
212
-
213
- afterEach(() => {
214
- fs.rmSync(tmpDir, { recursive: true, force: true });
215
- });
216
-
217
- it("ファイル内容から変数を抽出する", () => {
218
- fs.writeFileSync(path.join(tmpDir, "index.ts"), "export {{ name }}", "utf-8");
219
- const vars = extractVariablesFromDirectory(tmpDir);
220
- expect(vars).toContain("name");
221
- });
222
-
223
- it("ファイル名から変数を抽出する", () => {
224
- fs.writeFileSync(path.join(tmpDir, "{{ name }}.ts"), "content", "utf-8");
225
- const vars = extractVariablesFromDirectory(tmpDir);
226
- expect(vars).toContain("name");
227
- });
228
-
229
- it("サブディレクトリ名から変数を抽出する", () => {
230
- const subDir = path.join(tmpDir, "{{ module }}");
231
- fs.mkdirSync(subDir, { recursive: true });
232
- fs.writeFileSync(path.join(subDir, "index.ts"), "export {}", "utf-8");
233
- const vars = extractVariablesFromDirectory(tmpDir);
234
- expect(vars).toContain("module");
235
- });
236
-
237
- it("存在しないディレクトリは空配列", () => {
238
- expect(extractVariablesFromDirectory("/nonexistent")).toEqual([]);
239
- });
240
- });
@@ -1,61 +0,0 @@
1
- /**
2
- * mir-core: validateSnippetName の unit テスト
3
- */
4
- import { describe, it, expect } from "vitest";
5
- import { validateSnippetName, ValidationError } from "../index.js";
6
-
7
- // TODO: 現時点では理想の挙動をテストケースとして記述。後で有効化する。
8
-
9
- describe("validateSnippetName", () => {
10
- describe("正常系", () => {
11
- it("英小文字のみ", () => {
12
- expect(() => validateSnippetName("react")).not.toThrow();
13
- });
14
-
15
- it("英数字 + ハイフン", () => {
16
- expect(() => validateSnippetName("react-hook")).not.toThrow();
17
- });
18
-
19
- it("数字始まり", () => {
20
- expect(() => validateSnippetName("1component")).not.toThrow();
21
- });
22
-
23
- it("大文字含む", () => {
24
- expect(() => validateSnippetName("MyComponent")).not.toThrow();
25
- });
26
-
27
- it("1文字", () => {
28
- expect(() => validateSnippetName("a")).not.toThrow();
29
- });
30
- });
31
-
32
- describe("異常系", () => {
33
- it("空文字でエラー", () => {
34
- expect(() => validateSnippetName("")).toThrow(ValidationError);
35
- });
36
-
37
- it("アンダースコアでエラー", () => {
38
- expect(() => validateSnippetName("my_comp")).toThrow(ValidationError);
39
- });
40
-
41
- it("先頭ハイフンでエラー", () => {
42
- expect(() => validateSnippetName("-invalid")).toThrow(ValidationError);
43
- });
44
-
45
- it("スペース含むとエラー", () => {
46
- expect(() => validateSnippetName("my comp")).toThrow(ValidationError);
47
- });
48
-
49
- it("日本語でエラー", () => {
50
- expect(() => validateSnippetName("テスト")).toThrow(ValidationError);
51
- });
52
-
53
- it("特殊文字でエラー", () => {
54
- expect(() => validateSnippetName("react@hook")).toThrow(ValidationError);
55
- });
56
-
57
- it("ドット含むとエラー", () => {
58
- expect(() => validateSnippetName("my.comp")).toThrow(ValidationError);
59
- });
60
- });
61
- });
package/src/errors.ts DELETED
@@ -1,82 +0,0 @@
1
- import { t } from "./i18n/index.js";
2
-
3
- export class MirError extends Error {
4
- constructor(message: string) {
5
- super(message);
6
- this.name = "MirError";
7
- }
8
- }
9
-
10
- export class ValidationError extends MirError {
11
- constructor(message: string) {
12
- super(message);
13
- this.name = "ValidationError";
14
- }
15
- }
16
-
17
- export class SnippetNotFoundError extends MirError {
18
- readonly details: string;
19
-
20
- constructor(name: string) {
21
- super(t("error.snippet-not-found", { name }));
22
- this.name = "SnippetNotFoundError";
23
- this.details = t("error.snippet-not-found-details");
24
- }
25
- }
26
-
27
- export class SnippetAlreadyExistsError extends MirError {
28
- constructor(name: string) {
29
- super(t("error.snippet-already-exists", { name }));
30
- this.name = "SnippetAlreadyExistsError";
31
- }
32
- }
33
-
34
- export class RegistryNotFoundError extends MirError {
35
- constructor(name: string) {
36
- super(t("error.registry-not-found", { name }));
37
- this.name = "RegistryNotFoundError";
38
- }
39
- }
40
-
41
- export class RegistryRemoteError extends MirError {
42
- constructor(name?: string) {
43
- super(
44
- name
45
- ? t("error.registry-remote-named", { name })
46
- : t("error.registry-remote"),
47
- );
48
- this.name = "RegistryRemoteError";
49
- }
50
- }
51
-
52
- export class PathTraversalError extends MirError {
53
- constructor(filePath: string) {
54
- super(t("error.path-traversal", { path: filePath }));
55
- this.name = "PathTraversalError";
56
- }
57
- }
58
-
59
- export class FileConflictError extends MirError {
60
- constructor(filePath: string) {
61
- super(t("error.file-conflict", { path: filePath }));
62
- this.name = "FileConflictError";
63
- }
64
- }
65
-
66
- export class RemoteRegistryFetchError extends MirError {
67
- constructor(url: string, status?: number) {
68
- super(
69
- status
70
- ? t("error.remote-fetch-status", { url, status })
71
- : t("error.remote-fetch", { url }),
72
- );
73
- this.name = "RemoteRegistryFetchError";
74
- }
75
- }
76
-
77
- export class InvalidManifestError extends MirError {
78
- constructor(url: string) {
79
- super(t("error.invalid-manifest", { url }));
80
- this.name = "InvalidManifestError";
81
- }
82
- }
@@ -1,34 +0,0 @@
1
- import type Handlebars from "handlebars";
2
- import * as helpers from "./string-helpers.js";
3
-
4
- /** ヘルパー名一覧(extractVariables でフィルタ用) */
5
- export const HELPER_NAMES: ReadonlySet<string> = new Set([
6
- "lowercase",
7
- "uppercase",
8
- "capitalize",
9
- "uncapitalize",
10
- "replace",
11
- "camelCase",
12
- "pascalCase",
13
- "snakeCase",
14
- "kebabCase",
15
- "trim",
16
- ]);
17
-
18
- /** Handlebars インスタンスにヘルパーを一括登録 */
19
- export function registerHelpers(hbs: typeof Handlebars): void {
20
- hbs.registerHelper("lowercase", (v: unknown) => helpers.lowercase(v));
21
- hbs.registerHelper("uppercase", (v: unknown) => helpers.uppercase(v));
22
- hbs.registerHelper("capitalize", (v: unknown) => helpers.capitalize(v));
23
- hbs.registerHelper("uncapitalize", (v: unknown) => helpers.uncapitalize(v));
24
- hbs.registerHelper(
25
- "replace",
26
- (v: unknown, search: unknown, replacement: unknown) =>
27
- helpers.replace(v, search, replacement),
28
- );
29
- hbs.registerHelper("camelCase", (v: unknown) => helpers.camelCase(v));
30
- hbs.registerHelper("pascalCase", (v: unknown) => helpers.pascalCase(v));
31
- hbs.registerHelper("snakeCase", (v: unknown) => helpers.snakeCase(v));
32
- hbs.registerHelper("kebabCase", (v: unknown) => helpers.kebabCase(v));
33
- hbs.registerHelper("trim", (v: unknown) => helpers.trim(v));
34
- }