create-takt-sdd 0.1.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/cli.js +74 -0
- package/dist/i18n.js +71 -0
- package/dist/install.js +149 -0
- package/package.json +35 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { isLang } from "./i18n.js";
|
|
6
|
+
import { getMessages } from "./i18n.js";
|
|
7
|
+
import { install } from "./install.js";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const packageRoot = resolve(__dirname, "..");
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
const args = {
|
|
13
|
+
lang: "en",
|
|
14
|
+
force: false,
|
|
15
|
+
dryRun: false,
|
|
16
|
+
help: false,
|
|
17
|
+
version: false,
|
|
18
|
+
};
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
switch (arg) {
|
|
22
|
+
case "--lang": {
|
|
23
|
+
const value = argv[++i];
|
|
24
|
+
if (!value || !isLang(value)) {
|
|
25
|
+
console.error(`Error: --lang requires "en" or "ja". Got: ${value ?? "(empty)"}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
args.lang = value;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case "--force":
|
|
32
|
+
args.force = true;
|
|
33
|
+
break;
|
|
34
|
+
case "--dry-run":
|
|
35
|
+
args.dryRun = true;
|
|
36
|
+
break;
|
|
37
|
+
case "-h":
|
|
38
|
+
case "--help":
|
|
39
|
+
args.help = true;
|
|
40
|
+
break;
|
|
41
|
+
case "-v":
|
|
42
|
+
case "--version":
|
|
43
|
+
args.version = true;
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
console.error(`Unknown option: ${arg}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return args;
|
|
51
|
+
}
|
|
52
|
+
async function main() {
|
|
53
|
+
const args = parseArgs(process.argv.slice(2));
|
|
54
|
+
if (args.version) {
|
|
55
|
+
const pkg = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf-8"));
|
|
56
|
+
console.log(pkg.version);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (args.help) {
|
|
60
|
+
const msg = getMessages(args.lang);
|
|
61
|
+
console.log(msg.helpText);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await install({
|
|
65
|
+
lang: args.lang,
|
|
66
|
+
force: args.force,
|
|
67
|
+
dryRun: args.dryRun,
|
|
68
|
+
cwd: process.cwd(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
main().catch((err) => {
|
|
72
|
+
console.error(err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const en = {
|
|
2
|
+
downloading: "Downloading takt-sdd...",
|
|
3
|
+
installing: "Installing pieces and facets to .takt/...",
|
|
4
|
+
existsError: (cmd) => `.takt/ already exists. To overwrite, run:\n ${cmd} --force`,
|
|
5
|
+
complete: "Installation complete!",
|
|
6
|
+
dryRunHeader: "[dry-run] The following files would be installed:",
|
|
7
|
+
dryRunItem: (path) => ` ${path}`,
|
|
8
|
+
dryRunSkipped: "[dry-run] No files were written.",
|
|
9
|
+
taktNotFound: "Warning: takt is not installed. Install it first: https://github.com/nrslib/takt",
|
|
10
|
+
tarNotFound: "Error: tar command is required.",
|
|
11
|
+
archiveError: "Error: .takt/ not found in the downloaded archive.",
|
|
12
|
+
helpText: `Usage: npx create-takt-sdd [options]
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--lang <en|ja> Message language (default: en)
|
|
16
|
+
--force Overwrite existing .takt/ directory
|
|
17
|
+
--dry-run Preview without writing files
|
|
18
|
+
-h, --help Show this help
|
|
19
|
+
-v, --version Show version`,
|
|
20
|
+
usageExamples: `
|
|
21
|
+
Installed to: .takt/
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
takt -w sdd -t "description of requirements"
|
|
25
|
+
|
|
26
|
+
Run individual phases:
|
|
27
|
+
takt -w sdd-requirements -t "description of requirements"
|
|
28
|
+
takt -w sdd-design
|
|
29
|
+
takt -w sdd-tasks
|
|
30
|
+
takt -w sdd-impl
|
|
31
|
+
takt -w sdd-validate-impl`,
|
|
32
|
+
};
|
|
33
|
+
const ja = {
|
|
34
|
+
downloading: "takt-sdd をダウンロード中...",
|
|
35
|
+
installing: ".takt/ にピースとファセットをインストール中...",
|
|
36
|
+
existsError: (cmd) => `.takt/ が既に存在します。上書きするには以下を実行してください:\n ${cmd} --force`,
|
|
37
|
+
complete: "インストール完了!",
|
|
38
|
+
dryRunHeader: "[dry-run] 以下のファイルがインストールされます:",
|
|
39
|
+
dryRunItem: (path) => ` ${path}`,
|
|
40
|
+
dryRunSkipped: "[dry-run] ファイルは書き込まれませんでした。",
|
|
41
|
+
taktNotFound: "警告: takt がインストールされていません。先にインストールしてください: https://github.com/nrslib/takt",
|
|
42
|
+
tarNotFound: "エラー: tar コマンドが必要です。",
|
|
43
|
+
archiveError: "エラー: ダウンロードしたアーカイブに .takt/ が見つかりません。",
|
|
44
|
+
helpText: `使い方: npx create-takt-sdd [オプション]
|
|
45
|
+
|
|
46
|
+
オプション:
|
|
47
|
+
--lang <en|ja> メッセージ言語 (デフォルト: en)
|
|
48
|
+
--force 既存の .takt/ を上書き
|
|
49
|
+
--dry-run プレビューのみ(ファイル書き込みなし)
|
|
50
|
+
-h, --help ヘルプを表示
|
|
51
|
+
-v, --version バージョンを表示`,
|
|
52
|
+
usageExamples: `
|
|
53
|
+
インストール先: .takt/
|
|
54
|
+
|
|
55
|
+
使い方:
|
|
56
|
+
takt -w sdd -t "要件の説明"
|
|
57
|
+
|
|
58
|
+
各フェーズの個別実行:
|
|
59
|
+
takt -w sdd-requirements -t "要件の説明"
|
|
60
|
+
takt -w sdd-design
|
|
61
|
+
takt -w sdd-tasks
|
|
62
|
+
takt -w sdd-impl
|
|
63
|
+
takt -w sdd-validate-impl`,
|
|
64
|
+
};
|
|
65
|
+
const messages = { en, ja };
|
|
66
|
+
export function getMessages(lang) {
|
|
67
|
+
return messages[lang];
|
|
68
|
+
}
|
|
69
|
+
export function isLang(value) {
|
|
70
|
+
return value === "en" || value === "ja";
|
|
71
|
+
}
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
3
|
+
import https from "node:https";
|
|
4
|
+
import { createWriteStream } from "node:fs";
|
|
5
|
+
import { mkdtempSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join, relative } from "node:path";
|
|
8
|
+
import { getMessages } from "./i18n.js";
|
|
9
|
+
const REPO = "j5ik2o/takt-sdd";
|
|
10
|
+
const BRANCH = "main";
|
|
11
|
+
const TARGET_DIR = ".takt";
|
|
12
|
+
const FACET_DIRS = [
|
|
13
|
+
"pieces",
|
|
14
|
+
"personas",
|
|
15
|
+
"policies",
|
|
16
|
+
"instructions",
|
|
17
|
+
"knowledge",
|
|
18
|
+
"output-contracts",
|
|
19
|
+
];
|
|
20
|
+
function info(msg) {
|
|
21
|
+
console.log(`\x1b[1;34m==>\x1b[0m ${msg}`);
|
|
22
|
+
}
|
|
23
|
+
function warn(msg) {
|
|
24
|
+
console.log(`\x1b[1;33m==>\x1b[0m ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
function errorExit(msg) {
|
|
27
|
+
console.error(`\x1b[1;31m==>\x1b[0m ${msg}`);
|
|
28
|
+
return process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
function download(url, dest) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const file = createWriteStream(dest);
|
|
33
|
+
const request = (targetUrl) => {
|
|
34
|
+
https
|
|
35
|
+
.get(targetUrl, (res) => {
|
|
36
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
37
|
+
const location = res.headers.location;
|
|
38
|
+
if (location) {
|
|
39
|
+
request(location);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (res.statusCode !== 200) {
|
|
44
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode}`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
res.pipe(file);
|
|
48
|
+
file.on("finish", () => {
|
|
49
|
+
file.close();
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
.on("error", reject);
|
|
54
|
+
};
|
|
55
|
+
request(url);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function collectFiles(dir, base) {
|
|
59
|
+
const results = [];
|
|
60
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
61
|
+
const full = join(dir, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
results.push(...collectFiles(full, base));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
results.push(relative(base, full));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
export async function install(options) {
|
|
72
|
+
const msg = getMessages(options.lang);
|
|
73
|
+
const targetPath = join(options.cwd, TARGET_DIR);
|
|
74
|
+
// takt の存在チェック
|
|
75
|
+
try {
|
|
76
|
+
execSync("which takt", { stdio: "ignore" });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
warn(msg.taktNotFound);
|
|
80
|
+
}
|
|
81
|
+
// tar の存在チェック
|
|
82
|
+
try {
|
|
83
|
+
execSync("which tar", { stdio: "ignore" });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
errorExit(msg.tarNotFound);
|
|
87
|
+
}
|
|
88
|
+
// 既存ディレクトリチェック
|
|
89
|
+
if (existsSync(join(targetPath, "pieces")) && !options.force) {
|
|
90
|
+
errorExit(msg.existsError("npx create-takt-sdd"));
|
|
91
|
+
}
|
|
92
|
+
// ダウンロード
|
|
93
|
+
info(msg.downloading);
|
|
94
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "takt-sdd-"));
|
|
95
|
+
const archivePath = join(tmpDir, "archive.tar.gz");
|
|
96
|
+
try {
|
|
97
|
+
const tarballUrl = `https://github.com/${REPO}/archive/refs/heads/${BRANCH}.tar.gz`;
|
|
98
|
+
await download(tarballUrl, archivePath);
|
|
99
|
+
execSync(`tar -xzf "${archivePath}" -C "${tmpDir}"`, { stdio: "ignore" });
|
|
100
|
+
const extractedDir = join(tmpDir, `takt-sdd-${BRANCH}`);
|
|
101
|
+
const extractedTakt = join(extractedDir, ".takt");
|
|
102
|
+
if (!existsSync(extractedTakt)) {
|
|
103
|
+
errorExit(msg.archiveError);
|
|
104
|
+
}
|
|
105
|
+
// dry-run: ファイル一覧のみ表示
|
|
106
|
+
if (options.dryRun) {
|
|
107
|
+
info(msg.dryRunHeader);
|
|
108
|
+
for (const dir of FACET_DIRS) {
|
|
109
|
+
const srcDir = join(extractedTakt, dir);
|
|
110
|
+
if (existsSync(srcDir)) {
|
|
111
|
+
for (const file of collectFiles(srcDir, extractedTakt)) {
|
|
112
|
+
console.log(msg.dryRunItem(join(TARGET_DIR, file)));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const gitignoreSrc = join(extractedTakt, ".gitignore");
|
|
117
|
+
if (existsSync(gitignoreSrc)) {
|
|
118
|
+
console.log(msg.dryRunItem(join(TARGET_DIR, ".gitignore")));
|
|
119
|
+
}
|
|
120
|
+
console.log("");
|
|
121
|
+
info(msg.dryRunSkipped);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// インストール
|
|
125
|
+
info(msg.installing);
|
|
126
|
+
mkdirSync(targetPath, { recursive: true });
|
|
127
|
+
for (const dir of FACET_DIRS) {
|
|
128
|
+
const srcDir = join(extractedTakt, dir);
|
|
129
|
+
if (existsSync(srcDir)) {
|
|
130
|
+
const destDir = join(targetPath, dir);
|
|
131
|
+
if (existsSync(destDir)) {
|
|
132
|
+
rmSync(destDir, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
cpSync(srcDir, destDir, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// .gitignore
|
|
138
|
+
const gitignoreSrc = join(extractedTakt, ".gitignore");
|
|
139
|
+
if (existsSync(gitignoreSrc)) {
|
|
140
|
+
cpSync(gitignoreSrc, join(targetPath, ".gitignore"));
|
|
141
|
+
}
|
|
142
|
+
info(msg.complete);
|
|
143
|
+
console.log(msg.usageExamples);
|
|
144
|
+
console.log("");
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-takt-sdd",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Installer for takt-sdd: Spec-Driven Development workflow for takt",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-takt-sdd": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc && node scripts/add-shebang.js",
|
|
11
|
+
"prepare": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"takt",
|
|
15
|
+
"sdd",
|
|
16
|
+
"spec-driven-development",
|
|
17
|
+
"ai",
|
|
18
|
+
"workflow"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/j5ik2o/takt-sdd"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"typescript": "^5.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|