create-task-ops 0.1.3 → 0.1.5

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  - 새 리포를 빠르게 시작
8
8
  - 기존 리포에 task-ops 구조 주입
9
- - `tasks/*.md` 와 Task API 계약을 기본으로 포함
9
+ - `tasks/*.md` 와 OrbitOps API 계약을 기본으로 포함
10
10
 
11
11
  ## 사용 예시
12
12
 
@@ -33,7 +33,7 @@ npx create-task-ops add --docs-only
33
33
  - `create-task-ops create my-project`
34
34
  위와 같지만 명시적으로 create를 적는 형태
35
35
  - `create-task-ops add`
36
- 현재 리포에 task docs + Task API 추가
36
+ 현재 리포에 task docs + OrbitOps API 추가
37
37
  - `create-task-ops add --docs-only`
38
38
  현재 리포에 문서와 task 파일 규약만 추가
39
39
 
@@ -42,10 +42,37 @@ npx create-task-ops add --docs-only
42
42
  - `create`
43
43
  내부적으로 `full` 템플릿을 사용한다.
44
44
  - `add`
45
- 내부적으로 `api` 템플릿을 사용한다.
45
+ 내부적으로 `api` 수신기 파일만 선택적으로 추가한다.
46
46
  - `add --docs-only`
47
47
  내부적으로 `docs` 템플릿을 사용한다.
48
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/orbitops/health/route.ts`
59
+ - `app/api/orbitops/tasks/route.ts`
60
+ - `app/api/orbitops/tasks/[id]/route.ts`
61
+ - `lib/task-api.ts`
62
+
63
+ 즉 기존 `package.json`, `app/page.tsx`, `app/layout.tsx`, `tsconfig.json` 같은 루트 파일은 건드리지 않는다.
64
+
65
+ 기본 동작에서 기존 `CLAUDE.md`, `AGENT.md`, `tasks/*`, `docs/*` 같은 운영 문서는 스킵한다.
66
+
67
+ 대신 아래 receiver 경로가 이미 있으면 충돌 가능성이 크므로 경고 후 중단한다.
68
+
69
+ - `app/api/orbitops/health/route.ts`
70
+ - `app/api/orbitops/tasks/route.ts`
71
+ - `app/api/orbitops/tasks/[id]/route.ts`
72
+ - `lib/task-api.ts`
73
+
74
+ 이 receiver 파일까지 강제로 교체하려면 `--force` 를 쓴다.
75
+
49
76
  ## 로컬 테스트
50
77
 
51
78
  ```bash
@@ -8,6 +8,12 @@ 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/orbitops/health/route.ts",
13
+ "app/api/orbitops/tasks/route.ts",
14
+ "app/api/orbitops/tasks/[id]/route.ts",
15
+ "lib/task-api.ts",
16
+ ];
11
17
 
12
18
  function parseArgs(argv) {
13
19
  const options = {
@@ -80,7 +86,7 @@ function renderTemplate(content, projectName) {
80
86
  return content.replaceAll("__PROJECT_NAME__", projectName);
81
87
  }
82
88
 
83
- function copyTemplateTree(sourceDir, targetDir, projectName, force) {
89
+ function copyTemplateTree(sourceDir, targetDir, projectName, force, skipExisting = false) {
84
90
  mkdirSync(targetDir, { recursive: true });
85
91
  const files = listFiles(sourceDir);
86
92
 
@@ -90,6 +96,11 @@ function copyTemplateTree(sourceDir, targetDir, projectName, force) {
90
96
  const targetFolder = path.dirname(targetPath);
91
97
  mkdirSync(targetFolder, { recursive: true });
92
98
 
99
+ if (existsSync(targetPath) && skipExisting && !force) {
100
+ console.log(`Skipping existing file: ${targetPath}`);
101
+ continue;
102
+ }
103
+
93
104
  if (existsSync(targetPath) && !force) {
94
105
  console.error(`Refusing to overwrite existing file: ${targetPath}`);
95
106
  console.error("Use --force if you want to replace scaffolded files.");
@@ -101,6 +112,32 @@ function copyTemplateTree(sourceDir, targetDir, projectName, force) {
101
112
  }
102
113
  }
103
114
 
115
+ function copySelectedFiles(sourceDir, targetDir, projectName, force, selectedFiles, skipExisting = false) {
116
+ mkdirSync(targetDir, { recursive: true });
117
+
118
+ for (const relativeFile of selectedFiles) {
119
+ const sourcePath = path.join(sourceDir, relativeFile);
120
+ const targetPath = path.join(targetDir, relativeFile);
121
+ const targetFolder = path.dirname(targetPath);
122
+ mkdirSync(targetFolder, { recursive: true });
123
+
124
+ if (existsSync(targetPath) && skipExisting && !force) {
125
+ console.log(`Skipping existing file: ${targetPath}`);
126
+ continue;
127
+ }
128
+
129
+ if (existsSync(targetPath) && !force) {
130
+ console.error(`Refusing to overwrite existing receiver file: ${targetPath}`);
131
+ console.error("This path is reserved for task API integration.");
132
+ console.error("Use --force if you want to replace the existing receiver files.");
133
+ process.exit(1);
134
+ }
135
+
136
+ const content = readFileSync(sourcePath, "utf8");
137
+ writeFileSync(targetPath, renderTemplate(content, projectName), "utf8");
138
+ }
139
+ }
140
+
104
141
  function main() {
105
142
  const options = parseArgs(args);
106
143
  if (options.command === "add") {
@@ -130,8 +167,15 @@ function main() {
130
167
  const commonDir = path.join(templatesRoot, "common");
131
168
  const modeDir = path.join(templatesRoot, options.mode);
132
169
 
133
- copyTemplateTree(commonDir, targetDir, projectName, options.force);
134
- copyTemplateTree(modeDir, targetDir, projectName, options.force);
170
+ const skipExistingDocs = options.command === "add";
171
+
172
+ copyTemplateTree(commonDir, targetDir, projectName, options.force, skipExistingDocs);
173
+
174
+ if (options.command === "add" && options.mode === "api") {
175
+ copySelectedFiles(modeDir, targetDir, projectName, options.force, addApiFiles, false);
176
+ } else {
177
+ copyTemplateTree(modeDir, targetDir, projectName, options.force, skipExistingDocs);
178
+ }
135
179
 
136
180
  console.log(`create-task-ops completed`);
137
181
  console.log(`target: ${targetDir}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-task-ops",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Next.js-first task-ops scaffold generator for task docs and task APIs",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -6,7 +6,7 @@ export const dynamic = "force-dynamic";
6
6
  export async function GET() {
7
7
  return NextResponse.json({
8
8
  ok: true,
9
- service: "__PROJECT_NAME__-task-api",
9
+ service: "__PROJECT_NAME__-orbitops-api",
10
10
  updatedAt: new Date().toISOString(),
11
11
  });
12
12
  }
@@ -1,8 +1,8 @@
1
1
  export default function Home() {
2
2
  return (
3
3
  <main style={{ padding: 32, fontFamily: "sans-serif" }}>
4
- <h1>__PROJECT_NAME__ Task API</h1>
5
- <p>`/api/health`, `/api/tasks`, `/api/tasks/:id` 를 통해 중앙 대시보드와 연동한다.</p>
4
+ <h1>__PROJECT_NAME__ OrbitOps API</h1>
5
+ <p>`/api/orbitops/health`, `/api/orbitops/tasks`, `/api/orbitops/tasks/:id` 를 통해 중앙 대시보드와 연동한다.</p>
6
6
  </main>
7
7
  );
8
8
  }
@@ -1,12 +1,12 @@
1
1
  # Dashboard Connection
2
2
 
3
- 이 프로젝트를 중앙 task dashboard에 연결하려면 이 리포의 Task API를 외부에서 읽을 수 있어야 한다.
3
+ 이 프로젝트를 중앙 task dashboard에 연결하려면 이 리포의 OrbitOps API를 외부에서 읽을 수 있어야 한다.
4
4
 
5
5
  ## 제공 엔드포인트
6
6
 
7
- - `GET /api/health`
8
- - `GET /api/tasks`
9
- - `GET /api/tasks/:id`
7
+ - `GET /api/orbitops/health`
8
+ - `GET /api/orbitops/tasks`
9
+ - `GET /api/orbitops/tasks/:id`
10
10
 
11
11
  ## 로컬 개발 연결 예시
12
12
 
@@ -30,6 +30,6 @@
30
30
  ## 체크리스트
31
31
 
32
32
  1. `tasks/*.md` 가 최신 상태인가
33
- 2. `/api/tasks` 응답이 정상인가
33
+ 2. `/api/orbitops/tasks` 응답이 정상인가
34
34
  3. source `id` 와 `label` 이 명확한가
35
35
  4. 필요하면 API key 또는 reverse proxy 를 설정했는가
@@ -1,12 +1,12 @@
1
- # Task API Contract
1
+ # OrbitOps API Contract
2
2
 
3
3
  중앙 대시보드가 외부 리포를 읽기 위해 기대하는 최소 계약이다.
4
4
 
5
5
  ## Endpoints
6
6
 
7
- - `GET /api/health`
8
- - `GET /api/tasks`
9
- - `GET /api/tasks/:id`
7
+ - `GET /api/orbitops/health`
8
+ - `GET /api/orbitops/tasks`
9
+ - `GET /api/orbitops/tasks/:id`
10
10
 
11
11
  ## Task Fields
12
12
 
@@ -13,4 +13,4 @@ Task-ops 문서 모드로 생성된 프로젝트다.
13
13
 
14
14
  1. `tasks/example-task.md` 를 실제 작업으로 교체
15
15
  2. 리포 규약에 맞게 `CLAUDE.md`, `AGENT.md` 조정
16
- 3. 필요해지면 Next.js Task API를 추가
16
+ 3. 필요해지면 Next.js OrbitOps API를 추가
@@ -6,7 +6,7 @@ export const dynamic = "force-dynamic";
6
6
  export async function GET() {
7
7
  return NextResponse.json({
8
8
  ok: true,
9
- service: "__PROJECT_NAME__-task-api",
9
+ service: "__PROJECT_NAME__-orbitops-api",
10
10
  updatedAt: new Date().toISOString(),
11
11
  });
12
12
  }
@@ -7,9 +7,9 @@ export default async function Home() {
7
7
  <main className="page-shell">
8
8
  <section className="hero">
9
9
  <div className="hero-card">
10
- <span className="eyebrow">Task Ops</span>
10
+ <span className="eyebrow">OrbitOps</span>
11
11
  <h1>__PROJECT_NAME__</h1>
12
- <p>이 프로젝트는 task-first 운영 구조와 Next.js 기반 Task API를 기본으로 시작한다.</p>
12
+ <p>이 프로젝트는 task-first 운영 구조와 Next.js 기반 OrbitOps API를 기본으로 시작한다.</p>
13
13
  </div>
14
14
  </section>
15
15
  <section className="task-grid">