sonamu 0.8.24 → 0.8.25

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.
Files changed (61) hide show
  1. package/dist/api/__tests__/config.test.js +189 -0
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +7 -2
  4. package/dist/api/sonamu.d.ts.map +1 -1
  5. package/dist/api/sonamu.js +14 -10
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +2 -1
  9. package/dist/auth/knex-adapter.d.ts +23 -0
  10. package/dist/auth/knex-adapter.d.ts.map +1 -0
  11. package/dist/auth/knex-adapter.js +159 -0
  12. package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
  13. package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
  14. package/dist/bin/cli.js +47 -9
  15. package/dist/bin/ts-loader-register.js +3 -29
  16. package/dist/bin/ts-loader-registration.d.ts +2 -0
  17. package/dist/bin/ts-loader-registration.d.ts.map +1 -0
  18. package/dist/bin/ts-loader-registration.js +42 -0
  19. package/package.json +4 -4
  20. package/src/api/__tests__/config.test.ts +225 -0
  21. package/src/api/config.ts +10 -4
  22. package/src/api/sonamu.ts +16 -13
  23. package/src/auth/index.ts +1 -0
  24. package/src/auth/knex-adapter.ts +207 -0
  25. package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
  26. package/src/bin/cli.ts +52 -9
  27. package/src/bin/ts-loader-register.ts +2 -32
  28. package/src/bin/ts-loader-registration.ts +55 -0
  29. package/src/skills/commands/sonamu-skills.md +20 -0
  30. package/src/skills/sonamu/SKILL.md +136 -137
  31. package/src/skills/sonamu/ai-agents.md +69 -69
  32. package/src/skills/sonamu/api.md +147 -147
  33. package/src/skills/sonamu/auth-migration.md +220 -220
  34. package/src/skills/sonamu/auth-plugins.md +83 -83
  35. package/src/skills/sonamu/auth.md +106 -106
  36. package/src/skills/sonamu/cdd.md +65 -200
  37. package/src/skills/sonamu/cone.md +138 -138
  38. package/src/skills/sonamu/config.md +191 -191
  39. package/src/skills/sonamu/create-sonamu.md +66 -66
  40. package/src/skills/sonamu/database.md +158 -158
  41. package/src/skills/sonamu/entity-basic.md +292 -293
  42. package/src/skills/sonamu/entity-relations.md +246 -246
  43. package/src/skills/sonamu/entity-validation-checklist.md +124 -124
  44. package/src/skills/sonamu/fixture-cli.md +231 -231
  45. package/src/skills/sonamu/framework-change.md +37 -37
  46. package/src/skills/sonamu/frontend.md +223 -223
  47. package/src/skills/sonamu/i18n.md +82 -82
  48. package/src/skills/sonamu/migration.md +77 -77
  49. package/src/skills/sonamu/model.md +222 -222
  50. package/src/skills/sonamu/naite.md +86 -86
  51. package/src/skills/sonamu/project-init.md +228 -228
  52. package/src/skills/sonamu/puri.md +122 -122
  53. package/src/skills/sonamu/scaffolding.md +154 -154
  54. package/src/skills/sonamu/skill-contribution.md +124 -124
  55. package/src/skills/sonamu/subset.md +46 -46
  56. package/src/skills/sonamu/tasks.md +82 -82
  57. package/src/skills/sonamu/testing-devrunner.md +147 -147
  58. package/src/skills/sonamu/testing.md +673 -673
  59. package/src/skills/sonamu/upsert.md +79 -79
  60. package/src/skills/sonamu/vector.md +67 -67
  61. package/src/skills/sonamu/workflow.md +0 -317
@@ -0,0 +1,62 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const tsLoaderRegisterStateKey = Symbol.for("sonamu.ts-loader-register.state");
4
+
5
+ type TsLoaderRegisterState = {
6
+ registered: boolean;
7
+ };
8
+
9
+ type GlobalWithTsLoaderRegisterState = typeof globalThis & {
10
+ [tsLoaderRegisterStateKey]?: TsLoaderRegisterState;
11
+ };
12
+
13
+ function resetRegisterState() {
14
+ const globalState = globalThis as GlobalWithTsLoaderRegisterState;
15
+ delete globalState[tsLoaderRegisterStateKey];
16
+ delete process.env.SWCRC_PATH;
17
+ }
18
+
19
+ describe("ensureTsLoaderRegistered", () => {
20
+ beforeEach(() => {
21
+ vi.resetModules();
22
+ vi.restoreAllMocks();
23
+ vi.unmock("node:module");
24
+ vi.unmock("../../utils/fs-utils.js");
25
+ resetRegisterState();
26
+ });
27
+
28
+ afterEach(() => {
29
+ vi.resetModules();
30
+ vi.restoreAllMocks();
31
+ vi.unmock("node:module");
32
+ vi.unmock("../../utils/fs-utils.js");
33
+ resetRegisterState();
34
+ });
35
+
36
+ it("프로젝트 .swcrc를 우선 사용하고 중복 등록하지 않는다", async () => {
37
+ const registerMock = vi.fn();
38
+ vi.doMock("node:module", () => ({
39
+ register: registerMock,
40
+ }));
41
+ vi.doMock("../../utils/fs-utils.js", () => ({
42
+ exists: vi.fn(async (candidate: string) => candidate === "/tmp/fixture-api/.swcrc"),
43
+ }));
44
+
45
+ const module = await import("../ts-loader-registration");
46
+
47
+ expect(registerMock).not.toHaveBeenCalled();
48
+
49
+ await module.ensureTsLoaderRegistered("/tmp/fixture-api");
50
+
51
+ expect(registerMock).toHaveBeenCalledTimes(1);
52
+ expect(registerMock).toHaveBeenCalledWith("@sonamu-kit/ts-loader/loader", {
53
+ parentURL: expect.stringContaining("/src/bin/ts-loader-registration"),
54
+ });
55
+ expect(process.env.SWCRC_PATH).toBe("/tmp/fixture-api/.swcrc");
56
+
57
+ await module.ensureTsLoaderRegistered("/tmp/another-api");
58
+
59
+ expect(registerMock).toHaveBeenCalledTimes(1);
60
+ expect(process.env.SWCRC_PATH).toBe("/tmp/fixture-api/.swcrc");
61
+ });
62
+ });
package/src/bin/cli.ts CHANGED
@@ -159,6 +159,7 @@ async function bootstrap() {
159
159
  ["fixture", "explore"],
160
160
  ["migrate", "run"],
161
161
  ["migrate", "apply", "#targets"],
162
+ ["migrate", "generate"],
162
163
  ["migrate", "status"],
163
164
  ["stub", "practice", "#name"],
164
165
  ["stub", "entity", "#name"],
@@ -185,6 +186,7 @@ async function bootstrap() {
185
186
  migrate_status,
186
187
  migrate_run,
187
188
  migrate_apply,
189
+ migrate_generate,
188
190
  fixture_init,
189
191
  fixture_import,
190
192
  fixture_sync,
@@ -546,6 +548,31 @@ async function migrate_run() {
546
548
  await migrator.runAction("apply", targets as (keyof SonamuDBConfig)[]);
547
549
  }
548
550
 
551
+ async function migrate_generate() {
552
+ await setupMigrator();
553
+
554
+ const { conns } = await migrator.getStatus();
555
+ const hasStatus0 = conns.some((conn) => conn.status === 0);
556
+ if (!hasStatus0) {
557
+ console.log(
558
+ chalk.red(
559
+ "마이그레이션 파일을 생성하려면 기존 마이그레이션이 최소 하나의 DB에 모두 적용되어 있어야 합니다.",
560
+ ),
561
+ );
562
+ for (const conn of conns) {
563
+ if (conn.pending.length > 0) {
564
+ console.log(chalk.yellow(` ${conn.name}: pending ${conn.pending.length}개`));
565
+ }
566
+ }
567
+ process.exit(1);
568
+ }
569
+
570
+ const count = await migrator.generatePreparedCodes();
571
+ if (count > 0) {
572
+ console.log(chalk.green(`${count}개의 마이그레이션 파일이 생성되었습니다.`));
573
+ }
574
+ }
575
+
549
576
  async function migrate_status() {
550
577
  await setupMigrator();
551
578
 
@@ -972,11 +999,23 @@ async function skills_sync() {
972
999
  }
973
1000
 
974
1001
  if (isGlobal) {
975
- await skills_sync_to(path.join(os.homedir(), ".claude"), sourceSkillsDir, sourceClaudeMd, {
1002
+ const homeClaudeDir = path.join(os.homedir(), ".claude");
1003
+ await skills_sync_to(homeClaudeDir, sourceSkillsDir, sourceClaudeMd, {
976
1004
  useSymlink: false,
977
1005
  copyProjectTemplates: false,
978
1006
  isGlobal: true,
979
1007
  });
1008
+
1009
+ // ~/.claude/commands/sonamu-skills.md 설치
1010
+ const sourceCommandsDir = path.join(sourceBase, "commands");
1011
+ const sourceCommand = path.join(sourceCommandsDir, "sonamu-skills.md");
1012
+ if (await exists(sourceCommand)) {
1013
+ const targetCommandsDir = path.join(homeClaudeDir, "commands");
1014
+ await mkdir(targetCommandsDir, { recursive: true });
1015
+ await cp(sourceCommand, path.join(targetCommandsDir, "sonamu-skills.md"));
1016
+ console.log(chalk.green(`✓ /sonamu-skills command installed → ~/.claude/commands/`));
1017
+ }
1018
+
980
1019
  console.log(chalk.cyan(`\n Global sync complete → ~/.claude/skills/sonamu/`));
981
1020
  console.log(chalk.dim(` These skills are available in all Claude Code sessions.`));
982
1021
  console.log(
@@ -1325,23 +1364,21 @@ async function auth_add_companions() {
1325
1364
 
1326
1365
  /**
1327
1366
  * 워크스페이스 루트를 찾습니다.
1328
- * 우선순위: pnpm-workspace.yaml > CLAUDE.md > 루트 package.json (workspaces 필드)
1367
+ * 우선순위: pnpm-workspace.yaml > package.json(workspaces) > .agents/
1368
+ *
1369
+ * CLAUDE.md는 서브패키지에도 존재할 수 있으므로 사용하지 않습니다.
1370
+ * .agents/는 agents init이 생성하는 디렉토리로, 워크스페이스 루트에만 존재합니다.
1329
1371
  */
1330
1372
  async function findWorkspaceRoot() {
1331
1373
  let dir = process.cwd();
1332
1374
 
1333
1375
  while (dir !== path.dirname(dir)) {
1334
- // 1. pnpm-workspace.yaml 파일이 있는지 확인. 있으면 확실한 monorepo 루트.
1376
+ // 1. pnpm-workspace.yaml: 확실한 monorepo 루트.
1335
1377
  if (await exists(path.join(dir, "pnpm-workspace.yaml"))) {
1336
1378
  return dir;
1337
1379
  }
1338
1380
 
1339
- // 2. CLAUDE.md 파일이 있는지 확인. 있으면 프로젝트 루트로 간주함.
1340
- if (await exists(path.join(dir, "CLAUDE.md"))) {
1341
- return dir;
1342
- }
1343
-
1344
- // 3. package.json에 workspaces 필드가 있으면 루트.
1381
+ // 2. package.json에 workspaces 필드가 있으면 monorepo 루트.
1345
1382
  const packagePath = path.join(dir, "package.json");
1346
1383
  if (await exists(packagePath)) {
1347
1384
  try {
@@ -1353,6 +1390,12 @@ async function findWorkspaceRoot() {
1353
1390
  // 파싱 실패시 무시
1354
1391
  }
1355
1392
  }
1393
+
1394
+ // 3. .agents/: agents init이 생성한 디렉토리. 서브패키지에는 존재하지 않음.
1395
+ if (await exists(path.join(dir, ".agents"))) {
1396
+ return dir;
1397
+ }
1398
+
1356
1399
  dir = path.dirname(dir);
1357
1400
  }
1358
1401
 
@@ -1,38 +1,8 @@
1
- import { register } from "node:module";
2
- import * as path from "node:path";
3
- import { exists } from "../utils/fs-utils.js";
4
1
  import { findApiRootPath } from "../utils/utils.js";
2
+ import { ensureTsLoaderRegistered } from "./ts-loader-registration.js";
5
3
 
6
4
  /**
7
5
  * @sonamu-kit/ts-loader/loader를 등록하는 스크립트입니다.
8
6
  * 이 스크립트는 sonamu cli로 dev 실행할 때 --import로 실행됩니다.
9
7
  */
10
- async function setupSwcConfig() {
11
- try {
12
- const apiRoot = findApiRootPath();
13
-
14
- // 프로젝트 루트에서 .swcrc 찾기
15
- const projectSwcrcPath = path.join(apiRoot, ".swcrc");
16
- if (await exists(projectSwcrcPath)) {
17
- // 사용자 프로젝트에 .swcrc가 있으면 우선으로 사용합니다.
18
- process.env.SWCRC_PATH = projectSwcrcPath;
19
- return;
20
- }
21
-
22
- // 아니라면 sonamu가 관리하는 .swcrc.project-default를 가져다 씁니다.
23
- const sonamuSwcrcPath = path.join(import.meta.dirname, "..", "..", ".swcrc.project-default");
24
- if (await exists(sonamuSwcrcPath)) {
25
- process.env.SWCRC_PATH = sonamuSwcrcPath;
26
- return;
27
- }
28
- } catch {
29
- // 환경 변수 설정 실패는 무시 (loader가 기본 설정 사용)
30
- }
31
- }
32
-
33
- // swc 설정 파일 경로를 환경 변수로 설정
34
- await setupSwcConfig();
35
-
36
- register("@sonamu-kit/ts-loader/loader", {
37
- parentURL: import.meta.url,
38
- });
8
+ await ensureTsLoaderRegistered(findApiRootPath());
@@ -0,0 +1,55 @@
1
+ import { register } from "node:module";
2
+ import * as path from "node:path";
3
+ import { exists } from "../utils/fs-utils.js";
4
+
5
+ const tsLoaderRegisterStateKey = Symbol.for("sonamu.ts-loader-register.state");
6
+
7
+ type TsLoaderRegisterState = {
8
+ registered: boolean;
9
+ };
10
+
11
+ type GlobalWithTsLoaderRegisterState = typeof globalThis & {
12
+ [tsLoaderRegisterStateKey]?: TsLoaderRegisterState;
13
+ };
14
+
15
+ function getTsLoaderRegisterState(): TsLoaderRegisterState {
16
+ const globalState = globalThis as GlobalWithTsLoaderRegisterState;
17
+
18
+ if (!globalState[tsLoaderRegisterStateKey]) {
19
+ globalState[tsLoaderRegisterStateKey] = { registered: false };
20
+ }
21
+
22
+ return globalState[tsLoaderRegisterStateKey];
23
+ }
24
+
25
+ async function setupSwcConfig(apiRoot: string) {
26
+ try {
27
+ const projectSwcrcPath = path.join(apiRoot, ".swcrc");
28
+ if (await exists(projectSwcrcPath)) {
29
+ process.env.SWCRC_PATH = projectSwcrcPath;
30
+ return;
31
+ }
32
+
33
+ const sonamuSwcrcPath = path.join(import.meta.dirname, "..", "..", ".swcrc.project-default");
34
+ if (await exists(sonamuSwcrcPath)) {
35
+ process.env.SWCRC_PATH = sonamuSwcrcPath;
36
+ return;
37
+ }
38
+ } catch {
39
+ // 환경 변수 설정 실패는 무시 (loader가 기본 설정 사용)
40
+ }
41
+ }
42
+
43
+ export async function ensureTsLoaderRegistered(apiRoot: string): Promise<void> {
44
+ const state = getTsLoaderRegisterState();
45
+ if (state.registered) {
46
+ return;
47
+ }
48
+
49
+ await setupSwcConfig(apiRoot);
50
+
51
+ register("@sonamu-kit/ts-loader/loader", {
52
+ parentURL: import.meta.url,
53
+ });
54
+ state.registered = true;
55
+ }
@@ -0,0 +1,20 @@
1
+ Read ~/.claude/skills/sonamu/SKILL.md and follow its instructions.
2
+
3
+ Then greet the user with the following format (exactly):
4
+
5
+ ---
6
+ Sonamu 스킬 가이드가 로드됐습니다. 어떤 작업을 하실 건가요?
7
+
8
+ **Workflow** · CDD · project-init · config
9
+ **Entity/DB** · entity-basic · entity-relations · subset · migration · database
10
+ **API** · model · api · puri · upsert
11
+ **Testing** · testing · devrunner · naite · cone · fixture-cli
12
+ **Auth** · auth · auth-migration · auth-plugins
13
+ **Frontend** · frontend · scaffolding · i18n
14
+ **Advanced** · vector · ai-agents · tasks
15
+ **Meta** · skill-contribution · framework-change
16
+
17
+ 어떤 스킬이나 작업을 도와드릴까요? (하고 싶은 작업을 자유롭게 설명해도 됩니다.)
18
+ ---
19
+
20
+ If the user provides $ARGUMENTS, skip the greeting and directly help with: $ARGUMENTS