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

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
@@ -0,0 +1,181 @@
1
+ import { RemoteRegistryFetchError, InvalidManifestError, MirError } from "./errors.js";
2
+ import { parseSnippetYaml } from "./snippet-schema.js";
3
+ import { expandTemplate, expandPath } from "./template-engine.js";
4
+ import { t } from "./i18n/index.js";
5
+ const CACHE_TTL_MS = 60000; // 60秒
6
+ const manifestCache = new Map();
7
+ const snippetListCache = new Map();
8
+ /**
9
+ * キャッシュが有効か確認
10
+ */
11
+ function isCacheValid(entry) {
12
+ return Date.now() - entry.timestamp < CACHE_TTL_MS;
13
+ }
14
+ /**
15
+ * すべてのリモート registry キャッシュをクリア
16
+ */
17
+ export function clearAllRemoteRegistryCaches() {
18
+ manifestCache.clear();
19
+ snippetListCache.clear();
20
+ }
21
+ function normalizeBaseUrl(baseUrl) {
22
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
23
+ }
24
+ /**
25
+ * AbortController を使ったタイムアウト付き fetch
26
+ */
27
+ async function fetchWithTimeout(url, options = {}) {
28
+ if (!options.timeoutMs) {
29
+ return fetch(url);
30
+ }
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
33
+ try {
34
+ return await fetch(url, { signal: controller.signal });
35
+ }
36
+ catch (err) {
37
+ if (err instanceof Error && err.name === "AbortError") {
38
+ const timeoutSec = Math.round(options.timeoutMs / 1000);
39
+ throw new MirError(t("error.fetch-timeout", { url, timeout: timeoutSec }));
40
+ }
41
+ throw err;
42
+ }
43
+ finally {
44
+ clearTimeout(timeoutId);
45
+ }
46
+ }
47
+ /**
48
+ * マニフェスト (index.json) を取得する(キャッシュ付き)
49
+ */
50
+ export async function fetchRegistryManifest(baseUrl, options = {}) {
51
+ const normalizedUrl = normalizeBaseUrl(baseUrl);
52
+ const cached = manifestCache.get(normalizedUrl);
53
+ if (cached && isCacheValid(cached)) {
54
+ return cached.data;
55
+ }
56
+ const url = `${normalizedUrl}/index.json`;
57
+ let res;
58
+ try {
59
+ res = await fetchWithTimeout(url, options);
60
+ }
61
+ catch (err) {
62
+ if (err instanceof MirError)
63
+ throw err;
64
+ throw new RemoteRegistryFetchError(url);
65
+ }
66
+ if (!res.ok) {
67
+ throw new RemoteRegistryFetchError(url, res.status);
68
+ }
69
+ let data;
70
+ try {
71
+ data = await res.json();
72
+ }
73
+ catch {
74
+ throw new InvalidManifestError(url);
75
+ }
76
+ if (typeof data !== "object" ||
77
+ data === null ||
78
+ !("snippets" in data) ||
79
+ typeof data.snippets !== "object") {
80
+ throw new InvalidManifestError(url);
81
+ }
82
+ const manifest = data;
83
+ manifestCache.set(normalizedUrl, {
84
+ data: manifest,
85
+ timestamp: Date.now(),
86
+ });
87
+ return manifest;
88
+ }
89
+ /**
90
+ * リモート registry の snippet 名一覧を返す(キャッシュ付き)
91
+ */
92
+ export async function listRemoteSnippets(baseUrl, options = {}) {
93
+ const normalizedUrl = normalizeBaseUrl(baseUrl);
94
+ const cached = snippetListCache.get(normalizedUrl);
95
+ if (cached && isCacheValid(cached)) {
96
+ return cached.data;
97
+ }
98
+ const manifest = await fetchRegistryManifest(baseUrl, options);
99
+ const snippetList = Object.keys(manifest.snippets);
100
+ snippetListCache.set(normalizedUrl, {
101
+ data: snippetList,
102
+ timestamp: Date.now(),
103
+ });
104
+ return snippetList;
105
+ }
106
+ /**
107
+ * snippet 定義 (YAML) を取得する
108
+ */
109
+ export async function fetchSnippetDefinition(baseUrl, name, options = {}) {
110
+ const url = `${normalizeBaseUrl(baseUrl)}/${name}.yaml`;
111
+ let res;
112
+ try {
113
+ res = await fetchWithTimeout(url, options);
114
+ }
115
+ catch (err) {
116
+ if (err instanceof MirError)
117
+ throw err;
118
+ throw new RemoteRegistryFetchError(url);
119
+ }
120
+ if (!res.ok) {
121
+ throw new RemoteRegistryFetchError(url, res.status);
122
+ }
123
+ const text = await res.text();
124
+ return parseSnippetYaml(text);
125
+ }
126
+ /**
127
+ * テンプレートファイル群を並列で取得する
128
+ */
129
+ export async function fetchRemoteFiles(baseUrl, name, files, options = {}) {
130
+ const base = normalizeBaseUrl(baseUrl);
131
+ const result = new Map();
132
+ const entries = await Promise.all(files.map(async (filePath) => {
133
+ const url = `${base}/${name}/${encodeURIComponent(filePath)}`;
134
+ let res;
135
+ try {
136
+ res = await fetchWithTimeout(url, options);
137
+ }
138
+ catch (err) {
139
+ if (err instanceof MirError)
140
+ throw err;
141
+ throw new RemoteRegistryFetchError(url);
142
+ }
143
+ if (!res.ok) {
144
+ throw new RemoteRegistryFetchError(url, res.status);
145
+ }
146
+ const content = await res.text();
147
+ return [filePath, content];
148
+ }));
149
+ for (const [filePath, content] of entries) {
150
+ result.set(filePath, content);
151
+ }
152
+ return result;
153
+ }
154
+ /**
155
+ * snippet 定義とテンプレートファイルを統合して取得する
156
+ */
157
+ export async function fetchRemoteSnippet(baseUrl, name, options = {}) {
158
+ const manifest = await fetchRegistryManifest(baseUrl, options);
159
+ const snippetEntry = manifest.snippets[name];
160
+ if (!snippetEntry) {
161
+ throw new RemoteRegistryFetchError(`${normalizeBaseUrl(baseUrl)}/${name}.yaml`, 404);
162
+ }
163
+ const [definition, files] = await Promise.all([
164
+ fetchSnippetDefinition(baseUrl, name, options),
165
+ fetchRemoteFiles(baseUrl, name, snippetEntry.files, options),
166
+ ]);
167
+ return { definition, files };
168
+ }
169
+ /**
170
+ * リモートから取得したテンプレートファイルの変数を展開する
171
+ */
172
+ export function expandRemoteTemplateFiles(files, variables) {
173
+ const result = new Map();
174
+ for (const [filePath, content] of files) {
175
+ const expandedPath = expandPath(filePath, variables);
176
+ const expandedContent = expandTemplate(content, variables);
177
+ result.set(expandedPath, expandedContent);
178
+ }
179
+ return result;
180
+ }
181
+ //# sourceMappingURL=remote-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-registry.js","sourceRoot":"","sources":["../src/remote-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * YAML ドキュメントの最大許容サイズ(バイト)。
3
+ * 64 KB を上限とする。通常の snippet 定義には十分な値。
4
+ */
5
+ export declare const YAML_MAX_SIZE_BYTES: number;
6
+ /**
7
+ * 安全な設定で YAML をパースする。
8
+ *
9
+ * - 入力サイズが YAML_MAX_SIZE_BYTES を超える場合は ValidationError を投げる
10
+ * - CORE_SCHEMA を使用し、JS 固有のカスタムタグ(!!js/function 等)を禁止する
11
+ *
12
+ * @param content パースする YAML 文字列
13
+ * @returns パース結果
14
+ * @throws ValidationError 入力サイズ超過、または無効な YAML の場合
15
+ */
16
+ export declare function safeParseYaml(content: string): unknown;
17
+ /**
18
+ * JSON Schema オブジェクト内に $ref キーが存在しないか検証する。
19
+ *
20
+ * $ref を使った外部スキーマ読み込み攻撃を防ぐため、
21
+ * snippet.yaml 内の schema フィールドには $ref を禁止する。
22
+ *
23
+ * @param schema 検証対象の schema 値(any)
24
+ * @throws ValidationError $ref が見つかった場合
25
+ */
26
+ export declare function checkNoRefInSchema(schema: unknown): void;
@@ -8,13 +8,11 @@
8
8
  */
9
9
  import yaml from "js-yaml";
10
10
  import { ValidationError } from "./errors.js";
11
-
12
11
  /**
13
12
  * YAML ドキュメントの最大許容サイズ(バイト)。
14
13
  * 64 KB を上限とする。通常の snippet 定義には十分な値。
15
14
  */
16
15
  export const YAML_MAX_SIZE_BYTES = 64 * 1024; // 64 KB
17
-
18
16
  /**
19
17
  * 安全な設定で YAML をパースする。
20
18
  *
@@ -25,29 +23,26 @@ export const YAML_MAX_SIZE_BYTES = 64 * 1024; // 64 KB
25
23
  * @returns パース結果
26
24
  * @throws ValidationError 入力サイズ超過、または無効な YAML の場合
27
25
  */
28
- export function safeParseYaml(content: string): unknown {
29
- // 入力サイズチェック(YAML Bomb 対策)
30
- const sizeBytes = Buffer.byteLength(content, "utf-8");
31
- if (sizeBytes > YAML_MAX_SIZE_BYTES) {
32
- throw new ValidationError(
33
- `YAML ドキュメントのサイズが上限 (${YAML_MAX_SIZE_BYTES} バイト) を超えています: ${sizeBytes} バイト`,
34
- );
35
- }
36
-
37
- try {
38
- // CORE_SCHEMA: bool/int/float/null のみを認識し、JS 固有タグ(!!js/function 等)を拒否する
39
- return yaml.load(content, {
40
- schema: yaml.CORE_SCHEMA,
41
- });
42
- } catch (err) {
43
- if (err instanceof ValidationError) {
44
- throw err;
26
+ export function safeParseYaml(content) {
27
+ // 入力サイズチェック(YAML Bomb 対策)
28
+ const sizeBytes = Buffer.byteLength(content, "utf-8");
29
+ if (sizeBytes > YAML_MAX_SIZE_BYTES) {
30
+ throw new ValidationError(`YAML ドキュメントのサイズが上限 (${YAML_MAX_SIZE_BYTES} バイト) を超えています: ${sizeBytes} バイト`);
31
+ }
32
+ try {
33
+ // CORE_SCHEMA: bool/int/float/null のみを認識し、JS 固有タグ(!!js/function 等)を拒否する
34
+ return yaml.load(content, {
35
+ schema: yaml.CORE_SCHEMA,
36
+ });
37
+ }
38
+ catch (err) {
39
+ if (err instanceof ValidationError) {
40
+ throw err;
41
+ }
42
+ const message = err instanceof Error ? err.message : String(err);
43
+ throw new ValidationError(`YAML パースエラー: ${message}`);
45
44
  }
46
- const message = err instanceof Error ? err.message : String(err);
47
- throw new ValidationError(`YAML パースエラー: ${message}`);
48
- }
49
45
  }
50
-
51
46
  /**
52
47
  * JSON Schema オブジェクト内に $ref キーが存在しないか検証する。
53
48
  *
@@ -57,15 +52,14 @@ export function safeParseYaml(content: string): unknown {
57
52
  * @param schema 検証対象の schema 値(any)
58
53
  * @throws ValidationError $ref が見つかった場合
59
54
  */
60
- export function checkNoRefInSchema(schema: unknown): void {
61
- if (schema === null || schema === undefined) {
62
- return;
63
- }
64
- // JSON.stringify 経由で深いネストも含めて $ref キーを検出する
65
- const serialized = JSON.stringify(schema);
66
- if (serialized.includes('"$ref"')) {
67
- throw new ValidationError(
68
- 'snippet の schema フィールドに "$ref" を使用することはセキュリティ上禁止されています',
69
- );
70
- }
55
+ export function checkNoRefInSchema(schema) {
56
+ if (schema === null || schema === undefined) {
57
+ return;
58
+ }
59
+ // JSON.stringify 経由で深いネストも含めて $ref キーを検出する
60
+ const serialized = JSON.stringify(schema);
61
+ if (serialized.includes('"$ref"')) {
62
+ throw new ValidationError('snippet の schema フィールドに "$ref" を使用することはセキュリティ上禁止されています');
63
+ }
71
64
  }
65
+ //# sourceMappingURL=safe-yaml-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-yaml-parser.js","sourceRoot":"","sources":["../src/safe-yaml-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,QAAQ;AAEtD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,0BAA0B;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,SAAS,GAAG,mBAAmB,EAAE,CAAC;QACpC,MAAM,IAAI,eAAe,CACvB,uBAAuB,mBAAmB,kBAAkB,SAAS,MAAM,CAC5E,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,wEAAwE;QACxE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;YACxB,MAAM,EAAE,IAAI,CAAC,WAAW;SACzB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;YACnC,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,eAAe,CAAC,gBAAgB,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAe;IAChD,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO;IACT,CAAC;IACD,2CAA2C;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,eAAe,CACvB,wDAAwD,CACzD,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,38 @@
1
+ export interface VariableSchema {
2
+ type?: "string" | "number" | "boolean";
3
+ default?: unknown;
4
+ enum?: unknown[];
5
+ }
6
+ export interface VariableDefinition {
7
+ name?: string;
8
+ description?: string;
9
+ suggests?: string[];
10
+ schema?: VariableSchema;
11
+ }
12
+ export interface Action {
13
+ echo?: string;
14
+ exit?: boolean;
15
+ if?: string;
16
+ input?: Record<string, {
17
+ name?: string;
18
+ description?: string;
19
+ schema?: VariableSchema;
20
+ "answer-to"?: string;
21
+ }>;
22
+ }
23
+ export interface SnippetDefinition {
24
+ name: string;
25
+ /** semver 形式のバージョン文字列 (例: "1.0.0")。省略時は未バージョン管理扱い */
26
+ version?: string;
27
+ description?: string;
28
+ tags?: string[];
29
+ dependencies?: string[];
30
+ variables?: Record<string, VariableDefinition>;
31
+ hooks?: {
32
+ "before-install"?: Action[];
33
+ "after-install"?: Action[];
34
+ };
35
+ }
36
+ export declare function parseSnippetYaml(content: string): SnippetDefinition;
37
+ export declare function serializeSnippetYaml(def: SnippetDefinition): string;
38
+ export declare function validateSnippetDefinition(def: SnippetDefinition): void;
@@ -0,0 +1,63 @@
1
+ import yaml from "js-yaml";
2
+ import { ValidationError } from "./errors.js";
3
+ import { validateSnippetName } from "./validate-name.js";
4
+ import { safeParseYaml, checkNoRefInSchema } from "./safe-yaml-parser.js";
5
+ export function parseSnippetYaml(content) {
6
+ // 安全なパーサー使用(サイズ制限・カスタムタグ禁止)
7
+ const parsed = safeParseYaml(content);
8
+ if (typeof parsed !== "object" || parsed === null) {
9
+ throw new ValidationError("snippet YAML のパースに失敗しました");
10
+ }
11
+ const def = parsed;
12
+ validateSnippetDefinition(def);
13
+ return def;
14
+ }
15
+ export function serializeSnippetYaml(def) {
16
+ return yaml.dump(def, { noRefs: true, lineWidth: -1 });
17
+ }
18
+ export function validateSnippetDefinition(def) {
19
+ if (!def.name || typeof def.name !== "string") {
20
+ throw new ValidationError("snippet 定義に name フィールドが必要です");
21
+ }
22
+ validateSnippetName(def.name);
23
+ if (def.dependencies !== undefined) {
24
+ if (!Array.isArray(def.dependencies)) {
25
+ throw new ValidationError("dependencies は配列でなければなりません");
26
+ }
27
+ for (const dep of def.dependencies) {
28
+ if (typeof dep !== "string") {
29
+ throw new ValidationError(`dependencies の各要素は文字列でなければなりません。受け取った値: ${typeof dep}`);
30
+ }
31
+ validateSnippetName(dep);
32
+ }
33
+ }
34
+ if (def.variables !== undefined) {
35
+ if (typeof def.variables !== "object" || def.variables === null) {
36
+ throw new ValidationError("variables はオブジェクトでなければなりません");
37
+ }
38
+ for (const [key, varDef] of Object.entries(def.variables)) {
39
+ if (typeof varDef !== "object" || varDef === null) {
40
+ throw new ValidationError(`変数 "${key}" の定義はオブジェクトでなければなりません`);
41
+ }
42
+ if (varDef.suggests !== undefined) {
43
+ if (!Array.isArray(varDef.suggests)) {
44
+ throw new ValidationError(`変数 "${key}" の suggests は配列でなければなりません`);
45
+ }
46
+ for (const item of varDef.suggests) {
47
+ if (typeof item !== "string") {
48
+ throw new ValidationError(`変数 "${key}" の suggests の各要素は文字列でなければなりません`);
49
+ }
50
+ }
51
+ }
52
+ // $ref によるJSON Schema外部参照攻撃を禁止
53
+ checkNoRefInSchema(varDef.schema);
54
+ if (varDef.schema?.type !== undefined) {
55
+ const validTypes = ["string", "number", "boolean"];
56
+ if (!validTypes.includes(varDef.schema.type)) {
57
+ throw new ValidationError(`変数 "${key}" の type "${varDef.schema.type}" は無効です。string, number, boolean のいずれかを指定してください`);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ //# sourceMappingURL=snippet-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snippet-schema.js","sourceRoot":"","sources":["../src/snippet-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function expandTemplate(template: string, variables: Record<string, unknown>): string;
2
+ export declare function expandPath(pathTemplate: string, variables: Record<string, unknown>): string;
3
+ export declare function extractVariables(template: string): string[];
4
+ export declare function extractVariablesFromDirectory(dirPath: string): string[];
5
+ export declare function expandTemplateDirectory(registryPath: string, snippetName: string, variables: Record<string, unknown>): Map<string, string>;
@@ -0,0 +1,130 @@
1
+ import Handlebars from "handlebars";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { listTemplateFiles, readTemplateFile, } from "./registry.js";
5
+ import { HELPER_NAMES, registerHelpers } from "./helpers/index.js";
6
+ // 隔離インスタンスを作成しヘルパーを登録
7
+ const hbs = Handlebars.create();
8
+ registerHelpers(hbs);
9
+ export function expandTemplate(template, variables) {
10
+ const compiled = hbs.compile(template, { noEscape: true });
11
+ return compiled(variables);
12
+ }
13
+ export function expandPath(pathTemplate, variables) {
14
+ const expanded = expandTemplate(pathTemplate, variables);
15
+ // 展開後のパスを正規化(空セグメント除去、区切り文字統一)
16
+ return path.normalize(expanded);
17
+ }
18
+ /** Handlebars 組み込みヘルパー名 */
19
+ const BUILTIN_HELPERS = new Set([
20
+ "if",
21
+ "unless",
22
+ "each",
23
+ "with",
24
+ "lookup",
25
+ "log",
26
+ ]);
27
+ export function extractVariables(template) {
28
+ const ast = Handlebars.parse(template);
29
+ const vars = new Set();
30
+ function collectVarFromExpression(expr) {
31
+ if (expr.type === "PathExpression") {
32
+ const pathExpr = expr;
33
+ vars.add(pathExpr.parts[0]);
34
+ }
35
+ else if (expr.type === "SubExpression") {
36
+ // サブ式: {{lowercase (replace name "/" ".")}} 等
37
+ const sub = expr;
38
+ // sub.path はヘルパー名なのでスキップ、params のみ抽出
39
+ if (sub.params) {
40
+ for (const param of sub.params)
41
+ collectVarFromExpression(param);
42
+ }
43
+ }
44
+ }
45
+ function visit(node) {
46
+ if (node.type === "MustacheStatement") {
47
+ const stmt = node;
48
+ if (stmt.params && stmt.params.length > 0) {
49
+ // ヘルパー呼び出し: path はヘルパー名なのでスキップ、params のみ変数抽出
50
+ for (const param of stmt.params)
51
+ collectVarFromExpression(param);
52
+ }
53
+ else {
54
+ // 単純な変数参照: {{name}}
55
+ if (stmt.path)
56
+ collectVarFromExpression(stmt.path);
57
+ }
58
+ }
59
+ if (node.type === "BlockStatement") {
60
+ const block = node;
61
+ // #if, #unless, #each 等のパラメータから変数を抽出
62
+ if (block.params) {
63
+ for (const param of block.params)
64
+ collectVarFromExpression(param);
65
+ }
66
+ if (block.program)
67
+ visit(block.program);
68
+ if (block.inverse)
69
+ visit(block.inverse);
70
+ }
71
+ if ("body" in node && Array.isArray(node.body)) {
72
+ for (const child of node.body) {
73
+ visit(child);
74
+ }
75
+ }
76
+ }
77
+ visit(ast);
78
+ // ヘルパー名・組み込みヘルパー名を除外
79
+ for (const name of HELPER_NAMES)
80
+ vars.delete(name);
81
+ for (const name of BUILTIN_HELPERS)
82
+ vars.delete(name);
83
+ return [...vars];
84
+ }
85
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
86
+ export function extractVariablesFromDirectory(dirPath) {
87
+ const allVars = new Set();
88
+ function walkDir(currentPath) {
89
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
90
+ for (const entry of entries) {
91
+ if (IGNORED_FILES.has(entry.name))
92
+ continue;
93
+ const fullPath = path.join(currentPath, entry.name);
94
+ if (entry.isDirectory()) {
95
+ // ディレクトリ名からも変数を抽出
96
+ for (const v of extractVariables(entry.name)) {
97
+ allVars.add(v);
98
+ }
99
+ walkDir(fullPath);
100
+ }
101
+ else {
102
+ // ファイル名から変数を抽出
103
+ for (const v of extractVariables(entry.name)) {
104
+ allVars.add(v);
105
+ }
106
+ // ファイル内容から変数を抽出
107
+ const content = fs.readFileSync(fullPath, "utf-8");
108
+ for (const v of extractVariables(content)) {
109
+ allVars.add(v);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ if (fs.existsSync(dirPath)) {
115
+ walkDir(dirPath);
116
+ }
117
+ return [...allVars];
118
+ }
119
+ export function expandTemplateDirectory(registryPath, snippetName, variables) {
120
+ const files = listTemplateFiles(registryPath, snippetName);
121
+ const result = new Map();
122
+ for (const filePath of files) {
123
+ const expandedPath = expandPath(filePath, variables);
124
+ const content = readTemplateFile(registryPath, snippetName, filePath);
125
+ const expandedContent = expandTemplate(content, variables);
126
+ result.set(expandedPath, expandedContent);
127
+ }
128
+ return result;
129
+ }
130
+ //# sourceMappingURL=template-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-engine.js","sourceRoot":"","sources":["../src/template-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function validateSnippetName(name: string): void;
@@ -0,0 +1,9 @@
1
+ import { ValidationError } from "./errors.js";
2
+ import { t } from "./i18n/index.js";
3
+ const SNIPPET_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*$/;
4
+ export function validateSnippetName(name) {
5
+ if (!SNIPPET_NAME_PATTERN.test(name)) {
6
+ throw new ValidationError(t("error.invalid-snippet-name", { name }));
7
+ }
8
+ }
9
+ //# sourceMappingURL=validate-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-name.js","sourceRoot":"","sources":["../src/validate-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEpC,MAAM,oBAAoB,GAAG,4BAA4B,CAAC;AAE1D,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,eAAe,CACvB,CAAC,CAAC,4BAA4B,EAAE,EAAE,IAAI,EAAE,CAAC,CAC1C,CAAC;IACJ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tbsten/mir-core",
3
- "version": "0.0.2-alpha02",
3
+ "version": "0.0.2-alpha04",
4
4
  "description": "スニペット配布・取得ツール mir のコアロジック(テンプレート、スキーマ、i18n)",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -21,6 +21,10 @@
21
21
  "bugs": {
22
22
  "url": "https://github.com/tbsten/mir/issues"
23
23
  },
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
24
28
  "exports": {
25
29
  ".": "./dist/index.js"
26
30
  },
@@ -35,6 +39,7 @@
35
39
  "js-yaml": "^4.1.1"
36
40
  },
37
41
  "scripts": {
42
+ "build": "tsc",
38
43
  "test": "vitest run",
39
44
  "test:watch": "vitest",
40
45
  "typecheck": "tsc --noEmit"