@gaonjs/cli 0.35.0 → 0.36.0
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/dist/commands/check.d.ts +9 -0
- package/dist/commands/check.js +39 -8
- package/dist/commands/new.d.ts +7 -1
- package/dist/commands/new.js +8 -2
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.js +6 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.js +54 -23
- package/dist/templates/index.d.ts +17 -0
- package/dist/templates/index.js +18 -1
- package/dist/templates/index.ts +24 -0
- package/dist/templates/project/AGENTS.md.tpl +1 -1
- package/dist/templates/project/CLAUDE.md.tpl +1 -1
- package/dist/templates/project/package.json.tpl +1 -1
- package/package.json +5 -5
- package/dist/templates/auth/app.ts.tpl +0 -29
- package/dist/templates/auth/server.ts.tpl +0 -14
package/dist/commands/check.d.ts
CHANGED
|
@@ -49,6 +49,15 @@ export interface CheckReport {
|
|
|
49
49
|
readonly regen: CheckRegen;
|
|
50
50
|
readonly steps: readonly CheckStepResult[];
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 결정 170: 프로젝트가 선언한 패키지 매니저를 감지한다. `gaon new` 는
|
|
54
|
+
* package.json 의 `packageManager` 필드(corepack 핀 · 결정 169)에 선택 pm 을
|
|
55
|
+
* 기록하므로, `gaon check` 가 사용자 스크립트(예 build)를 돌릴 때 그 pm 으로
|
|
56
|
+
* 실행해야 한다 — pnpm 하드코딩은 npm/yarn 로 스캐폴드한 프로젝트에서 pnpm 이
|
|
57
|
+
* "This project is configured to use npm" 로 실행을 거부해 build 단계가 깨진다.
|
|
58
|
+
* 우선순위: packageManager 필드 → 락파일 → pnpm(기본).
|
|
59
|
+
*/
|
|
60
|
+
export declare function detectPackageManager(cwd: string): 'pnpm' | 'npm' | 'yarn';
|
|
52
61
|
/**
|
|
53
62
|
* `gaon check` 진입점. 검사 전에 .gaon 을 재생성(규칙 3)한 뒤 각 단계를
|
|
54
63
|
* 순서대로 실행하고, 하나라도 실패하면 exit 1. --only 지정 시 그 단계만
|
package/dist/commands/check.js
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* @gaonjs/cli · `gaon check` — 통합 검증 (M9-G · v0.15 §13.5)
|
|
3
3
|
*
|
|
4
4
|
* 배포 · CI · AI 에이전트가 한 번에 신뢰할 수 있는 검사 묶음. 사용자의
|
|
5
|
-
* package.json
|
|
6
|
-
* ·
|
|
7
|
-
* (`node_modules/.bin/tsc --noEmit` ·
|
|
8
|
-
*
|
|
5
|
+
* package.json 스크립트를 그대로 재사용하되, **프로젝트 선언 pm** 으로 돌린다
|
|
6
|
+
* (결정 170 · `<pm> run <name>` — pnpm/npm/yarn). 스크립트가 없으면 폴백으로
|
|
7
|
+
* 로컬 바이너리를 직접 부른다(`node_modules/.bin/tsc --noEmit` ·
|
|
8
|
+
* `node_modules/.bin/vue-tsc --noEmit`).
|
|
9
9
|
*
|
|
10
10
|
* The One Way — 하나의 명령이 4 검사를 순서대로 돌린다(결정 157 · doctor 기본 포함):
|
|
11
|
-
* 1) typecheck (
|
|
12
|
-
* 2) vue-tsc (
|
|
13
|
-
* 3) build (
|
|
11
|
+
* 1) typecheck (<pm> run typecheck 또는 tsc --noEmit)
|
|
12
|
+
* 2) vue-tsc (<pm> run vue-tsc 또는 vue-tsc --noEmit)
|
|
13
|
+
* 3) build (<pm> run build)
|
|
14
14
|
* 4) doctor (기본 포함 · 규칙 5 등 doctor 규칙 · --no-doctor 로 뺌 · 코어 재사용)
|
|
15
15
|
*
|
|
16
16
|
* 옵션 최소:
|
|
@@ -54,6 +54,35 @@ function hasScript(cwd, name) {
|
|
|
54
54
|
function binExists(cwd, name) {
|
|
55
55
|
return existsSync(join(cwd, 'node_modules', '.bin', name));
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* 결정 170: 프로젝트가 선언한 패키지 매니저를 감지한다. `gaon new` 는
|
|
59
|
+
* package.json 의 `packageManager` 필드(corepack 핀 · 결정 169)에 선택 pm 을
|
|
60
|
+
* 기록하므로, `gaon check` 가 사용자 스크립트(예 build)를 돌릴 때 그 pm 으로
|
|
61
|
+
* 실행해야 한다 — pnpm 하드코딩은 npm/yarn 로 스캐폴드한 프로젝트에서 pnpm 이
|
|
62
|
+
* "This project is configured to use npm" 로 실행을 거부해 build 단계가 깨진다.
|
|
63
|
+
* 우선순위: packageManager 필드 → 락파일 → pnpm(기본).
|
|
64
|
+
*/
|
|
65
|
+
export function detectPackageManager(cwd) {
|
|
66
|
+
const pkgPath = join(cwd, 'package.json');
|
|
67
|
+
if (existsSync(pkgPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
70
|
+
const pm = pkg.packageManager?.split('@')[0];
|
|
71
|
+
if (pm === 'pnpm' || pm === 'npm' || pm === 'yarn')
|
|
72
|
+
return pm;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// 파싱 실패는 락파일/기본으로 폴백
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml')))
|
|
79
|
+
return 'pnpm';
|
|
80
|
+
if (existsSync(join(cwd, 'yarn.lock')))
|
|
81
|
+
return 'yarn';
|
|
82
|
+
if (existsSync(join(cwd, 'package-lock.json')))
|
|
83
|
+
return 'npm';
|
|
84
|
+
return 'pnpm';
|
|
85
|
+
}
|
|
57
86
|
/**
|
|
58
87
|
* 단일 서브 프로세스를 spawn 해서 stdout+stderr 를 모으고 exit 코드를
|
|
59
88
|
* 돌려준다. 실행 실패(파일 없음)는 exit 127 로 매핑.
|
|
@@ -77,7 +106,9 @@ async function runStep(step, cwd) {
|
|
|
77
106
|
// 1) 사용자 스크립트가 정의돼 있으면 그걸 쓴다(한 곳에서 관리).
|
|
78
107
|
const scriptName = step;
|
|
79
108
|
if (hasScript(cwd, scriptName)) {
|
|
80
|
-
|
|
109
|
+
// 결정 170: pnpm 하드코딩 대신 프로젝트 선언 pm 으로 실행(npm/yarn 스캐폴드
|
|
110
|
+
// 대응). `<pm> run <name>` 는 pnpm·npm·yarn(classic) 셋 다 동일하게 동작한다.
|
|
111
|
+
const cmd = detectPackageManager(cwd);
|
|
81
112
|
const args = ['run', scriptName];
|
|
82
113
|
const { exitCode, output } = await runSubprocess(cwd, cmd, args);
|
|
83
114
|
// 결정 146(12차 W2): build 가 성공했으면 **등록된 앱마다** dist/<앱>/index.html 과
|
package/dist/commands/new.d.ts
CHANGED
|
@@ -8,7 +8,13 @@ export interface NewCommandOptions {
|
|
|
8
8
|
readonly skipInstall?: boolean;
|
|
9
9
|
/** git init · 첫 커밋 스킵(테스트·기존 git 저장소에 삽입). */
|
|
10
10
|
readonly skipGit?: boolean;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* 초기 `install` 을 돌릴 패키지 매니저(`--package-manager <pm>` · 별칭 `--pm`).
|
|
13
|
+
* 기본 pnpm(모노레포 관례 정합). blessed 툴체인(Dockerfile·pnpm-workspace)은
|
|
14
|
+
* The One Way 로 pnpm 을 유지하지만, package.json 의 `packageManager` 필드는
|
|
15
|
+
* 선택한 pm 에 맞춰 적는다 — 항상 pnpm 을 적으면 `--pm yarn` 시 yarn 1.22
|
|
16
|
+
* corepack enforcement 가 install 을 거부한다(결정 169 · 167 개정).
|
|
17
|
+
*/
|
|
12
18
|
readonly packageManager?: 'pnpm' | 'npm' | 'yarn';
|
|
13
19
|
/**
|
|
14
20
|
* (테스트 훅) 템플릿의 gaonjs 의존성 버전. 미지정 시 파사드(gaonjs)
|
package/dist/commands/new.js
CHANGED
|
@@ -22,7 +22,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
22
22
|
import { spawnSync } from 'node:child_process';
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { readFileSync } from 'node:fs';
|
|
25
|
-
import { renderProjectFiles } from '../templates/index.js';
|
|
25
|
+
import { renderProjectFiles, PACKAGE_MANAGER_PINS, } from '../templates/index.js';
|
|
26
26
|
import { writeUiKitScaffold } from '../uikit.js';
|
|
27
27
|
import { regenerateProjectGaon } from './gen.js';
|
|
28
28
|
/** 이름 유효성 — npm 패키지명 규칙(단순 부분)만 검사. */
|
|
@@ -172,7 +172,13 @@ export async function runNewCommand(name, opts = {}) {
|
|
|
172
172
|
// 파일 생성 — 실패 시 부분 생성물이 남지 않도록 폴더를 정리하지는 않는다
|
|
173
173
|
// (사용자가 원인 파악 후 rm -rf 로 지우도록). 정상 흐름에서는 문제 없음.
|
|
174
174
|
const gaonjsVersion = opts.gaonjsVersion ?? detectGaonjsVersion();
|
|
175
|
-
|
|
175
|
+
// 결정 169: packageManager 필드 = 선택한 pm 의 corepack 핀. 항상 pnpm 을
|
|
176
|
+
// 적던 옛 관례는 --pm yarn 시 yarn 1.22 corepack 이 install 을 거부시켰다.
|
|
177
|
+
const files = renderProjectFiles({
|
|
178
|
+
projectName: name,
|
|
179
|
+
gaonjsVersion,
|
|
180
|
+
packageManager: PACKAGE_MANAGER_PINS[pm],
|
|
181
|
+
});
|
|
176
182
|
let filesCreated = 0;
|
|
177
183
|
try {
|
|
178
184
|
mkdirSync(root, { recursive: true });
|
package/dist/doctor.d.ts
CHANGED
|
@@ -24,6 +24,15 @@ export { usesLayoutBreakpoint, checkPageLayoutBreakpoint, } from './doctor/page-
|
|
|
24
24
|
export { usesLinkButtonNesting, checkLinkButtonNesting, } from './doctor/link-button-nesting.js';
|
|
25
25
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
26
26
|
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
27
|
+
/**
|
|
28
|
+
* 실행할 검사 이름. 지정 없음(undefined) = 23개 모두.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* doctor 정적 검사 25종의 정본 목록(§2.2). `--check=` 필터의 인정 집합도
|
|
32
|
+
* 이 배열을 단일 출처로 삼는다(parseDoctorChecks) — 새 규칙 추가 시 여기만
|
|
33
|
+
* 늘리면 실행·필터·타입이 함께 정합된다(손유지 중복 리스트 표류 방지).
|
|
34
|
+
*/
|
|
35
|
+
export declare const ALL_RULES: readonly DoctorRule[];
|
|
27
36
|
export interface DoctorCommandOptions {
|
|
28
37
|
readonly cwd?: string;
|
|
29
38
|
readonly json?: boolean;
|
package/dist/doctor.js
CHANGED
|
@@ -94,7 +94,12 @@ export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, }
|
|
|
94
94
|
/**
|
|
95
95
|
* 실행할 검사 이름. 지정 없음(undefined) = 23개 모두.
|
|
96
96
|
*/
|
|
97
|
-
|
|
97
|
+
/**
|
|
98
|
+
* doctor 정적 검사 25종의 정본 목록(§2.2). `--check=` 필터의 인정 집합도
|
|
99
|
+
* 이 배열을 단일 출처로 삼는다(parseDoctorChecks) — 새 규칙 추가 시 여기만
|
|
100
|
+
* 늘리면 실행·필터·타입이 함께 정합된다(손유지 중복 리스트 표류 방지).
|
|
101
|
+
*/
|
|
102
|
+
export const ALL_RULES = [
|
|
98
103
|
'response-mixing',
|
|
99
104
|
'n-plus-one',
|
|
100
105
|
'dependency-direction',
|
package/dist/index.d.ts
CHANGED
|
@@ -53,5 +53,22 @@ export interface RunOptions {
|
|
|
53
53
|
* 지정 없음(undefined) = 5 검사 모두 실행. 알 수 없는 이름은 무시(안전).
|
|
54
54
|
*/
|
|
55
55
|
export declare function parseDoctorChecks(argv: readonly string[]): DoctorRule[] | undefined;
|
|
56
|
+
/** `gaon new` argv 파싱 결과. name 부재·pm 오타를 호출부가 분기 처리한다. */
|
|
57
|
+
export interface ParsedNewArgs {
|
|
58
|
+
/** 프로젝트 이름(첫 위치 인자). 부재 시 undefined. */
|
|
59
|
+
readonly name?: string;
|
|
60
|
+
/** 검증 통과한 패키지 매니저. 미지정 시 undefined(호출부가 pnpm 기본). */
|
|
61
|
+
readonly packageManager?: "pnpm" | "npm" | "yarn";
|
|
62
|
+
/** pm 값이 주어졌으나 pnpm·npm·yarn 이 아닐 때 그 원문(에러 안내용). */
|
|
63
|
+
readonly unknownPm?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `gaon new <name>` 의 인자를 파싱한다.
|
|
67
|
+
* 패키지 매니저 플래그: `--package-manager <pm>`(정본) · `--pm <pm>`(별칭) ·
|
|
68
|
+
* 둘 다 `=` 형(`--package-manager=npm`)도 허용. 값을 취하는 플래그이므로
|
|
69
|
+
* 이름 위치 인자를 고를 때 그 값을 건너뛴다 — `gaon new --pm npm demo` 의
|
|
70
|
+
* npm 이 프로젝트 이름으로 오인되지 않도록(결정 167 · O-1 근본 fix).
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseNewArgs(rest: readonly string[]): ParsedNewArgs;
|
|
56
73
|
/** CLI 진입점. argv 는 실행 인자(process.argv.slice(2))를 받는다. */
|
|
57
74
|
export declare function runCli(argv: readonly string[], opts?: RunOptions): void;
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { runServeCommand } from "./serve.js";
|
|
|
26
26
|
import { runWorkCommand } from "./work.js";
|
|
27
27
|
import { runJobsCommand } from "./jobs.js";
|
|
28
28
|
import { runDbCommand } from "./commands/db.js";
|
|
29
|
-
import { runDoctorCommand } from "./doctor.js";
|
|
29
|
+
import { runDoctorCommand, ALL_RULES } from "./doctor.js";
|
|
30
30
|
import { runMcpCommand } from "./commands/mcp.js";
|
|
31
31
|
export { startDev, resolveDevLayout, regenerateGaonOnce, } from "./dev.js";
|
|
32
32
|
export { runDevCommand } from "./commands/dev.js";
|
|
@@ -95,7 +95,7 @@ function renderHelp(version = VERSION) {
|
|
|
95
95
|
"",
|
|
96
96
|
" 사용법:",
|
|
97
97
|
" gaon 로드맵과 개발 상태를 출력",
|
|
98
|
-
" gaon new <name> 새 프로젝트 스캐폴드 (파일 → 설치 → git · --skip-install · --skip-git · --
|
|
98
|
+
" gaon new <name> 새 프로젝트 스캐폴드 (파일 → 설치 → git · --skip-install · --skip-git · --package-manager <pnpm|npm|yarn>)",
|
|
99
99
|
" gaon dev 개발 스택 통합 (Docker · .gaon · serve · tsc/vue-tsc · 재시작 워처)",
|
|
100
100
|
" gaon dev --stop-docker Ctrl+C 시 Docker Compose 도 down",
|
|
101
101
|
" gaon dev --no-watch|--no-tsc|--no-vue-tsc|--no-docker 개별 debug 옵션",
|
|
@@ -146,18 +146,10 @@ function renderHelp(version = VERSION) {
|
|
|
146
146
|
* 지정 없음(undefined) = 5 검사 모두 실행. 알 수 없는 이름은 무시(안전).
|
|
147
147
|
*/
|
|
148
148
|
export function parseDoctorChecks(argv) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"connections",
|
|
154
|
-
"migration-diff",
|
|
155
|
-
"shared-composable-purity",
|
|
156
|
-
"no-auto-import",
|
|
157
|
-
"csrf-wiring",
|
|
158
|
-
"async-offload",
|
|
159
|
-
];
|
|
160
|
-
const isKnown = (s) => known.includes(s);
|
|
149
|
+
// 인정 집합은 doctor.ts 의 ALL_RULES(정본 25종)를 단일 출처로 쓴다 — 과거
|
|
150
|
+
// 손유지 9종 리스트가 뒤처져 --check=seal-security 같은 16종이 조용히 무시되고
|
|
151
|
+
// 전체 검사로 되돌아가던 표류를 근본 차단한다(결정 168).
|
|
152
|
+
const isKnown = (s) => ALL_RULES.includes(s);
|
|
161
153
|
const out = [];
|
|
162
154
|
for (const a of argv) {
|
|
163
155
|
if (a.startsWith("--check=")) {
|
|
@@ -169,6 +161,41 @@ export function parseDoctorChecks(argv) {
|
|
|
169
161
|
}
|
|
170
162
|
return out.length ? out : undefined;
|
|
171
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* `gaon new <name>` 의 인자를 파싱한다.
|
|
166
|
+
* 패키지 매니저 플래그: `--package-manager <pm>`(정본) · `--pm <pm>`(별칭) ·
|
|
167
|
+
* 둘 다 `=` 형(`--package-manager=npm`)도 허용. 값을 취하는 플래그이므로
|
|
168
|
+
* 이름 위치 인자를 고를 때 그 값을 건너뛴다 — `gaon new --pm npm demo` 의
|
|
169
|
+
* npm 이 프로젝트 이름으로 오인되지 않도록(결정 167 · O-1 근본 fix).
|
|
170
|
+
*/
|
|
171
|
+
export function parseNewArgs(rest) {
|
|
172
|
+
let name;
|
|
173
|
+
let pmRaw;
|
|
174
|
+
for (let i = 0; i < rest.length; i++) {
|
|
175
|
+
const a = rest[i];
|
|
176
|
+
if (a === "--package-manager" || a === "--pm") {
|
|
177
|
+
pmRaw = rest[++i];
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const eqPrefix = a.startsWith("--package-manager=")
|
|
181
|
+
? "--package-manager="
|
|
182
|
+
: a.startsWith("--pm=")
|
|
183
|
+
? "--pm="
|
|
184
|
+
: undefined;
|
|
185
|
+
if (eqPrefix !== undefined) {
|
|
186
|
+
pmRaw = a.slice(eqPrefix.length);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (a.startsWith("--"))
|
|
190
|
+
continue;
|
|
191
|
+
if (name === undefined)
|
|
192
|
+
name = a;
|
|
193
|
+
}
|
|
194
|
+
if (pmRaw === "pnpm" || pmRaw === "npm" || pmRaw === "yarn") {
|
|
195
|
+
return { name, packageManager: pmRaw };
|
|
196
|
+
}
|
|
197
|
+
return { name, unknownPm: pmRaw === undefined || pmRaw === "" ? undefined : pmRaw };
|
|
198
|
+
}
|
|
172
199
|
/** CLI 진입점. argv 는 실행 인자(process.argv.slice(2))를 받는다. */
|
|
173
200
|
export function runCli(argv, opts = {}) {
|
|
174
201
|
const version = opts.version ?? VERSION;
|
|
@@ -460,19 +487,23 @@ export function runCli(argv, opts = {}) {
|
|
|
460
487
|
// `gaon new <name>` — 프로젝트 스캐폴드(M9-F). 파일 생성 → 의존성 설치 → git init.
|
|
461
488
|
if (argv[0] === "new") {
|
|
462
489
|
const rest = argv.slice(1);
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (!a.startsWith("--") && name === undefined)
|
|
466
|
-
name = a;
|
|
467
|
-
}
|
|
468
|
-
if (!name) {
|
|
490
|
+
const parsed = parseNewArgs(rest);
|
|
491
|
+
if (!parsed.name) {
|
|
469
492
|
process.stderr.write(" ✗ gaon new: 프로젝트 이름이 없습니다.\n → 예: gaon new demo\n");
|
|
470
493
|
process.exitCode = 1;
|
|
471
494
|
return;
|
|
472
495
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
496
|
+
// 알 수 없는 pm 값은 조용히 pnpm 으로 되돌리지 않고 사용자에게 알린다
|
|
497
|
+
// (에러가 곧 수리 안내서 · §7.5.3). 이전엔 무시돼 --package-manager npm 이
|
|
498
|
+
// pnpm 으로 설치되던 O-1 표류를 근본 차단한다(결정 167).
|
|
499
|
+
if (parsed.unknownPm !== undefined) {
|
|
500
|
+
process.stderr.write(` ✗ gaon new: 알 수 없는 패키지 매니저 '${parsed.unknownPm}'.\n` +
|
|
501
|
+
` → --package-manager 는 pnpm · npm · yarn 만 지원합니다(기본 pnpm).\n`);
|
|
502
|
+
process.exitCode = 1;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const name = parsed.name;
|
|
506
|
+
const packageManager = parsed.packageManager;
|
|
476
507
|
void runNewCommand(name, {
|
|
477
508
|
json: argv.includes("--json"),
|
|
478
509
|
skipInstall: argv.includes("--skip-install"),
|
|
@@ -7,7 +7,24 @@ export interface ProjectFile {
|
|
|
7
7
|
export interface ProjectTemplateTokens {
|
|
8
8
|
readonly projectName: string;
|
|
9
9
|
readonly gaonjsVersion: string;
|
|
10
|
+
/**
|
|
11
|
+
* package.json 의 `packageManager` 필드(corepack 핀 · 예 `pnpm@10.27.0`).
|
|
12
|
+
* 미지정 시 blessed 기본 pnpm. 선택한 pm 에 맞춰 채운다(결정 169).
|
|
13
|
+
*/
|
|
14
|
+
readonly packageManager?: string;
|
|
10
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* pm 별 `packageManager` 핀(corepack 형식 `<pm>@<x.y.z>`). 선택한 pm 을
|
|
18
|
+
* 그대로 적어 corepack enforcement 가 install 을 막지 않게 한다(결정 169 ·
|
|
19
|
+
* 12차 실사용 yarn 파손). 정본 버전 근거:
|
|
20
|
+
* · pnpm@10.27.0 — blessed 툴체인(Dockerfile corepack·CI)과 정합.
|
|
21
|
+
* · yarn@1.22.22 — yarn **classic** 최종 안정판. berry(2·4.x)는 `.yarnrc.yml`
|
|
22
|
+
* 없이 기본 PnP 라 node_modules 를 읽는 vite·gaon serve 를 깨므로 배제.
|
|
23
|
+
* · npm@10.9.9 — engines.node≥22(Node 22 LTS) 동봉 npm 라인의 tip.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PACKAGE_MANAGER_PINS: Readonly<Record<'pnpm' | 'npm' | 'yarn', string>>;
|
|
26
|
+
/** blessed 기본(pm 미선택 시). */
|
|
27
|
+
export declare const DEFAULT_PACKAGE_MANAGER: string;
|
|
11
28
|
/** 템플릿 문자열의 {{TOKEN}} 을 치환한다. 알 수 없는 토큰은 그대로 둔다. */
|
|
12
29
|
export declare function renderTemplate(raw: string, tokens: ProjectTemplateTokens): string;
|
|
13
30
|
/** 템플릿 폴더를 재귀 스캔해 파일 목록을 만든다(POSIX 경로 · 정렬). */
|
package/dist/templates/index.js
CHANGED
|
@@ -12,11 +12,28 @@ import { readFileSync, readdirSync } from 'node:fs';
|
|
|
12
12
|
import { dirname, join, posix, relative, sep } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
const TEMPLATE_DIR = join(dirname(fileURLToPath(import.meta.url)), 'project');
|
|
15
|
+
/**
|
|
16
|
+
* pm 별 `packageManager` 핀(corepack 형식 `<pm>@<x.y.z>`). 선택한 pm 을
|
|
17
|
+
* 그대로 적어 corepack enforcement 가 install 을 막지 않게 한다(결정 169 ·
|
|
18
|
+
* 12차 실사용 yarn 파손). 정본 버전 근거:
|
|
19
|
+
* · pnpm@10.27.0 — blessed 툴체인(Dockerfile corepack·CI)과 정합.
|
|
20
|
+
* · yarn@1.22.22 — yarn **classic** 최종 안정판. berry(2·4.x)는 `.yarnrc.yml`
|
|
21
|
+
* 없이 기본 PnP 라 node_modules 를 읽는 vite·gaon serve 를 깨므로 배제.
|
|
22
|
+
* · npm@10.9.9 — engines.node≥22(Node 22 LTS) 동봉 npm 라인의 tip.
|
|
23
|
+
*/
|
|
24
|
+
export const PACKAGE_MANAGER_PINS = {
|
|
25
|
+
pnpm: 'pnpm@10.27.0',
|
|
26
|
+
npm: 'npm@10.9.9',
|
|
27
|
+
yarn: 'yarn@1.22.22',
|
|
28
|
+
};
|
|
29
|
+
/** blessed 기본(pm 미선택 시). */
|
|
30
|
+
export const DEFAULT_PACKAGE_MANAGER = PACKAGE_MANAGER_PINS.pnpm;
|
|
15
31
|
/** 템플릿 문자열의 {{TOKEN}} 을 치환한다. 알 수 없는 토큰은 그대로 둔다. */
|
|
16
32
|
export function renderTemplate(raw, tokens) {
|
|
17
33
|
return raw
|
|
18
34
|
.replaceAll('{{PROJECT_NAME}}', tokens.projectName)
|
|
19
|
-
.replaceAll('{{GAONJS_VERSION}}', tokens.gaonjsVersion)
|
|
35
|
+
.replaceAll('{{GAONJS_VERSION}}', tokens.gaonjsVersion)
|
|
36
|
+
.replaceAll('{{PACKAGE_MANAGER}}', tokens.packageManager ?? DEFAULT_PACKAGE_MANAGER);
|
|
20
37
|
}
|
|
21
38
|
/** 템플릿 폴더를 재귀 스캔해 파일 목록을 만든다(POSIX 경로 · 정렬). */
|
|
22
39
|
export function listTemplateFiles(root = TEMPLATE_DIR) {
|
package/dist/templates/index.ts
CHANGED
|
@@ -23,15 +23,39 @@ export interface ProjectFile {
|
|
|
23
23
|
export interface ProjectTemplateTokens {
|
|
24
24
|
readonly projectName: string
|
|
25
25
|
readonly gaonjsVersion: string
|
|
26
|
+
/**
|
|
27
|
+
* package.json 의 `packageManager` 필드(corepack 핀 · 예 `pnpm@10.27.0`).
|
|
28
|
+
* 미지정 시 blessed 기본 pnpm. 선택한 pm 에 맞춰 채운다(결정 169).
|
|
29
|
+
*/
|
|
30
|
+
readonly packageManager?: string
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
const TEMPLATE_DIR = join(dirname(fileURLToPath(import.meta.url)), 'project')
|
|
29
34
|
|
|
35
|
+
/**
|
|
36
|
+
* pm 별 `packageManager` 핀(corepack 형식 `<pm>@<x.y.z>`). 선택한 pm 을
|
|
37
|
+
* 그대로 적어 corepack enforcement 가 install 을 막지 않게 한다(결정 169 ·
|
|
38
|
+
* 12차 실사용 yarn 파손). 정본 버전 근거:
|
|
39
|
+
* · pnpm@10.27.0 — blessed 툴체인(Dockerfile corepack·CI)과 정합.
|
|
40
|
+
* · yarn@1.22.22 — yarn **classic** 최종 안정판. berry(2·4.x)는 `.yarnrc.yml`
|
|
41
|
+
* 없이 기본 PnP 라 node_modules 를 읽는 vite·gaon serve 를 깨므로 배제.
|
|
42
|
+
* · npm@10.9.9 — engines.node≥22(Node 22 LTS) 동봉 npm 라인의 tip.
|
|
43
|
+
*/
|
|
44
|
+
export const PACKAGE_MANAGER_PINS: Readonly<Record<'pnpm' | 'npm' | 'yarn', string>> = {
|
|
45
|
+
pnpm: 'pnpm@10.27.0',
|
|
46
|
+
npm: 'npm@10.9.9',
|
|
47
|
+
yarn: 'yarn@1.22.22',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** blessed 기본(pm 미선택 시). */
|
|
51
|
+
export const DEFAULT_PACKAGE_MANAGER = PACKAGE_MANAGER_PINS.pnpm
|
|
52
|
+
|
|
30
53
|
/** 템플릿 문자열의 {{TOKEN}} 을 치환한다. 알 수 없는 토큰은 그대로 둔다. */
|
|
31
54
|
export function renderTemplate(raw: string, tokens: ProjectTemplateTokens): string {
|
|
32
55
|
return raw
|
|
33
56
|
.replaceAll('{{PROJECT_NAME}}', tokens.projectName)
|
|
34
57
|
.replaceAll('{{GAONJS_VERSION}}', tokens.gaonjsVersion)
|
|
58
|
+
.replaceAll('{{PACKAGE_MANAGER}}', tokens.packageManager ?? DEFAULT_PACKAGE_MANAGER)
|
|
35
59
|
}
|
|
36
60
|
|
|
37
61
|
/** 템플릿 폴더를 재귀 스캔해 파일 목록을 만든다(POSIX 경로 · 정렬). */
|
|
@@ -195,7 +195,7 @@ Gaon 의 제1 설계 목표는 **"AI 가 개발을 가장 잘하는 프레임웍
|
|
|
195
195
|
```bash
|
|
196
196
|
gaon check # .gaon 재생성 → typecheck + vue-tsc + build + doctor (기본 포함 · --no-doctor 로 뺌 · 결정 157)
|
|
197
197
|
gaon test # vitest — DB·NATS 는 실 인프라 (agents/testing.md)
|
|
198
|
-
gaon doctor # 정적 검사
|
|
198
|
+
gaon doctor # 정적 검사 25종 (§2.2)
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
### 4.1 CLI 명령 (전 명령 `--json` 지원)
|
|
@@ -85,7 +85,7 @@ Gaon 프레임웍 문서: https://gaonjs.dev
|
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
gaon check # .gaon 재생성 → 타입검사+build+doctor (CI 한 번에 · --no-doctor 로 doctor 뺌)
|
|
88
|
-
gaon doctor # 정적 검사
|
|
88
|
+
gaon doctor # 정적 검사 25종 (상세 AGENTS §2.2)
|
|
89
89
|
npm test # Vitest · DB 테스트는 실 Docker 필수 (§9)
|
|
90
90
|
```
|
|
91
91
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Gaon CLI 구현: 제너레이터·스캐폴딩·로드맵 출력 (M1 스텁)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
28
|
"typescript": "^5.9.0",
|
|
29
29
|
"vite": "^7.0.0",
|
|
30
|
-
"@gaonjs/config": "0.14.1",
|
|
31
|
-
"@gaonjs/data": "0.16.0",
|
|
32
30
|
"@gaonjs/async": "0.11.0",
|
|
33
|
-
"@gaonjs/i18n": "0.2.0",
|
|
34
31
|
"@gaonjs/mail": "0.2.0",
|
|
35
32
|
"@gaonjs/core": "0.2.1",
|
|
36
|
-
"@gaonjs/
|
|
33
|
+
"@gaonjs/config": "0.14.1",
|
|
34
|
+
"@gaonjs/i18n": "0.2.0",
|
|
35
|
+
"@gaonjs/web": "0.17.0",
|
|
36
|
+
"@gaonjs/data": "0.16.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node -e \"require('fs').cpSync('src/templates','dist/templates',{recursive:true})\""
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// 웹 앱 팩토리 — gaon g auth 스캐폴드. createWebApp 으로 세션·인증을 배선한다.
|
|
2
|
-
import { createApp, type AppSessionOptions } from 'gaonjs/web'
|
|
3
|
-
import appRoutes from './routes.js'
|
|
4
|
-
import session from './controllers/session.js'
|
|
5
|
-
import registration from './controllers/registration.js'
|
|
6
|
-
import dashboard from './controllers/dashboard.js'
|
|
7
|
-
import { loadUser } from './auth.js'
|
|
8
|
-
|
|
9
|
-
export interface WebAppDeps {
|
|
10
|
-
/** 세션 설정 — { redisUrl, secret } (또는 redis 인스턴스). */
|
|
11
|
-
readonly session: AppSessionOptions
|
|
12
|
-
/** 서명 쿠키/CSRF 용 비밀. */
|
|
13
|
-
readonly cookieSecret?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function createWebApp(deps: WebAppDeps) {
|
|
17
|
-
return createApp({
|
|
18
|
-
apps: [
|
|
19
|
-
{
|
|
20
|
-
name: '{{APP_NAME}}',
|
|
21
|
-
routes: appRoutes,
|
|
22
|
-
controllers: { session, registration, dashboard },
|
|
23
|
-
session: deps.session,
|
|
24
|
-
auth: { loadUser, loginRedirect: '/session/new' },
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
cookieSecret: deps.cookieSecret,
|
|
28
|
-
})
|
|
29
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// 서버 진입점 — gaon g auth 스캐폴드. `node dist/server.js` 로 실행.
|
|
2
|
-
import { createWebApp } from './apps/{{APP_NAME}}/app.js'
|
|
3
|
-
|
|
4
|
-
const app = await createWebApp({
|
|
5
|
-
session: {
|
|
6
|
-
redisUrl: process.env.REDIS_URL ?? 'redis://127.0.0.1:6379',
|
|
7
|
-
secret: process.env.SESSION_SECRET ?? 'change-me-to-a-32+char-random-secret!!',
|
|
8
|
-
},
|
|
9
|
-
cookieSecret: process.env.COOKIE_SECRET,
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const port = Number(process.env.PORT ?? 3000)
|
|
13
|
-
await app.listen({ port, host: '0.0.0.0' })
|
|
14
|
-
console.log(`web 앱이 http://localhost:${port} 에서 실행 중입니다.`)
|