create-takt-sdd 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18n.js +16 -2
- package/dist/install.js +150 -21
- package/package.json +1 -1
package/dist/i18n.js
CHANGED
|
@@ -2,8 +2,10 @@ const en = {
|
|
|
2
2
|
downloading: "Downloading takt-sdd...",
|
|
3
3
|
downloadingVersion: (tag) => `Downloading takt-sdd ${tag}...`,
|
|
4
4
|
installing: "Installing pieces and facets to .takt/...",
|
|
5
|
+
updating: "Updating takt-sdd...",
|
|
5
6
|
existsError: (cmd) => `.takt/ already exists. To overwrite, run:\n ${cmd} --force`,
|
|
6
7
|
complete: "Installation complete!",
|
|
8
|
+
updateComplete: "Update complete!",
|
|
7
9
|
dryRunHeader: "[dry-run] The following files would be installed:",
|
|
8
10
|
dryRunItem: (path) => ` ${path}`,
|
|
9
11
|
dryRunSkipped: "[dry-run] No files were written.",
|
|
@@ -13,6 +15,7 @@ const en = {
|
|
|
13
15
|
scriptsSkipped: (keys) => `Skipped existing scripts: ${keys.join(", ")}`,
|
|
14
16
|
scriptsCreated: "Created package.json with npm scripts and devDependencies",
|
|
15
17
|
depsAdded: (keys) => `Added devDependencies: ${keys.join(", ")}`,
|
|
18
|
+
depsUpdated: (keys) => `Updated devDependencies: ${keys.join(", ")}`,
|
|
16
19
|
installingSkills: "Installing takt skills to .agent/skills/...",
|
|
17
20
|
skillInstalled: (name) => `Installed skill: ${name}`,
|
|
18
21
|
skillSymlinked: (name, target) => `Symlinked ${target}/${name} -> .agent/skills/${name}`,
|
|
@@ -20,12 +23,16 @@ const en = {
|
|
|
20
23
|
taktRefsInstalled: "Installed takt references (builtins, docs)",
|
|
21
24
|
taktRefsSkipped: "Takt references already exist, skipping",
|
|
22
25
|
taktRefsError: "Warning: Failed to download takt references. Skills may not find style guides.",
|
|
26
|
+
fileAdded: (path) => `Added: ${path}`,
|
|
27
|
+
fileUpdated: (path) => `Updated: ${path}`,
|
|
28
|
+
fileSkippedCustomized: (path) => `Skipped (customized): ${path}`,
|
|
29
|
+
manifestCreated: "Created install manifest",
|
|
23
30
|
helpText: `Usage: npx create-takt-sdd [options]
|
|
24
31
|
|
|
25
32
|
Options:
|
|
26
33
|
--tag <version> Version to install ("latest", "0.2.0", default: installer version)
|
|
27
34
|
--lang <en|ja> Message language (default: en)
|
|
28
|
-
--force Overwrite existing .takt/ directory
|
|
35
|
+
--force Overwrite existing .takt/ directory (ignored if manifest exists)
|
|
29
36
|
--dry-run Preview without writing files
|
|
30
37
|
--without-skills Skip installing takt skills to .agent/skills/
|
|
31
38
|
--refs-path <path> Path for takt references (default: references/takt)
|
|
@@ -49,8 +56,10 @@ const ja = {
|
|
|
49
56
|
downloading: "takt-sdd をダウンロード中...",
|
|
50
57
|
downloadingVersion: (tag) => `takt-sdd ${tag} をダウンロード中...`,
|
|
51
58
|
installing: ".takt/ にピースとファセットをインストール中...",
|
|
59
|
+
updating: "takt-sdd をアップデート中...",
|
|
52
60
|
existsError: (cmd) => `.takt/ が既に存在します。上書きするには以下を実行してください:\n ${cmd} --force`,
|
|
53
61
|
complete: "インストール完了!",
|
|
62
|
+
updateComplete: "アップデート完了!",
|
|
54
63
|
dryRunHeader: "[dry-run] 以下のファイルがインストールされます:",
|
|
55
64
|
dryRunItem: (path) => ` ${path}`,
|
|
56
65
|
dryRunSkipped: "[dry-run] ファイルは書き込まれませんでした。",
|
|
@@ -60,6 +69,7 @@ const ja = {
|
|
|
60
69
|
scriptsSkipped: (keys) => `既存のスクリプトをスキップしました: ${keys.join(", ")}`,
|
|
61
70
|
scriptsCreated: "npm scripts と devDependencies 付きの package.json を作成しました",
|
|
62
71
|
depsAdded: (keys) => `devDependencies を追加しました: ${keys.join(", ")}`,
|
|
72
|
+
depsUpdated: (keys) => `devDependencies を更新しました: ${keys.join(", ")}`,
|
|
63
73
|
installingSkills: ".agent/skills/ に takt スキルをインストール中...",
|
|
64
74
|
skillInstalled: (name) => `スキルをインストールしました: ${name}`,
|
|
65
75
|
skillSymlinked: (name, target) => `シンボリックリンク作成: ${target}/${name} -> .agent/skills/${name}`,
|
|
@@ -67,12 +77,16 @@ const ja = {
|
|
|
67
77
|
taktRefsInstalled: "takt リファレンスをインストールしました(builtins, docs)",
|
|
68
78
|
taktRefsSkipped: "takt リファレンスは既に存在するためスキップしました",
|
|
69
79
|
taktRefsError: "警告: takt リファレンスのダウンロードに失敗しました。スキルがスタイルガイドを参照できない可能性があります。",
|
|
80
|
+
fileAdded: (path) => `追加: ${path}`,
|
|
81
|
+
fileUpdated: (path) => `更新: ${path}`,
|
|
82
|
+
fileSkippedCustomized: (path) => `スキップ(カスタマイズ済み): ${path}`,
|
|
83
|
+
manifestCreated: "インストールマニフェストを作成しました",
|
|
70
84
|
helpText: `使い方: npx create-takt-sdd [オプション]
|
|
71
85
|
|
|
72
86
|
オプション:
|
|
73
87
|
--tag <version> インストールするバージョン ("latest", "0.2.0", デフォルト: インストーラのバージョン)
|
|
74
88
|
--lang <en|ja> メッセージ言語 (デフォルト: en)
|
|
75
|
-
--force 既存の .takt/
|
|
89
|
+
--force 既存の .takt/ を上書き(マニフェストがある場合は無視)
|
|
76
90
|
--dry-run プレビューのみ(ファイル書き込みなし)
|
|
77
91
|
--without-skills takt スキルのインストールをスキップ
|
|
78
92
|
--refs-path <path> takt リファレンスのパス(デフォルト: references/takt)
|
package/dist/install.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, statSync, writeFileSync } from "node:fs";
|
|
3
4
|
import https from "node:https";
|
|
4
5
|
import { createWriteStream } from "node:fs";
|
|
@@ -144,9 +145,68 @@ function collectFiles(dir, base) {
|
|
|
144
145
|
}
|
|
145
146
|
return results;
|
|
146
147
|
}
|
|
148
|
+
const MANIFEST_FILE = ".manifest.json";
|
|
149
|
+
function computeFileHash(filePath) {
|
|
150
|
+
const content = readFileSync(filePath);
|
|
151
|
+
return createHash("sha256").update(content).digest("hex");
|
|
152
|
+
}
|
|
153
|
+
function loadManifest(manifestPath) {
|
|
154
|
+
if (!existsSync(manifestPath))
|
|
155
|
+
return null;
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function syncDirectory(srcDir, destDir, srcBase, destBase, manifest, msg, cwd) {
|
|
164
|
+
const files = {};
|
|
165
|
+
if (!existsSync(srcDir))
|
|
166
|
+
return { files };
|
|
167
|
+
const srcFiles = collectFiles(srcDir, srcBase);
|
|
168
|
+
for (const relFile of srcFiles) {
|
|
169
|
+
const srcPath = join(srcBase, relFile);
|
|
170
|
+
const destPath = join(destBase, relFile);
|
|
171
|
+
const manifestKey = relative(cwd, destPath).split("\\").join("/");
|
|
172
|
+
const srcHash = computeFileHash(srcPath);
|
|
173
|
+
files[manifestKey] = srcHash;
|
|
174
|
+
if (!existsSync(destPath)) {
|
|
175
|
+
// New file — copy
|
|
176
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
177
|
+
cpSync(srcPath, destPath);
|
|
178
|
+
info(msg.fileAdded(manifestKey));
|
|
179
|
+
}
|
|
180
|
+
else if (manifest !== null) {
|
|
181
|
+
const recordedHash = manifest.files[manifestKey];
|
|
182
|
+
if (recordedHash === undefined) {
|
|
183
|
+
// File exists but not in manifest (pre-manifest environment)
|
|
184
|
+
warn(msg.fileSkippedCustomized(manifestKey));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const currentHash = computeFileHash(destPath);
|
|
188
|
+
if (currentHash === recordedHash) {
|
|
189
|
+
// User has not modified — safe to overwrite
|
|
190
|
+
cpSync(srcPath, destPath);
|
|
191
|
+
info(msg.fileUpdated(manifestKey));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// User has customized — skip
|
|
195
|
+
warn(msg.fileSkippedCustomized(manifestKey));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// No manifest, fresh install or force — overwrite
|
|
201
|
+
cpSync(srcPath, destPath);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { files };
|
|
205
|
+
}
|
|
147
206
|
export async function install(options) {
|
|
148
207
|
const msg = getMessages(options.lang);
|
|
149
208
|
const targetPath = join(options.cwd, TARGET_DIR);
|
|
209
|
+
const manifestPath = join(targetPath, MANIFEST_FILE);
|
|
150
210
|
// tar の存在チェック
|
|
151
211
|
try {
|
|
152
212
|
execSync("which tar", { stdio: "ignore" });
|
|
@@ -154,8 +214,11 @@ export async function install(options) {
|
|
|
154
214
|
catch {
|
|
155
215
|
errorExit(msg.tarNotFound);
|
|
156
216
|
}
|
|
157
|
-
//
|
|
158
|
-
|
|
217
|
+
// マニフェスト読み込み&モード判定
|
|
218
|
+
const manifest = loadManifest(manifestPath);
|
|
219
|
+
const isUpdate = manifest !== null;
|
|
220
|
+
const piecesExist = existsSync(join(targetPath, "pieces"));
|
|
221
|
+
if (!isUpdate && piecesExist && !options.force) {
|
|
159
222
|
errorExit(msg.existsError("npx create-takt-sdd"));
|
|
160
223
|
}
|
|
161
224
|
// ダウンロード
|
|
@@ -208,17 +271,19 @@ export async function install(options) {
|
|
|
208
271
|
info(msg.dryRunSkipped);
|
|
209
272
|
return;
|
|
210
273
|
}
|
|
211
|
-
// インストール
|
|
212
|
-
info(msg.installing);
|
|
274
|
+
// インストール / アップデート
|
|
275
|
+
info(isUpdate ? msg.updating : msg.installing);
|
|
213
276
|
mkdirSync(targetPath, { recursive: true });
|
|
277
|
+
const allFiles = {};
|
|
214
278
|
for (const dir of FACET_DIRS) {
|
|
215
279
|
const srcDir = join(extractedTakt, options.lang, dir);
|
|
216
280
|
if (existsSync(srcDir)) {
|
|
217
281
|
const destDir = join(targetPath, dir);
|
|
218
|
-
if (existsSync(destDir)) {
|
|
282
|
+
if (!isUpdate && existsSync(destDir)) {
|
|
219
283
|
rmSync(destDir, { recursive: true });
|
|
220
284
|
}
|
|
221
|
-
|
|
285
|
+
const result = syncDirectory(srcDir, destDir, join(extractedTakt, options.lang), targetPath, isUpdate ? manifest : null, msg, options.cwd);
|
|
286
|
+
Object.assign(allFiles, result.files);
|
|
222
287
|
}
|
|
223
288
|
}
|
|
224
289
|
// .gitignore は takt が初回実行時に自動配置するため、インストーラでは生成しない
|
|
@@ -232,20 +297,20 @@ export async function install(options) {
|
|
|
232
297
|
const skillSrc = join(extractedSkillsDir, skill);
|
|
233
298
|
if (!existsSync(skillSrc))
|
|
234
299
|
continue;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
cpSync(skillSrc,
|
|
300
|
+
// Prepare a temp copy with language selection applied
|
|
301
|
+
const skillTmp = join(tmpDir, "skill-prep", skill);
|
|
302
|
+
if (existsSync(skillTmp))
|
|
303
|
+
rmSync(skillTmp, { recursive: true });
|
|
304
|
+
cpSync(skillSrc, skillTmp, { recursive: true });
|
|
240
305
|
// Select SKILL.md based on language
|
|
241
|
-
const skillLangMd = join(
|
|
242
|
-
const skillMdPath = join(
|
|
306
|
+
const skillLangMd = join(skillTmp, `SKILL.${options.lang}.md`);
|
|
307
|
+
const skillMdPath = join(skillTmp, "SKILL.md");
|
|
243
308
|
if (existsSync(skillLangMd)) {
|
|
244
309
|
cpSync(skillLangMd, skillMdPath);
|
|
245
310
|
}
|
|
246
|
-
// Remove language-specific SKILL files
|
|
311
|
+
// Remove language-specific SKILL files from temp
|
|
247
312
|
for (const l of ["ja", "en"]) {
|
|
248
|
-
const langFile = join(
|
|
313
|
+
const langFile = join(skillTmp, `SKILL.${l}.md`);
|
|
249
314
|
if (existsSync(langFile)) {
|
|
250
315
|
rmSync(langFile);
|
|
251
316
|
}
|
|
@@ -258,6 +323,12 @@ export async function install(options) {
|
|
|
258
323
|
writeFileSync(skillMdPath, updated, "utf-8");
|
|
259
324
|
}
|
|
260
325
|
}
|
|
326
|
+
const skillDest = join(agentSkillsDir, skill);
|
|
327
|
+
if (!isUpdate && existsSync(skillDest)) {
|
|
328
|
+
rmSync(skillDest, { recursive: true });
|
|
329
|
+
}
|
|
330
|
+
const result = syncDirectory(skillTmp, skillDest, skillTmp, skillDest, isUpdate ? manifest : null, msg, options.cwd);
|
|
331
|
+
Object.assign(allFiles, result.files);
|
|
261
332
|
info(msg.skillInstalled(skill));
|
|
262
333
|
}
|
|
263
334
|
// .claude/skills/ と .codex/skills/ にシンボリックリンクを作成
|
|
@@ -279,7 +350,10 @@ export async function install(options) {
|
|
|
279
350
|
// takt リファレンスのダウンロード(スキルが参照するbuiltins等)
|
|
280
351
|
if (!options.withoutSkills) {
|
|
281
352
|
const refsDir = join(options.cwd, options.refsPath);
|
|
282
|
-
|
|
353
|
+
const needsRefDownload = isUpdate
|
|
354
|
+
? manifest.taktRefHash !== TAKT_REF_HASH
|
|
355
|
+
: !existsSync(join(refsDir, "builtins"));
|
|
356
|
+
if (needsRefDownload) {
|
|
283
357
|
info(msg.downloadingTaktRefs(options.refsPath));
|
|
284
358
|
const taktTmpDir = mkdtempSync(join(tmpdir(), "takt-refs-"));
|
|
285
359
|
try {
|
|
@@ -292,16 +366,45 @@ export async function install(options) {
|
|
|
292
366
|
if (taktExtracted) {
|
|
293
367
|
const taktRoot = join(taktTmpDir, taktExtracted);
|
|
294
368
|
mkdirSync(refsDir, { recursive: true });
|
|
295
|
-
// builtins/
|
|
369
|
+
// builtins/ を syncDirectory でコピー
|
|
296
370
|
const builtinsSrc = join(taktRoot, "builtins");
|
|
297
371
|
if (existsSync(builtinsSrc)) {
|
|
298
|
-
|
|
372
|
+
const builtinsDest = join(refsDir, "builtins");
|
|
373
|
+
const result = syncDirectory(builtinsSrc, builtinsDest, builtinsSrc, builtinsDest, isUpdate ? manifest : null, msg, options.cwd);
|
|
374
|
+
Object.assign(allFiles, result.files);
|
|
299
375
|
}
|
|
300
376
|
// docs/faceted-prompting.ja.md をコピー
|
|
301
377
|
const fpSrc = join(taktRoot, "docs", "faceted-prompting.ja.md");
|
|
302
378
|
if (existsSync(fpSrc)) {
|
|
303
|
-
|
|
304
|
-
|
|
379
|
+
const docsDir = join(refsDir, "docs");
|
|
380
|
+
mkdirSync(docsDir, { recursive: true });
|
|
381
|
+
const fpDest = join(docsDir, "faceted-prompting.ja.md");
|
|
382
|
+
const fpKey = relative(options.cwd, fpDest).split("\\").join("/");
|
|
383
|
+
const fpHash = computeFileHash(fpSrc);
|
|
384
|
+
if (!existsSync(fpDest)) {
|
|
385
|
+
cpSync(fpSrc, fpDest);
|
|
386
|
+
info(msg.fileAdded(fpKey));
|
|
387
|
+
}
|
|
388
|
+
else if (isUpdate) {
|
|
389
|
+
const recordedHash = manifest.files[fpKey];
|
|
390
|
+
if (recordedHash === undefined) {
|
|
391
|
+
warn(msg.fileSkippedCustomized(fpKey));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const currentHash = computeFileHash(fpDest);
|
|
395
|
+
if (currentHash === recordedHash) {
|
|
396
|
+
cpSync(fpSrc, fpDest);
|
|
397
|
+
info(msg.fileUpdated(fpKey));
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
warn(msg.fileSkippedCustomized(fpKey));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
cpSync(fpSrc, fpDest);
|
|
406
|
+
}
|
|
407
|
+
allFiles[fpKey] = fpHash;
|
|
305
408
|
}
|
|
306
409
|
info(msg.taktRefsInstalled);
|
|
307
410
|
}
|
|
@@ -318,6 +421,14 @@ export async function install(options) {
|
|
|
318
421
|
}
|
|
319
422
|
else {
|
|
320
423
|
info(msg.taktRefsSkipped);
|
|
424
|
+
// Preserve existing file hashes from manifest for refs
|
|
425
|
+
if (isUpdate) {
|
|
426
|
+
for (const [key, hash] of Object.entries(manifest.files)) {
|
|
427
|
+
if (key.startsWith(options.refsPath)) {
|
|
428
|
+
allFiles[key] = hash;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
321
432
|
}
|
|
322
433
|
}
|
|
323
434
|
// アーカイブの package.json から devDependencies を取得
|
|
@@ -349,11 +460,16 @@ export async function install(options) {
|
|
|
349
460
|
pkg.scripts = scripts;
|
|
350
461
|
const devDeps = pkg.devDependencies ?? {};
|
|
351
462
|
const depsAdded = [];
|
|
463
|
+
const depsUpdatedKeys = [];
|
|
352
464
|
for (const [key, value] of Object.entries(sddDevDependencies)) {
|
|
353
465
|
if (devDeps[key] === undefined) {
|
|
354
466
|
devDeps[key] = value;
|
|
355
467
|
depsAdded.push(key);
|
|
356
468
|
}
|
|
469
|
+
else if (devDeps[key] !== value) {
|
|
470
|
+
devDeps[key] = value;
|
|
471
|
+
depsUpdatedKeys.push(key);
|
|
472
|
+
}
|
|
357
473
|
}
|
|
358
474
|
pkg.devDependencies = devDeps;
|
|
359
475
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
@@ -366,6 +482,9 @@ export async function install(options) {
|
|
|
366
482
|
if (depsAdded.length > 0) {
|
|
367
483
|
info(msg.depsAdded(depsAdded));
|
|
368
484
|
}
|
|
485
|
+
if (depsUpdatedKeys.length > 0) {
|
|
486
|
+
info(msg.depsUpdated(depsUpdatedKeys));
|
|
487
|
+
}
|
|
369
488
|
}
|
|
370
489
|
else {
|
|
371
490
|
const pkg = {
|
|
@@ -376,7 +495,17 @@ export async function install(options) {
|
|
|
376
495
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
377
496
|
info(msg.scriptsCreated);
|
|
378
497
|
}
|
|
379
|
-
|
|
498
|
+
// マニフェスト書き込み
|
|
499
|
+
const newManifest = {
|
|
500
|
+
version: version,
|
|
501
|
+
installedAt: new Date().toISOString(),
|
|
502
|
+
lang: options.lang,
|
|
503
|
+
taktRefHash: TAKT_REF_HASH,
|
|
504
|
+
files: allFiles,
|
|
505
|
+
};
|
|
506
|
+
writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2) + "\n", "utf-8");
|
|
507
|
+
info(msg.manifestCreated);
|
|
508
|
+
info(isUpdate ? msg.updateComplete : msg.complete);
|
|
380
509
|
console.log(msg.usageExamples);
|
|
381
510
|
console.log("");
|
|
382
511
|
}
|