2025년 04월 21일
지금은 새로운 게시글을 작성하려면
을 거쳐야한다. 매번 반복해야 하는 파일 생성이나 프론트매터 작성을 자동화 하면 좋을것 같다는 생각이 들었다.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function ask(question: string): Promise<string> {
return new Promise((resolve) => {
rl.question(question, resolve);
});
}
const category = await ask('카테고리 입력 : ');
const fileName = await ask('파일명 입력: ');
const title = await ask('제목 입력: ');
const slug = fileName.toLowerCase().replace(/\s+/g, '-');
const date = new Date().toISOString().split('T')[0];
const frontmatter = `---
title: '${title}'
date: '${date}'
desc: ''
---
여기에 본문을 작성하세요.
`;
const postDir = path.join('src/posts', category);
const filePath = path.join(postDir, `${slug}.mdx`);
fs.mkdirSync(postDir, { recursive: true });
fs.writeFileSync(filePath, frontmatter);
await execa('code', ['--reuse-window', filePath]);
import { execa } from 'execa';
import fs from 'fs';
import path from 'path';
import readline from 'readline';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function ask(question: string): Promise<string> {
return new Promise((resolve) => {
rl.question(question, resolve);
});
}
(async () => {
const category = await ask('카테고리 입력 : ');
const fileName = await ask('파일명 입력: ');
const title = await ask('제목 입력: ');
const slug = fileName.toLowerCase().replace(/\s+/g, '-');
const date = new Date().toISOString().split('T')[0];
const frontmatter = `---
title: '${title}'
date: '${date}'
desc: ''
---
여기에 본문을 작성하세요.
`;
const postDir = path.join('src/posts', category);
const filePath = path.join(postDir, `${slug}.mdx`);
fs.mkdirSync(postDir, { recursive: true });
fs.writeFileSync(filePath, frontmatter);
console.log(`🌟 포스트 생성 완료: ${filePath}`);
await execa('code', ['--reuse-window', filePath]);
rl.close();
})();
이제 만든 스크립트를 짧은 명령어로 실행할 수 있도록 해보자.
package.json 파일의 scripts에 다음과 같이 등록하면 터미널에서 yarn new-post 명령어로 바로 게시물을 생성할 수 있다.
"scripts": {
"new-post": "ts-node scripts/newPost.ts"
}
이제 실행해보자. 터미널에 yarn new-post를 실행했더니 아래와 같은 오류가 발생했다.
$ ts-node scripts/newPost.ts
(node:1994) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use node --trace-warnings ... to show where the warning was created)
/Users/~/scripts/newPost.ts:1
import fs from 'fs';
^^^^^^
SyntaxError: Cannot use import statement outside a module
문제
현재 newPost.ts파일 내에서 import 구문를 사용하고 있어 Node.js가 이 파일을 ESM으로 인식한다. 하지만 package.json에 "type" : "module"로 지정되어 있다거나 .mjs같은 확장자를 쓰지 않아 CommonJS로 해석된다.
그 결과, import 구문을 지원하지 않아 SyntaxError가 발생한 것이다.
해결
package.json에 "type": "module"을 추가해 ESM이라는 것을 명시해준다.
"type": "module",
"scripts": {
"new-post": "ts-node scripts/new-post.ts"
}
그랬더니 다음과 같은 오류가 다시 발생했다.
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/~/scripts/newPost.ts
문제
Node.js는 .ts 파일을 기본적으로 해석할 수 없으며, ESM 환경에서는 ts-node를 사용하더라도 .ts 확장자를 직접 실행하려면 loader를 명시해야 한다.
해결
--loader ts-node/esm: TypeScript ESM 환경에서 ts 파일을 실행할 수 있도록 ts-node의 로더를 명시해주었다.
"scripts": {
"new-post": "node --loader ts-node/esm scripts/newPost.ts"
}
이제 잘 실행되지만, 아래와 같은 경고문구가 뜬다.
Node.js에서 --loader 플래그가 실험적인 기능으로 간주되며, 향후 제거될 수 있다는 내용이다.
따라서, --loader 대신 register() 함수를 사용하는 방식으로 전환하는 것이 권장된다.
"scripts": {
"new-post": "node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));"
}
깔끔한 코드를 위해 별도 파일로 분리해주었다.
import { register } from 'node:module';
import { pathToFileURL } from 'node:url';
register('ts-node/esm', pathToFileURL('./'));
"scripts": {
"new-post": "node --import ./register.js scripts/newPost.ts"
}