feat: 브리프 인터뷰 단계와 브라우저 도구를 넣고 온보딩 말투를 고친다
세 가지를 고친다. 전부 실제 사용에서 드러난 것이다.
1) 0단계에서 가정으로 채우던 것을 인터뷰로 바꾼다
"꽃집 사이트 만들어보자"를 받고 업종 성격·목표 행동·톤·이름을 혼자 정했다.
물어보니 넷 중 넷이 달랐다(일상 구독 → 하이엔드 스튜디오, 문의 하나 → 넷 다,
톤 미정 → 에디토리얼, 이름 지어냄 → 목요일의 화원).
그대로 갔으면 레퍼런스 세 개를 전부 틀린 방향에서 골랐다.
SKILL.md 0단계에 질문 도구로 한 번에 묻는 절차를 넣었다. 무엇을 묻고
무엇을 묻지 않는지, 답이 모순될 때 어떻게 정리하는지까지 적었다.
2) 레퍼런스가 막히면 브라우저를 띄운다
Aesop(403) → Kinto(404) → Hasami(DNS) → MUJI(타임아웃)로 네 번 왕복하고
톤 레퍼런스를 하나도 못 얻었다. 좋은 레퍼런스일수록 봇을 막는다.
galleries.md 에 headed 브라우저로 직접 열어 스크린샷과 실측값을 받는
방법을 넣고, 접근 실패 2회면 바로 전환하도록 규칙을 세웠다.
Playwright 를 선택 의존성으로 잡았다(core/tools.ts).
- 온보딩 마지막에 설치 여부를 묻는다. 건너뛰어도 스킬은 동작한다
- `designpaca tools` 로 상태 확인, `--yes` 로 설치
- 수백 MB 라 --yes 없이는 상태만 보여준다
- MCP 서버는 설치만 하고 등록 명령은 안내만 한다(에이전트 설정을
대신 건드리지 않는다)
3) 온보딩 말투
"이제 브리프를 던져라", "설치해라", "건너뛴다" — 사용자를 향한 문구가
명령조였다. 스킬 문서의 단정한 반말은 의도지만 CLI UI 는 다르다.
전부 존댓말로 바꾸고, 각 단계가 왜 필요한지 설명을 붙였다.
명령조가 다시 섞이지 않도록 검사하는 테스트를 넣었다(테스트 22 → 25개).
This commit is contained in:
parent
eb9f61e907
commit
4e1e5f0073
15 changed files with 486 additions and 39 deletions
|
|
@ -1,16 +1,35 @@
|
|||
{
|
||||
"name": "designpaca",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다",
|
||||
"keywords": ["design", "web-design", "claude-code", "codex", "cursor", "agent-skill", "svg-filter", "threejs"],
|
||||
"keywords": [
|
||||
"design",
|
||||
"web-design",
|
||||
"claude-code",
|
||||
"codex",
|
||||
"cursor",
|
||||
"agent-skill",
|
||||
"svg-filter",
|
||||
"threejs"
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": "Yun Chan",
|
||||
"homepage": "https://designpaca.chanpaca.net",
|
||||
"repository": { "type": "git", "url": "git+https://git.chanpaca.net/yunchan/designpaca.git" },
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.chanpaca.net/yunchan/designpaca.git"
|
||||
},
|
||||
"type": "module",
|
||||
"bin": { "designpaca": "./dist/index.js" },
|
||||
"files": ["dist", "README.md"],
|
||||
"engines": { "node": ">=20.11" },
|
||||
"bin": {
|
||||
"designpaca": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/bundle-skill.mjs && tsup",
|
||||
"dev": "node scripts/bundle-skill.mjs && tsup --watch",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export async function runDoctor(): Promise<number> {
|
|||
if (pruned > 0) console.log(info(`사라진 설치 기록 ${pruned}건을 정리했다`));
|
||||
|
||||
if (manifest.installs.length === 0) {
|
||||
console.log(warn("설치된 곳이 없다. `npx designpaca install` 로 설치해라."));
|
||||
console.log(warn("아직 설치된 곳이 없습니다. `npx designpaca install` 로 설치하실 수 있습니다."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ export async function runDoctor(): Promise<number> {
|
|||
if (rec.version !== version) problems++;
|
||||
if (missing.length > 0) {
|
||||
problems++;
|
||||
console.log(bad(`파일 ${missing.length}개가 사라졌다 — \`designpaca update --force\` 로 복구해라`));
|
||||
console.log(bad(`파일 ${missing.length}개가 보이지 않습니다 — \`designpaca update --force\` 로 복구하실 수 있습니다`));
|
||||
for (const m of missing.slice(0, 5)) console.log(dim(` ${tildify(m.path)}`));
|
||||
}
|
||||
if (modified.length > 0) {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export function printOutcomes(outcomes: InstallOutcome[], dryRun = false): void
|
|||
const verb = dryRun ? "쓸 파일" : "설치됨";
|
||||
console.log(ok(`${o.target} — ${verb} ${o.written}개 ${dim(tildify(o.root))}`));
|
||||
if (o.skipped.length > 0) {
|
||||
console.log(warn(` 직접 수정한 파일이라 건드리지 않았다 (--force 로 덮어쓴다):`));
|
||||
console.log(warn(` 직접 수정하신 파일이라 그대로 두었습니다 (--force 로 덮어쓸 수 있습니다):`));
|
||||
for (const s of fold(o.skipped)) console.log(dim(` ${tildify(s)}`));
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ export function printOutcomes(outcomes: InstallOutcome[], dryRun = false): void
|
|||
export function printNextSteps(targets: TargetId[]): void {
|
||||
console.log(heading("다음 단계"));
|
||||
if (targets.includes("claude-code")) {
|
||||
console.log(info(`Claude Code 를 새로 열고 ${dim("/designpaca")} 를 실행해라`));
|
||||
console.log(info(`Claude Code 를 새로 연 뒤 ${dim("/designpaca")} 를 실행해 주세요`));
|
||||
}
|
||||
if (targets.includes("codex")) {
|
||||
console.log(info(`Codex CLI 에서 designpaca 스킬이 자동으로 잡힌다`));
|
||||
|
|
|
|||
67
packages/cli/src/commands/tools.ts
Normal file
67
packages/cli/src/commands/tools.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { checkTools } from "@designpaca/core";
|
||||
import { bad, dim, heading, info, ok, table, warn } from "../ui.ts";
|
||||
|
||||
/**
|
||||
* 선택 도구의 상태를 보여주고, 없는 것을 설치한다.
|
||||
*
|
||||
* 온보딩에서 건너뛴 사람과, 나중에 필요해진 사람 모두를 위한 자리다.
|
||||
* `--yes` 없이 비대화형으로 부르면 **상태만 보여주고 아무것도 설치하지 않는다** —
|
||||
* 도구 설치는 수백 MB 를 받는 일이라 조용히 시작하면 안 된다.
|
||||
*/
|
||||
export async function runTools(opts: { install: boolean }): Promise<number> {
|
||||
const status = await checkTools();
|
||||
|
||||
console.log(heading("브라우저 도구"));
|
||||
console.log(
|
||||
table(
|
||||
status.map(({ tool, installed }) => [
|
||||
tool.label,
|
||||
installed ? "설치됨" : dim("설치되지 않음"),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
const missing = status.filter((s) => !s.installed);
|
||||
|
||||
if (missing.length === 0) {
|
||||
console.log(ok("필요한 도구가 모두 준비돼 있습니다."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.log();
|
||||
for (const { tool } of missing) {
|
||||
console.log(`${tool.label} ${dim(`· ${tool.size}`)}`);
|
||||
console.log(` ${dim(tool.why)}`);
|
||||
}
|
||||
|
||||
if (!opts.install) {
|
||||
console.log();
|
||||
console.log(info("설치하시려면 `designpaca tools --yes` 를 실행해 주세요."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.log();
|
||||
let failed = 0;
|
||||
const followUps: string[] = [];
|
||||
for (const { tool } of missing) {
|
||||
process.stdout.write(`${tool.label} 설치 중… `);
|
||||
try {
|
||||
await tool.install();
|
||||
console.log("완료");
|
||||
if (tool.followUp) followUps.push(tool.followUp);
|
||||
} catch (err) {
|
||||
failed++;
|
||||
console.log("실패");
|
||||
const first = err instanceof Error ? (err.message.split("\n")[0] ?? err.message) : String(err);
|
||||
console.log(` ${bad(first)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (followUps.length > 0) {
|
||||
console.log();
|
||||
console.log(warn("아래는 에이전트 설정을 바꾸는 것이라 직접 실행해 주세요."));
|
||||
for (const f of followUps) console.log(` ${f}`);
|
||||
}
|
||||
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export async function runUninstall(opts: {
|
|||
);
|
||||
|
||||
if (targets.length === 0) {
|
||||
console.log(warn("제거할 설치 기록이 없다"));
|
||||
console.log(warn("제거할 설치 기록이 없습니다"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export async function runUpdate(opts: { force?: boolean } = {}): Promise<number>
|
|||
const manifest = await readManifest();
|
||||
|
||||
if (manifest.installs.length === 0) {
|
||||
console.log(warn("설치 기록이 없다. `npx designpaca install` 을 먼저 실행해라."));
|
||||
console.log(warn("설치 기록이 없습니다. `npx designpaca install` 을 먼저 실행해 주세요."));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ export async function runUpdate(opts: { force?: boolean } = {}): Promise<number>
|
|||
const stale = manifest.installs.filter((i) => i.version !== version);
|
||||
|
||||
if (stale.length === 0) {
|
||||
console.log(ok("모든 설치가 이미 최신이다"));
|
||||
console.log(ok("설치된 스킬이 모두 최신 버전입니다"));
|
||||
|
||||
// 버전이 같아도 파일이 사라졌거나 수정됐을 수 있다. 조용히 넘기면 사용자가 모른다.
|
||||
let broken = 0;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { getVersion } from "./skill.ts";
|
|||
import { checkForUpdate } from "./update-check.ts";
|
||||
import { printNextSteps, printOutcomes, runInstall } from "./commands/install.ts";
|
||||
import { runDoctor } from "./commands/doctor.ts";
|
||||
import { runTools } from "./commands/tools.ts";
|
||||
import { runUpdate } from "./commands/update.ts";
|
||||
import { runUninstall } from "./commands/uninstall.ts";
|
||||
import { runList } from "./commands/list.ts";
|
||||
|
|
@ -70,14 +71,15 @@ ${bold("사용법")}
|
|||
npx designpaca uninstall 제거
|
||||
npx designpaca doctor 설치 상태 진단
|
||||
npx designpaca list 설치 가능 대상과 현재 설치 목록
|
||||
npx designpaca tools 브라우저 도구 상태 확인 ${dim("(--yes 로 설치)")}
|
||||
|
||||
${bold("옵션")}
|
||||
-t, --target <id[,id]> 설치 대상: ${VALID_TARGETS.join(", ")}
|
||||
-s, --scope <범위> user | project ${dim("(기본: user)")}
|
||||
-y, --yes 확인 없이 진행
|
||||
-f, --force 직접 수정한 파일도 덮어쓴다 ${dim("(.orig 로 백업)")}
|
||||
--dry-run 쓰지 않고 계획만 출력
|
||||
--no-update-check 새 버전 확인을 건너뛴다
|
||||
-y, --yes 확인 없이 진행합니다
|
||||
-f, --force 직접 수정한 파일도 덮어씁니다 ${dim("(.orig 로 백업)")}
|
||||
--dry-run 쓰지 않고 계획만 보여줍니다
|
||||
--no-update-check 새 버전 확인을 건너뜁니다
|
||||
-h, --help 이 도움말
|
||||
-v, --version 버전
|
||||
|
||||
|
|
@ -85,6 +87,7 @@ ${bold("예시")}
|
|||
${dim("npx designpaca install -t claude-code,codex -s user -y")}
|
||||
${dim("npx designpaca install -t cursor -s project")}
|
||||
${dim("npx designpaca update --force")}
|
||||
${dim("npx designpaca tools --yes")}
|
||||
`.trimStart();
|
||||
}
|
||||
|
||||
|
|
@ -146,12 +149,16 @@ async function main(): Promise<number> {
|
|||
case "doctor":
|
||||
code = await runDoctor();
|
||||
break;
|
||||
case "tools":
|
||||
// 수백 MB 를 받는 일이라 --yes 없이는 상태만 보여준다
|
||||
code = await runTools({ install: args.yes });
|
||||
break;
|
||||
case "list":
|
||||
case "ls":
|
||||
code = await runList();
|
||||
break;
|
||||
default:
|
||||
console.error(bad(`알 수 없는 명령: ${args.command}`));
|
||||
console.error(bad(`알 수 없는 명령입니다: ${args.command}`));
|
||||
console.log(usage(version));
|
||||
return 2;
|
||||
}
|
||||
|
|
@ -160,7 +167,7 @@ async function main(): Promise<number> {
|
|||
const latest = await checkForUpdate(version);
|
||||
if (latest) {
|
||||
console.log(
|
||||
"\n" + warn(`새 버전 ${bold(`v${latest}`)} 이 있다 — ${dim("npx designpaca@latest update")}`),
|
||||
"\n" + warn(`새 버전 v${latest} 이 나왔습니다 — ${dim("npx designpaca@latest update")}`),
|
||||
);
|
||||
}
|
||||
return code;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import * as p from "@clack/prompts";
|
|||
import path from "node:path";
|
||||
import {
|
||||
ADAPTERS,
|
||||
checkTools,
|
||||
detectTargets,
|
||||
getAdapter,
|
||||
planInstall,
|
||||
|
|
@ -27,19 +28,21 @@ export async function onboard(): Promise<number> {
|
|||
|
||||
p.note(
|
||||
[
|
||||
"브리프에서 시작해 레퍼런스 조사 · 방향 결정 · 디자인 토큰 ·",
|
||||
"구현 · 셀프 감사까지 끌고 가는 웹 디자인 파이프라인 스킬.",
|
||||
"웹 디자인을 순서대로 진행하게 만드는 스킬입니다.",
|
||||
"브리프 정리 → 레퍼런스 조사 → 방향 결정 → 디자인 토큰 →",
|
||||
"구현 → 셀프 감사까지, 단계마다 통과 조건을 두고 진행합니다.",
|
||||
"",
|
||||
`${dim("SVG 필터 · three.js · 인터랙티브 모션을 기본 재료로 쓴다.")}`,
|
||||
dim("설치하면 에이전트에서 /designpaca 로 부를 수 있습니다."),
|
||||
dim("스킬 파일만 복사하며, 기존 설정은 건드리지 않습니다."),
|
||||
].join("\n"),
|
||||
"무엇을 설치하나",
|
||||
"designpaca 는 무엇인가요",
|
||||
);
|
||||
|
||||
// 시스템에 흔적이 있는 도구를 기본 체크해 둔다 — 사용자가 매번 고르게 하지 않는다
|
||||
const detected = await detectTargets(skill);
|
||||
|
||||
const targets = await p.multiselect<TargetId>({
|
||||
message: "어디에 설치할까?",
|
||||
message: "어디에 설치할까요?",
|
||||
options: ADAPTERS.map((a) => ({
|
||||
value: a.id,
|
||||
label: a.label + (detected.includes(a.id) ? dim(" (감지됨)") : ""),
|
||||
|
|
@ -51,10 +54,18 @@ export async function onboard(): Promise<number> {
|
|||
if (p.isCancel(targets)) return cancel();
|
||||
|
||||
const wanted = await p.select<Scope>({
|
||||
message: "설치 범위",
|
||||
message: "어느 범위에 설치할까요?",
|
||||
options: [
|
||||
{ value: "user", label: "전역", hint: "홈 디렉터리 — 모든 프로젝트에서 쓴다" },
|
||||
{ value: "project", label: "이 프로젝트만", hint: tildify(process.cwd()) },
|
||||
{
|
||||
value: "user",
|
||||
label: "전역 (권장)",
|
||||
hint: "홈 디렉터리에 두어 모든 프로젝트에서 쓸 수 있습니다",
|
||||
},
|
||||
{
|
||||
value: "project",
|
||||
label: "이 프로젝트만",
|
||||
hint: `${tildify(process.cwd())} · 팀과 함께 쓰려면 이쪽이 좋습니다`,
|
||||
},
|
||||
],
|
||||
initialValue: "user",
|
||||
});
|
||||
|
|
@ -67,7 +78,7 @@ export async function onboard(): Promise<number> {
|
|||
downgraded
|
||||
.map((t) => {
|
||||
const a = getAdapter(t);
|
||||
return `${a.label} 은(는) ${a.scopes.join("/")} 범위만 지원한다 → ${effectiveScope(t, wanted)} 로 설치한다`;
|
||||
return `${a.label} 은(는) ${a.scopes.join("/")} 범위만 지원해서, ${effectiveScope(t, wanted)} 범위로 설치됩니다`;
|
||||
})
|
||||
.join("\n"),
|
||||
);
|
||||
|
|
@ -91,35 +102,126 @@ export async function onboard(): Promise<number> {
|
|||
].join("\n"),
|
||||
);
|
||||
}
|
||||
p.note(previews.join("\n\n"), "설치 계획");
|
||||
p.note(
|
||||
[
|
||||
previews.join("\n\n"),
|
||||
"",
|
||||
dim("아래 경로에 스킬 파일을 복사합니다."),
|
||||
dim("직접 수정하신 파일이 있으면 덮어쓰지 않고 알려드립니다."),
|
||||
].join("\n"),
|
||||
"이렇게 설치됩니다",
|
||||
);
|
||||
|
||||
const go = await p.confirm({ message: "이대로 설치할까?", initialValue: true });
|
||||
const go = await p.confirm({ message: "이대로 진행할까요?", initialValue: true });
|
||||
if (p.isCancel(go) || !go) return cancel();
|
||||
|
||||
const s = p.spinner();
|
||||
s.start("설치 중");
|
||||
s.start("설치하고 있습니다");
|
||||
const outcomes = [];
|
||||
try {
|
||||
for (const t of targets) {
|
||||
const scope = effectiveScope(t, wanted);
|
||||
s.message(`설치 중 — ${getAdapter(t).label}`);
|
||||
s.message(`${getAdapter(t).label} 에 설치하고 있습니다`);
|
||||
outcomes.push(...(await runInstall({ targets: [t], scope })));
|
||||
}
|
||||
s.stop("설치 완료");
|
||||
s.stop("설치가 끝났습니다");
|
||||
} catch (err) {
|
||||
s.stop("설치 실패", 1);
|
||||
s.stop("설치하지 못했습니다", 1);
|
||||
p.log.error(err instanceof Error ? err.message : String(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printOutcomes(outcomes);
|
||||
|
||||
await offerTools();
|
||||
|
||||
printNextSteps(targets);
|
||||
|
||||
p.outro(`${accent("designpaca")} 준비됨 — 이제 브리프를 던져라`);
|
||||
p.outro(`${accent("designpaca")} 준비됐습니다 — 만들고 싶은 것을 말씀해 주세요`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 도구를 제안한다. 스킬 설치가 끝난 뒤에 묻는다 —
|
||||
* 본 작업을 이것 때문에 붙잡지 않기 위해서다. 건너뛰어도 스킬은 돈다.
|
||||
*/
|
||||
export async function offerTools(): Promise<void> {
|
||||
const status = await checkTools();
|
||||
const missing = status.filter((s) => !s.installed);
|
||||
|
||||
if (missing.length === 0) {
|
||||
p.log.success(
|
||||
`브라우저 도구가 이미 준비돼 있습니다 ${dim("— 레퍼런스 조사에서 바로 활용됩니다")}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
p.note(
|
||||
[
|
||||
"이 스킬은 디자인을 시작하기 전에 실제 사이트를 찾아 분석합니다.",
|
||||
"그런데 참고할 만한 사이트일수록 자동 접근을 막아두는 경우가 많습니다.",
|
||||
"",
|
||||
"브라우저 도구가 있으면 그런 사이트도 직접 열어서",
|
||||
"화면을 캡처하고 글자 크기·여백 같은 값을 실제로 잴 수 있습니다.",
|
||||
"",
|
||||
dim("설치하지 않아도 스킬은 정상 동작합니다."),
|
||||
dim("나중에 `designpaca tools` 로 언제든 추가하실 수 있습니다."),
|
||||
].join("\n"),
|
||||
"브라우저 도구를 함께 설치할까요?",
|
||||
);
|
||||
|
||||
const picked = await p.multiselect<string>({
|
||||
message: "설치할 도구를 골라주세요 (스페이스로 선택 / 엔터로 확인)",
|
||||
options: missing.map(({ tool }) => ({
|
||||
value: tool.id,
|
||||
label: tool.label,
|
||||
hint: `${tool.why} · ${tool.size}`,
|
||||
})),
|
||||
initialValues: missing.map((m) => m.tool.id),
|
||||
required: false,
|
||||
});
|
||||
if (p.isCancel(picked) || picked.length === 0) {
|
||||
p.log.info(
|
||||
`건너뛰겠습니다. ${dim("필요해지면 `designpaca tools` 로 설치하실 수 있습니다.")}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const s = p.spinner();
|
||||
const followUps: string[] = [];
|
||||
for (const id of picked) {
|
||||
const entry = missing.find((m) => m.tool.id === id);
|
||||
if (!entry) continue;
|
||||
s.start(`${entry.tool.label} 을(를) 설치하고 있습니다 ${dim(`(${entry.tool.size})`)}`);
|
||||
try {
|
||||
await entry.tool.install();
|
||||
s.stop(`${entry.tool.label} 설치 완료`);
|
||||
if (entry.tool.followUp) followUps.push(entry.tool.followUp);
|
||||
} catch (err) {
|
||||
// 도구 설치 실패로 스킬 설치까지 실패시키지 않는다. 스킬은 이미 깔렸다.
|
||||
s.stop(`${entry.tool.label} 을(를) 설치하지 못했습니다`, 1);
|
||||
p.log.warn(
|
||||
[
|
||||
err instanceof Error ? err.message.split("\n")[0] : String(err),
|
||||
dim("스킬 설치는 정상적으로 끝났습니다. 이 도구만 나중에 다시 시도하셔도 됩니다."),
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (followUps.length > 0) {
|
||||
p.note(
|
||||
[
|
||||
"아래 명령은 에이전트 설정을 바꾸는 것이라 직접 실행해 주세요.",
|
||||
"",
|
||||
...followUps,
|
||||
].join("\n"),
|
||||
"마지막 한 단계",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function cancel(): number {
|
||||
p.cancel("설치를 취소했다. 아무것도 바꾸지 않았다.");
|
||||
p.cancel("설치를 취소했습니다. 아무것도 변경하지 않았습니다.");
|
||||
return 130;
|
||||
}
|
||||
|
|
|
|||
29
packages/cli/test/tools.test.ts
Normal file
29
packages/cli/test/tools.test.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
const CLI = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../dist/index.js");
|
||||
|
||||
test("tools 는 --yes 없이 아무것도 설치하지 않는다", async () => {
|
||||
// 수백 MB 를 받는 명령이라 조용히 시작하면 안 된다.
|
||||
// 상태만 보이거나, 이미 다 있으면 그렇다고 말해야 한다.
|
||||
const { stdout } = await exec(process.execPath, [CLI, "tools", "--no-update-check"]);
|
||||
assert.match(stdout, /브라우저 도구/);
|
||||
assert.doesNotMatch(stdout, /설치 중/);
|
||||
});
|
||||
|
||||
test("도움말에 tools 명령이 나온다", async () => {
|
||||
const { stdout } = await exec(process.execPath, [CLI, "--help"]);
|
||||
assert.match(stdout, /designpaca tools/);
|
||||
});
|
||||
|
||||
test("사용자에게 보이는 문구가 명령조가 아니다", async () => {
|
||||
// 스킬 문서는 단정한 반말이 의도지만, 사용자를 향한 CLI 문구는 다르다.
|
||||
const { stdout } = await exec(process.execPath, [CLI, "--help"]);
|
||||
const rude = /(해라|써라|봐라|던져라|가라)/;
|
||||
assert.doesNotMatch(stdout, rude, "도움말에 명령조 표현이 남아 있다");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue