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 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 --mode api
25
- node packages/create-task-ops/bin/create-task-ops.js --init --mode docs
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 --mode api
33
- npx create-task-ops --init --mode docs
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 -- --init --mode docs
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
  ```
@@ -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
- init: false,
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 === "--init") {
28
- options.init = true;
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.init
144
+ const targetDir = options.command === "add"
94
145
  ? process.cwd()
95
146
  : path.resolve(process.cwd(), options.name ?? projectName);
96
147
 
97
- if (!options.init && existsSync(targetDir) && readdirSync(targetDir).length > 0 && !options.force) {
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
- copyTemplateTree(modeDir, targetDir, projectName, options.force);
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.init ? "." : projectName}`);
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.2",
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:api": "node ./bin/create-task-ops.js /tmp/create-task-ops-api --mode api --force",
32
- "smoke:docs": "node ./bin/create-task-ops.js /tmp/create-task-ops-docs --mode docs --force",
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 를 설정했는가