sonamu 0.9.0 → 0.9.2
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/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -4
- package/dist/api/sonamu.d.ts +5 -2
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +61 -22
- package/dist/auth/plugins/entity-definitions/api-key.d.ts.map +1 -1
- package/dist/auth/plugins/entity-definitions/api-key.js +16 -12
- package/dist/auth/plugins/wrappers/admin.d.ts +169 -73
- package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/api-key.d.ts +2 -3
- package/dist/auth/plugins/wrappers/api-key.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/api-key.js +4 -3
- package/dist/auth/plugins/wrappers/phone-number.d.ts +19 -18
- package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/sso.d.ts +119 -38
- package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/two-factor.d.ts +29 -19
- package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/username.d.ts +14 -13
- package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
- package/dist/bin/cli.js +7 -2
- package/dist/database/knex.d.ts.map +1 -1
- package/dist/database/knex.js +10 -18
- package/dist/database/puri.d.ts +6 -6
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +1 -1
- package/dist/database/puri.types.d.ts +2 -1
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/database/upsert-builder.js +1 -1
- package/dist/testing/fixture-manager.js +3 -3
- package/dist/ui-web/assets/index-CfgbCoOJ.js +199 -0
- package/dist/ui-web/assets/index-Dr8pRJC_.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/package.json +14 -12
- package/src/api/config.ts +0 -4
- package/src/api/sonamu.ts +70 -23
- package/src/auth/plugins/entity-definitions/api-key.ts +5 -10
- package/src/auth/plugins/wrappers/api-key.ts +4 -4
- package/src/bin/cli.ts +7 -1
- package/src/database/knex.ts +9 -19
- package/src/database/puri.ts +6 -6
- package/src/database/puri.types.ts +2 -1
- package/src/database/upsert-builder.ts +1 -1
- package/src/skills/AGENTS.md +3 -3
- package/src/skills/project/README.md +1 -3
- package/src/skills/sonamu/auth-plugins.md +21 -13
- package/src/skills/sonamu/auth.md +17 -0
- package/src/skills/sonamu/cdd.md +18 -26
- package/src/skills/sonamu/create-sonamu.md +8 -0
- package/src/skills/sonamu/entity-basic.md +1 -1
- package/src/skills/sonamu/scaffolding.md +13 -0
- package/src/testing/fixture-manager.ts +2 -2
- package/dist/ui-web/assets/index-C-Zz-wYg.css +0 -1
- package/dist/ui-web/assets/index-DejDON8K.js +0 -238
|
@@ -43,19 +43,13 @@ export const apiKeyEntityDef: BetterAuthEntityDef = {
|
|
|
43
43
|
{ name: "enabled", type: "boolean", desc: "활성화 여부" },
|
|
44
44
|
{ name: "permissions", type: "string", nullable: true, desc: "권한" },
|
|
45
45
|
{ name: "metadata", type: "string", nullable: true, desc: "메타데이터 (JSON)" },
|
|
46
|
+
{ name: "reference_id", type: "string", desc: "참조 대상 ID" },
|
|
47
|
+
{ name: "config_id", type: "string", dbDefault: "'default'", desc: "설정 ID" },
|
|
46
48
|
{ name: "created_at", type: "date", dbDefault: "CURRENT_TIMESTAMP", desc: "생성일시" },
|
|
47
49
|
{ name: "updated_at", type: "date", nullable: true, desc: "수정일시" },
|
|
48
|
-
{
|
|
49
|
-
type: "relation",
|
|
50
|
-
name: "user",
|
|
51
|
-
with: "User",
|
|
52
|
-
relationType: "BelongsToOne",
|
|
53
|
-
onDelete: "CASCADE",
|
|
54
|
-
desc: "사용자",
|
|
55
|
-
},
|
|
56
50
|
],
|
|
57
51
|
indexes: [
|
|
58
|
-
{ type: "index", name: "
|
|
52
|
+
{ type: "index", name: "api_keys_reference_id_idx", columns: [{ name: "reference_id" }] },
|
|
59
53
|
{ type: "unique", name: "api_keys_key_unique", columns: [{ name: "key" }] },
|
|
60
54
|
],
|
|
61
55
|
subsets: {
|
|
@@ -80,7 +74,8 @@ export const apiKeyEntityDef: BetterAuthEntityDef = {
|
|
|
80
74
|
"metadata",
|
|
81
75
|
"created_at",
|
|
82
76
|
"updated_at",
|
|
83
|
-
"
|
|
77
|
+
"reference_id",
|
|
78
|
+
"config_id",
|
|
84
79
|
],
|
|
85
80
|
},
|
|
86
81
|
enums: {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { apiKey as _apiKey } from "better-auth/
|
|
2
|
-
import { type ApiKeyOptions } from "better-auth/plugins";
|
|
1
|
+
import { apiKey as _apiKey, type ApiKeyOptions } from "@better-auth/api-key";
|
|
3
2
|
|
|
4
3
|
import { merge } from "../../../utils/utils";
|
|
5
4
|
|
|
6
|
-
export type { ApiKeyOptions } from "better-auth/
|
|
5
|
+
export type { ApiKeyOptions } from "@better-auth/api-key";
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* API Key 플러그인 스키마
|
|
@@ -17,7 +16,8 @@ export const API_KEY_SCHEMA: ApiKeyOptions["schema"] = {
|
|
|
17
16
|
apikey: {
|
|
18
17
|
modelName: "api_keys",
|
|
19
18
|
fields: {
|
|
20
|
-
|
|
19
|
+
referenceId: "reference_id",
|
|
20
|
+
configId: "config_id",
|
|
21
21
|
lastRequest: "last_request",
|
|
22
22
|
requestCount: "request_count",
|
|
23
23
|
rateLimitEnabled: "rate_limit_enabled",
|
package/src/bin/cli.ts
CHANGED
|
@@ -309,7 +309,9 @@ function spawnApiDevServer(options?: { extraEnv?: Record<string, string> }) {
|
|
|
309
309
|
* Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
|
|
310
310
|
*/
|
|
311
311
|
function dev_all() {
|
|
312
|
-
|
|
312
|
+
const require = createRequire(import.meta.url);
|
|
313
|
+
const { version } = require("../../package.json");
|
|
314
|
+
console.log(`🌲 Sonamu v${version}\n`);
|
|
313
315
|
spawnApiDevServer();
|
|
314
316
|
}
|
|
315
317
|
|
|
@@ -498,6 +500,10 @@ async function runBuildSteps<T>(
|
|
|
498
500
|
* Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
|
|
499
501
|
*/
|
|
500
502
|
async function start() {
|
|
503
|
+
const require = createRequire(import.meta.url);
|
|
504
|
+
const { version } = require("../../package.json");
|
|
505
|
+
console.log(`🌲 Sonamu v${version}\n`);
|
|
506
|
+
|
|
501
507
|
const apiRoot = findApiRootPath();
|
|
502
508
|
const entryPoint = "dist/index.js";
|
|
503
509
|
|
package/src/database/knex.ts
CHANGED
|
@@ -12,12 +12,20 @@ export function createKnexInstance(config: Knex.Config): Knex {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
config.pool = {
|
|
15
|
+
maxConnectionLifetimeMillis: 1800000,
|
|
16
|
+
maxConnectionLifetimeJitterMillis: 300000,
|
|
15
17
|
...config.pool,
|
|
16
18
|
propagateCreateError: false,
|
|
17
19
|
idleTimeoutMillis: 10000,
|
|
18
20
|
reapIntervalMillis: 1000,
|
|
19
21
|
acquireTimeoutMillis: 30000,
|
|
20
22
|
createTimeoutMillis: 30000,
|
|
23
|
+
validate: (connection: unknown) => {
|
|
24
|
+
if (typeof connection !== "object" || connection === null) return false;
|
|
25
|
+
const conn = connection as Record<string, unknown>;
|
|
26
|
+
if (conn._ending === true || conn._closed === true) return false;
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
21
29
|
afterCreate: ((
|
|
22
30
|
conn: Knex.Client & Record<string, unknown>,
|
|
23
31
|
done: (err: Error | null, conn: Knex.Client) => void,
|
|
@@ -30,27 +38,9 @@ export function createKnexInstance(config: Knex.Config): Knex {
|
|
|
30
38
|
stream.stream.setKeepAlive(true, 10000);
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
conn.on("error", (err: Error) => {
|
|
34
|
-
Object.defineProperty(conn, "__knex__disposed", {
|
|
35
|
-
value: err,
|
|
36
|
-
writable: true,
|
|
37
|
-
configurable: true,
|
|
38
|
-
enumerable: true,
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
41
|
done(null, conn);
|
|
43
42
|
}) satisfies Knex.PoolConfig["afterCreate"],
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
knexInstance.client.validateConnection = (connection: unknown) => {
|
|
48
|
-
if (typeof connection !== "object" || connection === null) return false;
|
|
49
|
-
if ("__knex__disposed" in connection) return false;
|
|
50
|
-
const conn = connection as Record<string, unknown>;
|
|
51
|
-
if (conn._ending === true || conn._closed === true) return false;
|
|
52
|
-
return true;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return knexInstance;
|
|
45
|
+
return knex(config);
|
|
56
46
|
}
|
package/src/database/puri.ts
CHANGED
|
@@ -225,23 +225,23 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// Raw functions for SELECT
|
|
228
|
-
static rawString(sql: string, params:
|
|
228
|
+
static rawString(sql: string, params: Knex.RawBinding[] = []): SqlExpression<"string"> {
|
|
229
229
|
return { _type: "sql_expression", _return: "string", _sql: sql, _params: params };
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
static rawStringArray(sql: string, params:
|
|
232
|
+
static rawStringArray(sql: string, params: Knex.RawBinding[] = []): SqlExpression<"string[]"> {
|
|
233
233
|
return { _type: "sql_expression", _return: "string[]", _sql: sql, _params: params };
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
static rawNumber(sql: string, params:
|
|
236
|
+
static rawNumber(sql: string, params: Knex.RawBinding[] = []): SqlExpression<"number"> {
|
|
237
237
|
return { _type: "sql_expression", _return: "number", _sql: sql, _params: params };
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
static rawBoolean(sql: string, params:
|
|
240
|
+
static rawBoolean(sql: string, params: Knex.RawBinding[] = []): SqlExpression<"boolean"> {
|
|
241
241
|
return { _type: "sql_expression", _return: "boolean", _sql: sql, _params: params };
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
static rawDate(sql: string, params:
|
|
244
|
+
static rawDate(sql: string, params: Knex.RawBinding[] = []): SqlExpression<"date"> {
|
|
245
245
|
return { _type: "sql_expression", _return: "date", _sql: sql, _params: params };
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -814,7 +814,7 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
|
|
|
814
814
|
}
|
|
815
815
|
|
|
816
816
|
// WHERE RAW
|
|
817
|
-
whereRaw(sql: string, bindings?: readonly
|
|
817
|
+
whereRaw(sql: string, bindings?: readonly Knex.RawBinding[]): this {
|
|
818
818
|
this.knexQuery.whereRaw(sql, bindings);
|
|
819
819
|
return this;
|
|
820
820
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* oxlint-disable @typescript-eslint/no-explicit-any */ // Puri.types.ts는 다양한 타입을 사용하고 있습니다.
|
|
2
2
|
|
|
3
|
+
import { type Knex } from "knex";
|
|
3
4
|
import { type QueryResult } from "pg";
|
|
4
5
|
|
|
5
6
|
import { type DatabaseForeignKeys, type DatabaseSchemaExtend } from "../types/types";
|
|
@@ -308,7 +309,7 @@ export type SqlExpression<
|
|
|
308
309
|
_type: "sql_expression"; // 또는 "computed_value"
|
|
309
310
|
_return: T;
|
|
310
311
|
_sql: string;
|
|
311
|
-
_params:
|
|
312
|
+
_params: Knex.RawBinding[];
|
|
312
313
|
};
|
|
313
314
|
|
|
314
315
|
// 결과 타입 가독성을 위한 타입 확장
|
|
@@ -458,7 +458,7 @@ export class UpsertBuilder {
|
|
|
458
458
|
|
|
459
459
|
// 각 FK 컬럼에 대한 WHERE IN 조건 추가
|
|
460
460
|
for (const { column, values } of fkConditions) {
|
|
461
|
-
deleteQuery = deleteQuery.whereIn(column, values);
|
|
461
|
+
deleteQuery = deleteQuery.whereIn(column, values as Knex.Value[]);
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
// 방금 upsert한 ID는 제외
|
package/src/skills/AGENTS.md
CHANGED
|
@@ -39,7 +39,7 @@ See `.claude/skills/sonamu/SKILL.md` for the full skill list.
|
|
|
39
39
|
|
|
40
40
|
## Cross-workspace gate
|
|
41
41
|
|
|
42
|
-
- For changes in this scope, root `pnpm check` (
|
|
42
|
+
- For changes in this scope, root `pnpm check` (oxlint + oxfmt) must pass before handoff.
|
|
43
43
|
|
|
44
44
|
## TypeScript type safety policy
|
|
45
45
|
|
|
@@ -54,9 +54,9 @@ See `.claude/skills/sonamu/SKILL.md` for the full skill list.
|
|
|
54
54
|
After editing any `.ts` or `.tsx` file, always run both checks before considering the task done:
|
|
55
55
|
|
|
56
56
|
1. `npx tsc --noEmit --skipLibCheck` — type errors
|
|
57
|
-
2. `pnpm
|
|
57
|
+
2. `pnpm check` — lint and format (oxlint + oxfmt)
|
|
58
58
|
|
|
59
|
-
Do not skip
|
|
59
|
+
Do not skip lint/format check even when tsc passes. oxlint catches `noNonNullAssertion`, import order, and other issues that tsc does not.
|
|
60
60
|
|
|
61
61
|
## Skill read triggers
|
|
62
62
|
|
|
@@ -18,18 +18,18 @@ Use the `auth generate --plugins` command to auto-generate plugin entities.
|
|
|
18
18
|
|
|
19
19
|
## Supported Plugins
|
|
20
20
|
|
|
21
|
-
| Plugin ID | Wrapper function | Package
|
|
22
|
-
| -------------- | ---------------- |
|
|
23
|
-
| `admin` | `admin()` | `better-auth/plugins`
|
|
24
|
-
| `organization` | `organization()` | `better-auth/plugins`
|
|
25
|
-
| `2fa` | `twoFactor()` | `better-auth/plugins`
|
|
26
|
-
| `username` | `username()` | `better-auth/plugins`
|
|
27
|
-
| `phone-number` | `phoneNumber()` | `better-auth/plugins`
|
|
28
|
-
| `api-key` | `apiKey()` |
|
|
29
|
-
| `jwt` | `jwt()` | `better-auth/plugins`
|
|
30
|
-
| `passkey` | `passkey()` | `@better-auth/passkey`
|
|
31
|
-
| `sso` | `sso()` | `@better-auth/sso`
|
|
32
|
-
| `anonymous` | `anonymous()` | `better-auth/plugins`
|
|
21
|
+
| Plugin ID | Wrapper function | Package | Purpose |
|
|
22
|
+
| -------------- | ---------------- | ----------------------- | ----------------------------------------------------- |
|
|
23
|
+
| `admin` | `admin()` | `better-auth/plugins` | Admin features, user ban/unban, session impersonation |
|
|
24
|
+
| `organization` | `organization()` | `better-auth/plugins` | Organization, team, member, and invitation management |
|
|
25
|
+
| `2fa` | `twoFactor()` | `better-auth/plugins` | TOTP-based two-factor authentication |
|
|
26
|
+
| `username` | `username()` | `better-auth/plugins` | Username-based authentication |
|
|
27
|
+
| `phone-number` | `phoneNumber()` | `better-auth/plugins` | Phone number authentication |
|
|
28
|
+
| `api-key` | `apiKey()` | `@better-auth/api-key` | API key issuance/management, rate limiting |
|
|
29
|
+
| `jwt` | `jwt()` | `better-auth/plugins` | JWT tokens + JWKS key management |
|
|
30
|
+
| `passkey` | `passkey()` | `@better-auth/passkey` | WebAuthn/Passkey authentication |
|
|
31
|
+
| `sso` | `sso()` | `@better-auth/sso` | OIDC/SAML SSO integration |
|
|
32
|
+
| `anonymous` | `anonymous()` | `better-auth/plugins` | Anonymous user support |
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
@@ -183,6 +183,11 @@ Schema mapping:
|
|
|
183
183
|
### api-key
|
|
184
184
|
|
|
185
185
|
**Additional entities:** ApiKey (table: `api_keys`)
|
|
186
|
+
**Package:** `@better-auth/api-key` (must be installed separately)
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pnpm add @better-auth/api-key
|
|
190
|
+
```
|
|
186
191
|
|
|
187
192
|
```typescript
|
|
188
193
|
import { apiKey } from "sonamu/auth/plugins";
|
|
@@ -192,12 +197,15 @@ apiKey();
|
|
|
192
197
|
|
|
193
198
|
Schema mapping:
|
|
194
199
|
|
|
195
|
-
- `
|
|
200
|
+
- `referenceId` → `reference_id`, `configId` → `config_id`
|
|
201
|
+
- `lastRequest` → `last_request`, `requestCount` → `request_count`
|
|
196
202
|
- `rateLimitEnabled` → `rate_limit_enabled`, `rateLimitTimeWindow` → `rate_limit_time_window`
|
|
197
203
|
- `rateLimitMax` → `rate_limit_max`, `refillInterval` → `refill_interval`
|
|
198
204
|
- `refillAmount` → `refill_amount`, `lastRefillAt` → `last_refill_at`
|
|
199
205
|
- `expiresAt` → `expires_at`, `createdAt` → `created_at`, `updatedAt` → `updated_at`
|
|
200
206
|
|
|
207
|
+
Note: v1.5.0에서 `userId`가 `referenceId`로 변경됨. `referenceId`는 user 또는 organization을 참조하는 polymorphic ID.
|
|
208
|
+
|
|
201
209
|
### jwt
|
|
202
210
|
|
|
203
211
|
**Additional entities:** Jwks (table: `jwks`)
|
|
@@ -71,6 +71,23 @@ The 4 entities generated (`betterAuthV1` array):
|
|
|
71
71
|
- Fields with changed types are updated automatically
|
|
72
72
|
- Uses snake_case column names (better-auth uses camelCase)
|
|
73
73
|
|
|
74
|
+
## Adding Fixture Companions (`auth add-companions`)
|
|
75
|
+
|
|
76
|
+
After running `auth generate`, run this command once to add `fixtureCompanions` to the `id` prop of better-auth entities (User, etc.).
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm sonamu auth add-companions
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Purpose:** Enables automatic Account fixture creation when generating User fixtures. Without this, fixture gen creates User records without a corresponding credentials Account, breaking auth-dependent tests.
|
|
83
|
+
|
|
84
|
+
**What it does:**
|
|
85
|
+
- Reads `fixtureCompanions` from the `betterAuthV1` definitions
|
|
86
|
+
- Adds them to the existing entity.json `id` prop's cone
|
|
87
|
+
- Skips if `fixtureCompanions` already exists
|
|
88
|
+
|
|
89
|
+
**When to run:** Once, after `auth generate`, before running `fixture gen` for the first time. Re-running is safe (idempotent).
|
|
90
|
+
|
|
74
91
|
## Field Mapping (Applied Automatically)
|
|
75
92
|
|
|
76
93
|
**Source code:** `modules/sonamu/src/auth/better-auth-entities.ts` (BASE_FIELD_MAPPINGS)
|
package/src/skills/sonamu/cdd.md
CHANGED
|
@@ -12,7 +12,6 @@ description: CDD artifact technical reference. contract.md format, rules.json fo
|
|
|
12
12
|
Documents domain rules and decision rationale in a cohesive form. Located at `contract/**/*.contract.md`.
|
|
13
13
|
|
|
14
14
|
**Include:**
|
|
15
|
-
|
|
16
15
|
- Domain rules and constraints ("Refunds are only allowed within 7 days of payment")
|
|
17
16
|
- Decision rationale ("Due to PG provider policy")
|
|
18
17
|
- Cross-module domain workflows ("Order status: pending → confirmed → shipped → completed")
|
|
@@ -21,7 +20,6 @@ Documents domain rules and decision rationale in a cohesive form. Located at `co
|
|
|
21
20
|
- Edge cases and intended handling
|
|
22
21
|
|
|
23
22
|
**Exclude:**
|
|
24
|
-
|
|
25
23
|
- Implementation details (file paths, function names, class structure)
|
|
26
24
|
- API endpoints or data schemas
|
|
27
25
|
- UI layouts or component structure
|
|
@@ -64,13 +62,13 @@ Records code conventions and UI/API rules.
|
|
|
64
62
|
}
|
|
65
63
|
```
|
|
66
64
|
|
|
67
|
-
| Field
|
|
68
|
-
|
|
69
|
-
| `description`
|
|
70
|
-
| `rules[].id`
|
|
71
|
-
| `rules[].when`
|
|
72
|
-
| `rules[].instruction` | Specific directive to follow
|
|
73
|
-
| `rules[].examples`
|
|
65
|
+
| Field | Description |
|
|
66
|
+
|-------|-------------|
|
|
67
|
+
| `description` | Scope and intent of the rule-set |
|
|
68
|
+
| `rules[].id` | Stable identifier |
|
|
69
|
+
| `rules[].when` | Condition under which the rule applies |
|
|
70
|
+
| `rules[].instruction` | Specific directive to follow |
|
|
71
|
+
| `rules[].examples` | Optional code/usage examples |
|
|
74
72
|
|
|
75
73
|
### `*.known-issues.json`
|
|
76
74
|
|
|
@@ -96,21 +94,14 @@ Records known bugs, framework constraints, and temporary workarounds. Include in
|
|
|
96
94
|
ACs are not managed as separate documents. The describe/test names in test files are the ACs themselves.
|
|
97
95
|
|
|
98
96
|
**AC writing principles:**
|
|
99
|
-
|
|
100
97
|
- **Small and specific**: one AC = one behavior/outcome
|
|
101
98
|
- **Include input and expected result in the name**: `"Returns 409 when email is duplicate"` > `"Returns an error"`
|
|
102
99
|
|
|
103
100
|
```typescript
|
|
104
|
-
describe(
|
|
105
|
-
test(
|
|
106
|
-
|
|
107
|
-
});
|
|
108
|
-
test("returns 400 when password is shorter than 8 characters", () => {
|
|
109
|
-
/* TODO */
|
|
110
|
-
});
|
|
111
|
-
test("returns the created user_id on success", () => {
|
|
112
|
-
/* TODO */
|
|
113
|
-
});
|
|
101
|
+
describe('sign up', () => {
|
|
102
|
+
test('returns 409 when email is duplicate', () => { /* TODO */ });
|
|
103
|
+
test('returns 400 when password is shorter than 8 characters', () => { /* TODO */ });
|
|
104
|
+
test('returns the created user_id on success', () => { /* TODO */ });
|
|
114
105
|
});
|
|
115
106
|
```
|
|
116
107
|
|
|
@@ -122,13 +113,14 @@ describe("sign up", () => {
|
|
|
122
113
|
pnpm cdd <command>
|
|
123
114
|
```
|
|
124
115
|
|
|
125
|
-
| Command
|
|
126
|
-
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---------|-------------|
|
|
127
118
|
| `cdd ac add <file> <test-name>` | Add an empty test skeleton to a test file. Use `--describe <group>` to specify a describe block |
|
|
128
|
-
| `cdd ac list [file]`
|
|
129
|
-
| `cdd rules validate`
|
|
130
|
-
| `cdd agents init [--force]`
|
|
131
|
-
| `cdd agents sync [--dry-run]`
|
|
119
|
+
| `cdd ac list [file]` | Print the describe/test tree of a test file. Parses `test()`, `it()`, and `testAs()` patterns |
|
|
120
|
+
| `cdd rules validate` | Validate the format of `contract/rules/*.rules.json` |
|
|
121
|
+
| `cdd agents init [--force]` | Initialize CDD agent setup + contract scaffold (creates `.agents/`, `AGENTS.md`, `contract/planning.md`, `contract/rules/web.rules.json`, `contract/rules/api.rules.json`) |
|
|
122
|
+
| `cdd agents sync [--dry-run]` | Update CDD agent prompts to the latest version |
|
|
123
|
+
| `cdd contract init [--force]` | Scaffold contract directory only (`planning.md`, `rules/web.rules.json`, `rules/api.rules.json`). Alias: `cdd agents contract init` |
|
|
132
124
|
|
|
133
125
|
### Common options
|
|
134
126
|
|
|
@@ -114,6 +114,14 @@ pnpm create sonamu my_project \
|
|
|
114
114
|
|
|
115
115
|
> Will fail if sonamu is an npm version. See "Sonamu Link Setup" below.
|
|
116
116
|
|
|
117
|
+
To create a new **project-local** skill file (in `.claude/skills/local/`):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pnpm sonamu skills create <name>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This creates `.claude/skills/local/<name>.md` with a frontmatter skeleton. Use it to document project-specific conventions or troubleshooting notes that aren't appropriate for the shared Sonamu skills.
|
|
124
|
+
|
|
117
125
|
3. Start the dev server
|
|
118
126
|
|
|
119
127
|
```bash
|
|
@@ -622,7 +622,7 @@ When setting a default value on an ENUM field, the value must be wrapped in **es
|
|
|
622
622
|
{ "name": "status", "type": "enum", "id": "ApprovalStatus", "dbDefault": "pending" }
|
|
623
623
|
// Error: cannot use column reference in DEFAULT expression
|
|
624
624
|
|
|
625
|
-
// Incorrect: single quotes — causes
|
|
625
|
+
// Incorrect: single quotes — causes oxfmt format error
|
|
626
626
|
{ "name": "status", "type": "enum", "id": "ApprovalStatus", "dbDefault": "'pending'" }
|
|
627
627
|
```
|
|
628
628
|
|
|
@@ -19,6 +19,19 @@ description: Reference for using Sonamu UI Scaffolding. Common errors and soluti
|
|
|
19
19
|
|
|
20
20
|
**DO NOT:** scaffold only model and model_test and skip the view-related items
|
|
21
21
|
|
|
22
|
+
## CLI Scaffold Commands
|
|
23
|
+
|
|
24
|
+
All scaffold types can be generated via CLI (in addition to Sonamu UI):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm sonamu scaffold model <entityId>
|
|
28
|
+
pnpm sonamu scaffold model_test <entityId>
|
|
29
|
+
pnpm sonamu scaffold view_list <entityId>
|
|
30
|
+
pnpm sonamu scaffold view_form <entityId>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`model_test` generates the test file skeleton (`{entity}.model.test.ts`) with the correct imports and `bootstrap(vi)` setup. Run it from `packages/api` after the entity's types.ts and migration are ready.
|
|
34
|
+
|
|
22
35
|
---
|
|
23
36
|
|
|
24
37
|
## Pre-Scaffolding Checklist
|
|
@@ -342,7 +342,7 @@ export class FixtureManagerClass {
|
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
for
|
|
345
|
+
for (const fixture of fixtures) {
|
|
346
346
|
const entity = EntityManager.get(fixture.entityId);
|
|
347
347
|
|
|
348
348
|
// 사용자 지정 컬럼 기준 중복 확인 → target
|
|
@@ -680,7 +680,7 @@ export class FixtureManagerClass {
|
|
|
680
680
|
]);
|
|
681
681
|
!isTest() && console.log(chalk.green(`Reset sequence for ${tableName}: ${maxId}`));
|
|
682
682
|
}
|
|
683
|
-
} catch
|
|
683
|
+
} catch {
|
|
684
684
|
// 시퀀스가 없는 테이블(join table 등)은 무시
|
|
685
685
|
!isTest() && console.log(chalk.gray(`Skipped sequence reset for ${tableName}`));
|
|
686
686
|
}
|