create-task-ops 0.1.2 → 0.1.4
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/README.md +45 -14
- package/bin/create-task-ops.js +64 -7
- package/package.json +4 -4
- package/templates/common/docs/DASHBOARD_CONNECTION.md +35 -0
package/README.md
CHANGED
|
@@ -8,36 +8,67 @@
|
|
|
8
8
|
- 기존 리포에 task-ops 구조 주입
|
|
9
9
|
- `tasks/*.md` 와 Task API 계약을 기본으로 포함
|
|
10
10
|
|
|
11
|
-
## 모드
|
|
12
|
-
|
|
13
|
-
- `full`
|
|
14
|
-
Next.js 앱, 기본 대시보드 페이지, Task API, tasks 문서 포함
|
|
15
|
-
- `api`
|
|
16
|
-
Next.js 앱, Task API 중심, 최소 페이지 포함
|
|
17
|
-
- `docs`
|
|
18
|
-
`tasks/`, `CLAUDE.md`, `AGENT.md`, 계약 문서만 포함
|
|
19
|
-
|
|
20
11
|
## 사용 예시
|
|
21
12
|
|
|
22
13
|
```bash
|
|
23
14
|
node packages/create-task-ops/bin/create-task-ops.js my-project
|
|
24
|
-
node packages/create-task-ops/bin/create-task-ops.js my-project
|
|
25
|
-
node packages/create-task-ops/bin/create-task-ops.js
|
|
15
|
+
node packages/create-task-ops/bin/create-task-ops.js create my-project
|
|
16
|
+
node packages/create-task-ops/bin/create-task-ops.js add
|
|
17
|
+
node packages/create-task-ops/bin/create-task-ops.js add --docs-only
|
|
26
18
|
```
|
|
27
19
|
|
|
28
20
|
publish 후에는 아래처럼 쓸 수 있게 설계했다.
|
|
29
21
|
|
|
30
22
|
```bash
|
|
31
23
|
npx create-task-ops my-project
|
|
32
|
-
npx create-task-ops my-project
|
|
33
|
-
npx create-task-ops
|
|
24
|
+
npx create-task-ops create my-project
|
|
25
|
+
npx create-task-ops add
|
|
26
|
+
npx create-task-ops add --docs-only
|
|
34
27
|
```
|
|
35
28
|
|
|
29
|
+
## 명령 의미
|
|
30
|
+
|
|
31
|
+
- `create-task-ops my-project`
|
|
32
|
+
새 Next.js 기반 task-ops 프로젝트 생성
|
|
33
|
+
- `create-task-ops create my-project`
|
|
34
|
+
위와 같지만 명시적으로 create를 적는 형태
|
|
35
|
+
- `create-task-ops add`
|
|
36
|
+
현재 리포에 task docs + Task API 추가
|
|
37
|
+
- `create-task-ops add --docs-only`
|
|
38
|
+
현재 리포에 문서와 task 파일 규약만 추가
|
|
39
|
+
|
|
40
|
+
## 내부 생성 타입
|
|
41
|
+
|
|
42
|
+
- `create`
|
|
43
|
+
내부적으로 `full` 템플릿을 사용한다.
|
|
44
|
+
- `add`
|
|
45
|
+
내부적으로 `api` 수신기 파일만 선택적으로 추가한다.
|
|
46
|
+
- `add --docs-only`
|
|
47
|
+
내부적으로 `docs` 템플릿을 사용한다.
|
|
48
|
+
|
|
49
|
+
## add가 넣는 파일
|
|
50
|
+
|
|
51
|
+
`add` 는 기존 리포를 통째로 덮어쓰지 않는다. 기본적으로 아래만 추가한다.
|
|
52
|
+
|
|
53
|
+
- `tasks/example-task.md`
|
|
54
|
+
- `CLAUDE.md`
|
|
55
|
+
- `AGENT.md`
|
|
56
|
+
- `docs/TASK_API_CONTRACT.md`
|
|
57
|
+
- `docs/DASHBOARD_CONNECTION.md`
|
|
58
|
+
- `app/api/health/route.ts`
|
|
59
|
+
- `app/api/tasks/route.ts`
|
|
60
|
+
- `app/api/tasks/[id]/route.ts`
|
|
61
|
+
- `lib/task-api.ts`
|
|
62
|
+
|
|
63
|
+
즉 기존 `package.json`, `app/page.tsx`, `app/layout.tsx`, `tsconfig.json` 같은 루트 파일은 건드리지 않는다.
|
|
64
|
+
|
|
36
65
|
## 로컬 테스트
|
|
37
66
|
|
|
38
67
|
```bash
|
|
39
68
|
npm run create-task-ops -- my-project
|
|
40
|
-
npm run create-task-ops --
|
|
69
|
+
npm run create-task-ops -- create my-project
|
|
70
|
+
npm run create-task-ops -- add
|
|
71
|
+
npm run create-task-ops -- add --docs-only
|
|
41
72
|
cd packages/create-task-ops
|
|
42
73
|
npm run smoke:full
|
|
43
74
|
```
|
package/bin/create-task-ops.js
CHANGED
|
@@ -8,24 +8,39 @@ import process from "node:process";
|
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
10
|
const templatesRoot = path.join(packageRoot, "templates");
|
|
11
|
+
const addApiFiles = [
|
|
12
|
+
"app/api/health/route.ts",
|
|
13
|
+
"app/api/tasks/route.ts",
|
|
14
|
+
"app/api/tasks/[id]/route.ts",
|
|
15
|
+
"lib/task-api.ts",
|
|
16
|
+
];
|
|
11
17
|
|
|
12
18
|
function parseArgs(argv) {
|
|
13
19
|
const options = {
|
|
20
|
+
command: "create",
|
|
14
21
|
name: null,
|
|
15
22
|
mode: "full",
|
|
16
|
-
|
|
23
|
+
docsOnly: false,
|
|
17
24
|
force: false,
|
|
18
25
|
};
|
|
19
26
|
|
|
20
27
|
for (let index = 0; index < argv.length; index += 1) {
|
|
21
28
|
const arg = argv[index];
|
|
29
|
+
if (arg === "create") {
|
|
30
|
+
options.command = "create";
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (arg === "add") {
|
|
34
|
+
options.command = "add";
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
22
37
|
if (arg === "--mode") {
|
|
23
38
|
options.mode = argv[index + 1] ?? "full";
|
|
24
39
|
index += 1;
|
|
25
40
|
continue;
|
|
26
41
|
}
|
|
27
|
-
if (arg === "--
|
|
28
|
-
options.
|
|
42
|
+
if (arg === "--docs-only") {
|
|
43
|
+
options.docsOnly = true;
|
|
29
44
|
continue;
|
|
30
45
|
}
|
|
31
46
|
if (arg === "--force") {
|
|
@@ -47,6 +62,14 @@ function ensureMode(mode) {
|
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
function printUsage() {
|
|
66
|
+
console.log("Usage:");
|
|
67
|
+
console.log(" create-task-ops <project-name>");
|
|
68
|
+
console.log(" create-task-ops create <project-name>");
|
|
69
|
+
console.log(" create-task-ops add");
|
|
70
|
+
console.log(" create-task-ops add --docs-only");
|
|
71
|
+
}
|
|
72
|
+
|
|
50
73
|
function listFiles(dir, prefix = "") {
|
|
51
74
|
const entries = readdirSync(dir);
|
|
52
75
|
return entries.flatMap((entry) => {
|
|
@@ -84,17 +107,45 @@ function copyTemplateTree(sourceDir, targetDir, projectName, force) {
|
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
109
|
|
|
110
|
+
function copySelectedFiles(sourceDir, targetDir, projectName, force, selectedFiles) {
|
|
111
|
+
mkdirSync(targetDir, { recursive: true });
|
|
112
|
+
|
|
113
|
+
for (const relativeFile of selectedFiles) {
|
|
114
|
+
const sourcePath = path.join(sourceDir, relativeFile);
|
|
115
|
+
const targetPath = path.join(targetDir, relativeFile);
|
|
116
|
+
const targetFolder = path.dirname(targetPath);
|
|
117
|
+
mkdirSync(targetFolder, { recursive: true });
|
|
118
|
+
|
|
119
|
+
if (existsSync(targetPath) && !force) {
|
|
120
|
+
console.error(`Refusing to overwrite existing file: ${targetPath}`);
|
|
121
|
+
console.error("Use --force if you want to replace scaffolded files.");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const content = readFileSync(sourcePath, "utf8");
|
|
126
|
+
writeFileSync(targetPath, renderTemplate(content, projectName), "utf8");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
87
130
|
function main() {
|
|
88
131
|
const options = parseArgs(args);
|
|
132
|
+
if (options.command === "add") {
|
|
133
|
+
options.mode = options.docsOnly ? "docs" : "api";
|
|
134
|
+
}
|
|
89
135
|
ensureMode(options.mode);
|
|
90
136
|
|
|
137
|
+
if (options.command === "create" && !options.name) {
|
|
138
|
+
printUsage();
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
91
142
|
const inferredName = options.name ? path.basename(path.resolve(process.cwd(), options.name)) : path.basename(process.cwd());
|
|
92
143
|
const projectName = inferredName || "task-ops-project";
|
|
93
|
-
const targetDir = options.
|
|
144
|
+
const targetDir = options.command === "add"
|
|
94
145
|
? process.cwd()
|
|
95
146
|
: path.resolve(process.cwd(), options.name ?? projectName);
|
|
96
147
|
|
|
97
|
-
if (
|
|
148
|
+
if (options.command === "create" && existsSync(targetDir) && readdirSync(targetDir).length > 0 && !options.force) {
|
|
98
149
|
console.error(`Target directory is not empty: ${targetDir}`);
|
|
99
150
|
console.error("Use --force if you want to write scaffold files there.");
|
|
100
151
|
process.exit(1);
|
|
@@ -106,14 +157,20 @@ function main() {
|
|
|
106
157
|
const modeDir = path.join(templatesRoot, options.mode);
|
|
107
158
|
|
|
108
159
|
copyTemplateTree(commonDir, targetDir, projectName, options.force);
|
|
109
|
-
|
|
160
|
+
|
|
161
|
+
if (options.command === "add" && options.mode === "api") {
|
|
162
|
+
copySelectedFiles(modeDir, targetDir, projectName, options.force, addApiFiles);
|
|
163
|
+
} else {
|
|
164
|
+
copyTemplateTree(modeDir, targetDir, projectName, options.force);
|
|
165
|
+
}
|
|
110
166
|
|
|
111
167
|
console.log(`create-task-ops completed`);
|
|
112
168
|
console.log(`target: ${targetDir}`);
|
|
169
|
+
console.log(`command: ${options.command}`);
|
|
113
170
|
console.log(`mode: ${options.mode}`);
|
|
114
171
|
console.log(`project: ${projectName}`);
|
|
115
172
|
console.log(`next steps:`);
|
|
116
|
-
console.log(`1. cd ${options.
|
|
173
|
+
console.log(`1. cd ${options.command === "add" ? "." : projectName}`);
|
|
117
174
|
if (options.mode !== "docs") {
|
|
118
175
|
console.log(`2. npm install`);
|
|
119
176
|
console.log(`3. npm run dev`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-task-ops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Next.js-first task-ops scaffold generator for task docs and task APIs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"pack:dry-run": "npm_config_cache=/tmp/npm-cache npm pack --dry-run",
|
|
30
30
|
"smoke:full": "node ./bin/create-task-ops.js /tmp/create-task-ops-full --mode full --force",
|
|
31
|
-
"smoke:
|
|
32
|
-
"smoke:docs": "
|
|
33
|
-
"prepublishOnly": "npm run smoke:docs && npm run pack:dry-run"
|
|
31
|
+
"smoke:add": "PACKAGE_DIR=\"$PWD\"; mkdir -p /tmp/create-task-ops-add && cd /tmp/create-task-ops-add && node \"$PACKAGE_DIR/bin/create-task-ops.js\" add --force",
|
|
32
|
+
"smoke:docs": "PACKAGE_DIR=\"$PWD\"; mkdir -p /tmp/create-task-ops-docs && cd /tmp/create-task-ops-docs && node \"$PACKAGE_DIR/bin/create-task-ops.js\" add --docs-only --force",
|
|
33
|
+
"prepublishOnly": "npm run smoke:add && npm run smoke:docs && npm run pack:dry-run"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Dashboard Connection
|
|
2
|
+
|
|
3
|
+
이 프로젝트를 중앙 task dashboard에 연결하려면 이 리포의 Task API를 외부에서 읽을 수 있어야 한다.
|
|
4
|
+
|
|
5
|
+
## 제공 엔드포인트
|
|
6
|
+
|
|
7
|
+
- `GET /api/health`
|
|
8
|
+
- `GET /api/tasks`
|
|
9
|
+
- `GET /api/tasks/:id`
|
|
10
|
+
|
|
11
|
+
## 로컬 개발 연결 예시
|
|
12
|
+
|
|
13
|
+
프로젝트를 `3000` 포트에서 띄운 경우, 중앙 대시보드의 `task-sources.json` 에 아래처럼 추가한다.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"sources": [
|
|
18
|
+
{
|
|
19
|
+
"id": "__PROJECT_NAME__",
|
|
20
|
+
"label": "__PROJECT_NAME__",
|
|
21
|
+
"type": "remote",
|
|
22
|
+
"baseUrl": "http://127.0.0.1:3000",
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"timeoutMs": 3000
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 체크리스트
|
|
31
|
+
|
|
32
|
+
1. `tasks/*.md` 가 최신 상태인가
|
|
33
|
+
2. `/api/tasks` 응답이 정상인가
|
|
34
|
+
3. source `id` 와 `label` 이 명확한가
|
|
35
|
+
4. 필요하면 API key 또는 reverse proxy 를 설정했는가
|