TypeSpecでscaffdogを利用する

# TypeSpecでscaffdogを利用する

## 背景

[TypeSpec](https://typespec.io/)を利用する際に、SchemaとModelを分離したい気持ちが生じており、複数ファイルのテンプレートをVSCなどのテンプレートで利用せずに[scaffdog](https://scaff.dog/)を利用してファイルの生成速度をあげるための対応。

## 0. 想定しているdirectory構成

```terminal
tree -L . -I node_modules
.
├── dockerfile
├── package-lock.json
├── package.json
├── scripts
│   └── sync
│       └── main.ts
├── src
│   ├── main.tsp       // monosilic dist用のファイル
│   ├── tspconfig.yaml // dist用config file
│   ├── blog           // model package
│   │   ├── model.tsp  // Model　
│   │   └── rotue.tsp  // Open API Schema
│   ├── rest-util
│   │   └── page.tsp
│   └── util
│       └── uuid.tsp
├── tsconfig.json
└── tsp-output
    └── @typespec
        └── openapi3
```

構成としては、src/{model_name}/model.tspにModelの内容を記載。
src/{model_name}/route.tspにOpenAPIのSchemaを記載

```ex)src/blog/model.tsp
model Blog {
  @visibility("read")
  @key
  id: uint64;

  @visibility("create")
  title: blogTitle;
}

@minLength(1)
@maxLength(255)
scalar blogTitle extends string;

@withVisibility("create")
model CreateBlogContent {
  ...Blog;
}

op createBlogCotnent(dto: CreateBlogContent): Blog;

```

```ex)src/blog/route.tsp
import "@typespec/http";
import "./model.tsp";
import "../rest-util/page.tsp";

using TypeSpec.Http;

@tag("blog")
@route("/blogs")
interface Blogs {
  create is createBlogCotnent;
  list(): Page<Blog>;
}

```

[この記事](https://zenn.dev/gama/articles/9994ec83783c44)で、ある程度の内容を書いてます。


## 1. scaffdogのinstallとinitialise

### 1-1. scaff dogのinstall

```terminal
% npm install --save-dev scaffdog
```

### 1-2. scaff dogのinit

```terminal
% npx scaffdog init

? Please enter a document name. scaff

Setup of scaffdog 🐶 is complete!

  ✔ .scaffdog/config.js
  ✔ .scaffdog/scaff.md

Now you can do scaffold by running `$ scaffdog generate`.

Please refer to the following documents and customize it.
https://scaff.dog/docs/templates
```

上記を実施すると、.scaffdogのdirectoryが作成され、2つのファイル(config.jsとscaff.md)が生成されていることが確認できます。


```terminal
tree .scaffdog
.scaffdog
├── config.js
└── scaff.md

1 directory, 2 files
```

## 2. tspファイル用に生成ファイルをいじる

scaffdogでは、pascal caseやsnake caseなどのhelperなども準備されているため、
scaholdingのdocumentを生成します。
markdownの開始と終了については、qiitaの記事では省いておりますが、つけてください。

```.scaffdog/scaff.md
---
name: 'scaff'
root: './src'
output: '**/*'
ignore: []
questions:
  name: 'Please enter name of model'
---


# Variables

- featurename: `{{ inputs.name | kebab }}`
- modelname: `{{ inputs.name | pascal }}`

# `{{ featurename }}/model.tsp`

markdown
/**
 * Generated by scaffdog
 * feature name is {{ featurename }}
 * created at {{ date "YYYY/MM/DD" }}
 **/

model {{ modelname }} {
}

# `{{ featurename }}/route.tsp`

/**
 * Generated by scaffdog
 * feature name is {{ featurename }}
 * created at {{ date "YYYY/MM/DD" }}
 **/
import "@typespec/http";
import "./model.tsp";

using TypeSpec.Http;

@tag("{{ inputs.name }}")
@route("/{{ inputs.name | kebab | plur }}")
interface {{ modelname | plur }} {
}
```

variablesに変数を置いています。

`#` {{ inputs.name | kebab | define 'filename' }}のようにした場合、defineしたファイルが正常にdistされないので注意してください。
(多分、内部の噛み合わせなのですが、中身まで見てない)

## 3. ファイルを生成してみる

```terminal
% npx scaffdog generate
? Please select a document. scaff
ℹ Output destination directory: "./src"
? Please enter name of model user

🐶 Generated 2 files!

     ✔ src/user/model.tsp
     ✔ src/user/route.tsp
```

```src/user/model.tsp
/**
 * Generated by scaffdog
 * feature name is user
 * created at 2024/10/28
 **/

model User {
}
```

```src/user/route.tsp
/**
 * Generated by scaffdog
 * feature name is user
 * created at 2024/10/28
 **/
import "@typespec/http";
import "./model.tsp";

using TypeSpec.Http;

@tag("user")
@route("/users")
interface Users {
}
```

## 4. Templateを拡張して、distファイルにも追加する

### 4-0. 現状のmain.tsp

現状のsrc/main.tspについては、以下となっています。

```src/main.tsp
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi3";
import "./blog/route.tsp";
```

手動で、`import "./user/route.tsp"`を追加してもいいのですが、scaffdogを利用してもいいのですが、変更していきます。


### 4-1. main.tspのtemplateを作成

```.scaffdog/.scaff.md
---
name: 'scaff'
root: './src'
output: 'src/*' // <- 変更
ignore: []
questions:
  name: 'Please enter name of model'
---
## 省略

# `main.tsp`

|```
{{ read output.abs }}
import "./{{ featurename }}/route.tsp";
|```
```

read output.absを利用してファイルの内部を読み込み最終行に追加していきます。

### 4-2. 生成

scaff dogはoverrideをする場合には、terminalで確認が可能となります。

```terminal
% npx scaffdog generate
? Please select a document. scaff
ℹ Output destination directory: "./src"
? Please enter name of model user
⚠ Would you like to overwrite it?
("src/user/model.tsp") No
⚠ Would you like to overwrite it?
("src/user/route.tsp") No
⚠ Would you like to overwrite it? ("src/main.tsp") Yes


🐶 Generated 1 file! (2 skipped)

     ✔ src/main.tsp
     ⚠ src/user/model.tsp (skipped)
     ⚠ src/user/route.tsp (skipped)
```

```src/main.tsp
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi3";
import "./blog/route.tsp";
import "./user/route.tsp";
```