@sayren/store-sdk 0.19.0 → 0.21.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/index.d.mts +2010 -1314
- package/dist/index.mjs +1283 -1028
- package/package.json +1 -1
- package/src/client.ts +165 -93
- package/src/index.ts +1 -2
- package/src/schemas/claim.ts +1 -1
- package/src/schemas/fulfillment.ts +26 -275
- package/src/schemas/misc.ts +1 -1
- package/src/schemas/order.ts +25 -61
- package/src/schemas/product.ts +234 -7
- package/src/schemas/promotion.ts +3 -1
- package/src/schemas/saved-option-set.ts +168 -0
- package/src/schemas/store.ts +2 -30
- package/src/schemas/storefront-hosting.ts +576 -3
- package/src/schemas/upload.ts +2 -48
- package/src/schemas/webhook-event.ts +28 -14
- package/src/schemas/digital-asset.ts +0 -65
- package/src/schemas/fulfillment-code.ts +0 -143
|
@@ -20,10 +20,28 @@ import { RESERVED_STORE_CODES } from "./store";
|
|
|
20
20
|
* - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 스토어당 30일에 3번까지 만든다
|
|
21
21
|
* - 409 `STOREFRONT_SITE_CHANGED` — 읽은 상태(`expected*`)와 현재 상태가 다르다. 다시 읽고 시도한다
|
|
22
22
|
* - 409 `STOREFRONT_SITE_NOT_ACTIVE` — 정지(`SUSPENDED`)된 사이트는 바꾸거나 끌 수 없다
|
|
23
|
+
* - 409 `STORE_SUSPENDED` — 정지된 스토어에는 사이트를 만들 수 없다
|
|
23
24
|
* - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
|
|
24
25
|
* - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
|
|
25
26
|
* - 400 `STOREFRONT_SLUG_CONFIRM_MISMATCH` — 호스팅 끄기 확인 입력이 현재 슬러그와 다르다
|
|
26
27
|
* - 503 `STOREFRONT_PREVIEW_NOT_CONFIGURED` — 미리보기 서명 키가 설정되지 않았다
|
|
28
|
+
*
|
|
29
|
+
* 편집(M2b, `/v1/storefront/site/editing`·`files/**`·`checks/**`·`verify`·`builds/**`, 버전 만들기):
|
|
30
|
+
* - 403 `STOREFRONT_EDITING_DISABLED` — 플랫폼 편집 스위치가 꺼져 있다
|
|
31
|
+
* - 503 `STOREFRONT_EDITING_NOT_CONFIGURED` — 편집 Sandbox가 설정되지 않았다
|
|
32
|
+
* - 409 `EDIT_CAPACITY_FULL` — 플랫폼 전체 동시 편집 상한에 걸렸다. `error.details.retryAfterSeconds` 뒤에 다시 한다
|
|
33
|
+
* - 409 `EDIT_SESSION_REQUIRED` — 편집 미리보기 링크: 편집 환경이 없다
|
|
34
|
+
* - 409 `CHECK_RUNNING` — 이 사이트에서 다른 종류의 검사가 돌고 있다(`error.details.checkId`)
|
|
35
|
+
* - 404 `STOREFRONT_CHECK_NOT_FOUND`
|
|
36
|
+
* - 409 `FILE_CHANGED` — 해시 조건이 어긋났다. 아무것도 쓰지 않았다(`error.details.conflicts: [{ path, currentHash }]`)
|
|
37
|
+
* - 409 `FILE_EXISTS` — 옮길 자리에 파일이 있다(`overwrite: true`로 덮는다)
|
|
38
|
+
* - 404 `FILE_NOT_FOUND` — 파일이 없다
|
|
39
|
+
* - 400 `INVALID_FILE_PATH` — 경로 형식이 틀렸거나 편집할 수 없는 경로다(`node_modules`·`dist`·`.env*` 등). 쓸 수 있는 곳은
|
|
40
|
+
* `src/**`·`public/**`와 루트 설정 파일 몇 개이고, `package.json`·`pnpm-lock.yaml`은 읽기만 된다
|
|
41
|
+
* - 413 `FILE_TOO_LARGE`·`WORKSPACE_TOO_LARGE` — 파일 하나 1MiB, 작업 트리 전체 20MiB·2,000개를 넘는다
|
|
42
|
+
* - 409 `STOREFRONT_BUILD_IN_PROGRESS` — 이 사이트에 끝나지 않은 빌드가 있다(사이트당 하나)
|
|
43
|
+
* - 404 `STOREFRONT_BUILD_NOT_FOUND`
|
|
44
|
+
* - 502 `STOREFRONT_SANDBOX_UNAVAILABLE` — 편집 Sandbox 호출이 실패했다. 잠시 뒤 다시 한다
|
|
27
45
|
*/
|
|
28
46
|
|
|
29
47
|
// ─── 슬러그 ───────────────────────────────────────────────
|
|
@@ -53,8 +71,26 @@ export const RESERVED_STOREFRONT_SLUGS: readonly string[] = [
|
|
|
53
71
|
"outbound",
|
|
54
72
|
];
|
|
55
73
|
|
|
74
|
+
/**
|
|
75
|
+
* 편집 미리보기 호스트 접미사 — 편집 미리보기 주소는 `{사이트 id 정규화}-preview.{호스팅 도메인}`이다(M2b). 사이트 주소와 같은
|
|
76
|
+
* 1단계 이름이라 슬러그가 이 모양이면 남의 편집 미리보기 주소와 겹칠 수 있어 슬러그로 쓰지 못한다.
|
|
77
|
+
*/
|
|
78
|
+
export const STOREFRONT_EDIT_PREVIEW_SUFFIX = "-preview";
|
|
79
|
+
|
|
56
80
|
export function isReservedStorefrontSlug(slug: string): boolean {
|
|
57
|
-
|
|
81
|
+
const lower = slug.toLowerCase();
|
|
82
|
+
return (
|
|
83
|
+
RESERVED_STOREFRONT_SLUGS.includes(lower) || lower.endsWith(STOREFRONT_EDIT_PREVIEW_SUFFIX)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 편집 미리보기 호스트 이름의 라벨 — 사이트 id의 `_`를 `-`로 바꾸고 `-preview`를 붙인다(`sfs_ab12cd34` → `sfs-ab12cd34-preview`).
|
|
89
|
+
* 편집 Sandbox Worker(`apps/hosting-sandbox/src/preview-host.ts`)가 같은 규칙의 복제본을 들고 있고 api
|
|
90
|
+
* `test/storefront-hosting-parity.spec.ts`가 둘을 대조한다. 규칙을 바꾸면 두 곳을 같이 고친다.
|
|
91
|
+
*/
|
|
92
|
+
export function storefrontEditPreviewLabelOf(siteId: string): string {
|
|
93
|
+
return `${siteId.replaceAll("_", "-")}${STOREFRONT_EDIT_PREVIEW_SUFFIX}`;
|
|
58
94
|
}
|
|
59
95
|
|
|
60
96
|
export const storefrontSlugSchema = z
|
|
@@ -92,8 +128,10 @@ export type StorefrontTemplate = z.infer<typeof storefrontTemplateSchema>;
|
|
|
92
128
|
// ─── 버전 ─────────────────────────────────────────────────
|
|
93
129
|
|
|
94
130
|
/**
|
|
95
|
-
* 버전 상태 —
|
|
96
|
-
*
|
|
131
|
+
* 버전 상태 — 템플릿 버전(`source: TEMPLATE`)은 템플릿 산출물을 그대로 쓰므로 만들 때 곧바로 `READY`다. 편집 버전
|
|
132
|
+
* (`source: EDIT`, M2b)은 작업 트리 스냅샷을 빌드하는 동안 `BUILDING`이고, 빌드·규칙 검증을 통과하면 `READY`, 실패하면
|
|
133
|
+
* `FAILED`다(실패 단계·로그 꼬리는 `buildId`의 빌드). 발행할 수 있는 것은 `READY`뿐이다. 발행 여부는 상태가 아니라 사이트의
|
|
134
|
+
* 발행 포인터가 원천이다(`published`).
|
|
97
135
|
*/
|
|
98
136
|
export const storefrontVersionStatusSchema = z
|
|
99
137
|
.enum(["BUILDING", "READY", "FAILED"])
|
|
@@ -112,6 +150,13 @@ export const storefrontVersionSchema = z.object({
|
|
|
112
150
|
.nullable()
|
|
113
151
|
.describe("마지막으로 발행한 시각 (ISO 8601). 발행한 적 없으면 null"),
|
|
114
152
|
message: z.string().nullable().describe("버전 메모"),
|
|
153
|
+
source: z
|
|
154
|
+
.enum(["TEMPLATE", "EDIT"])
|
|
155
|
+
.describe("`TEMPLATE` 템플릿 산출물 그대로 · `EDIT` 편집 작업 트리를 빌드함"),
|
|
156
|
+
buildId: z
|
|
157
|
+
.string()
|
|
158
|
+
.nullable()
|
|
159
|
+
.describe("편집 버전의 빌드 (`GET /v1/storefront/site/builds/{buildId}`). 템플릿 버전은 null"),
|
|
115
160
|
createdByType: z
|
|
116
161
|
.string()
|
|
117
162
|
.describe("만든 행위자 유형 (`USER`·`AGENT`·`APP`·`TOKEN`·`SYSTEM`). 늘어날 수 있다"),
|
|
@@ -161,6 +206,30 @@ export const storefrontProvisioningSchema = z.object({
|
|
|
161
206
|
});
|
|
162
207
|
export type StorefrontProvisioning = z.infer<typeof storefrontProvisioningSchema>;
|
|
163
208
|
|
|
209
|
+
/**
|
|
210
|
+
* 편집 미리보기 주소(M2b) — 사이트마다 `{사이트 id 정규화}-preview.{호스팅 도메인}` 호스트 하나다. 호스팅 도메인의 와일드카드
|
|
211
|
+
* 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(콘솔 전용)로 받은 쿠키가 있어야 열린다.
|
|
212
|
+
*/
|
|
213
|
+
export const storefrontEditPreviewSchema = z.object({
|
|
214
|
+
hostname: z
|
|
215
|
+
.string()
|
|
216
|
+
.nullable()
|
|
217
|
+
.describe(
|
|
218
|
+
"편집 미리보기 호스트 (예: `sfs-ab12cd34-preview.sayren.co`). 편집을 쓸 수 없으면 null",
|
|
219
|
+
),
|
|
220
|
+
});
|
|
221
|
+
export type StorefrontEditPreview = z.infer<typeof storefrontEditPreviewSchema>;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 사이트 정지 — 운영 정책에 따라 플랫폼 운영자가 사이트를 정지한다. 정지 중에는 방문자에게 정지 안내가 보이고 발행·되돌리기·템플릿 변경·
|
|
225
|
+
* 공개 전환·슬러그 변경·끄기·편집이 409 `STOREFRONT_SITE_NOT_ACTIVE`다. 셀러가 풀 수 없다(고객센터로 문의한다).
|
|
226
|
+
*/
|
|
227
|
+
export const storefrontSuspensionSchema = z.object({
|
|
228
|
+
reason: z.string().nullable().describe("정지 사유. 운영자가 적은 안내이고 없으면 null"),
|
|
229
|
+
suspendedAt: z.string().nullable().describe("정지한 시각 (ISO 8601)"),
|
|
230
|
+
});
|
|
231
|
+
export type StorefrontSuspension = z.infer<typeof storefrontSuspensionSchema>;
|
|
232
|
+
|
|
164
233
|
export const storefrontSiteSchema = z.object({
|
|
165
234
|
siteId: z.string(),
|
|
166
235
|
slug: z.string().describe("주소 슬러그"),
|
|
@@ -173,11 +242,16 @@ export const storefrontSiteSchema = z.object({
|
|
|
173
242
|
visibility: storefrontVisibilitySchema,
|
|
174
243
|
publishedVersion: storefrontVersionSchema.nullable().describe("지금 발행된 버전"),
|
|
175
244
|
provisioning: storefrontProvisioningSchema,
|
|
245
|
+
editPreview: storefrontEditPreviewSchema,
|
|
176
246
|
slugChangesRemaining: z
|
|
177
247
|
.number()
|
|
178
248
|
.int()
|
|
179
249
|
.min(0)
|
|
180
250
|
.describe(`최근 ${STOREFRONT_SLUG_CHANGE_WINDOW_DAYS}일 기준 남은 슬러그 변경 횟수`),
|
|
251
|
+
suspension: storefrontSuspensionSchema
|
|
252
|
+
.nullable()
|
|
253
|
+
.optional()
|
|
254
|
+
.describe("정지 정보. `status`가 `SUSPENDED`일 때만 있고 그 밖은 null"),
|
|
181
255
|
createdAt: z.string(),
|
|
182
256
|
updatedAt: z.string(),
|
|
183
257
|
});
|
|
@@ -187,6 +261,9 @@ export type StorefrontSite = z.infer<typeof storefrontSiteSchema>;
|
|
|
187
261
|
export const storefrontHostingSchema = z.object({
|
|
188
262
|
enabled: z.boolean().describe("새 사이트를 만들 수 있는가 (플랫폼 스위치)"),
|
|
189
263
|
domain: z.string().describe("호스팅 도메인 (예: `sayren.co`). 사이트 주소는 `{slug}.{domain}`"),
|
|
264
|
+
editingEnabled: z
|
|
265
|
+
.boolean()
|
|
266
|
+
.describe("파일 편집·버전 만들기를 쓸 수 있는가 (플랫폼 편집 스위치와 Sandbox 설정)"),
|
|
190
267
|
site: storefrontSiteSchema.nullable(),
|
|
191
268
|
});
|
|
192
269
|
export type StorefrontHosting = z.infer<typeof storefrontHostingSchema>;
|
|
@@ -281,3 +358,499 @@ export const storefrontPreviewLinkSchema = z.object({
|
|
|
281
358
|
expiresAt: z.string().describe("링크 만료 시각 (ISO 8601)"),
|
|
282
359
|
});
|
|
283
360
|
export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
|
|
361
|
+
|
|
362
|
+
// ─── 편집 (M2b) ───────────────────────────────────────────
|
|
363
|
+
//
|
|
364
|
+
// 셀러(콘솔)와 에이전트(MCP)가 사이트의 작업 트리를 고치고 버전을 만든다. 작업 트리는 사이트당 하나이고 편집 Sandbox
|
|
365
|
+
// (격리된 컨테이너)가 dev 서버로 띄운다. 편집 환경은 플랫폼 전체 동시 상한이 있고 15분 동안 활동이 없으면 잠든다(작업
|
|
366
|
+
// 트리는 보존된다). 버전 만들기는 그 시점의 작업 트리를 고정해 일회용 빌드 환경에서 규칙 검증 → 타입 검사 → 빌드를
|
|
367
|
+
// 하고, 통과하면 발행할 수 있는 `READY` 버전이 된다. 에이전트의 파일 쓰기·검사·버전 만들기는 승인 없이 되고, 발행·
|
|
368
|
+
// 되돌리기·공개 전환은 기존대로 승인이다. 편집 미리보기 링크는 콘솔 OWNER·ADMIN만 받는다(에이전트에게 주지 않는다).
|
|
369
|
+
|
|
370
|
+
/** 플랫폼 전체 동시 편집 환경 상한 */
|
|
371
|
+
export const STOREFRONT_EDIT_CAPACITY = 20;
|
|
372
|
+
/** 활동이 없으면 편집 환경이 잠드는 시간(분). 작업 트리는 보존된다 */
|
|
373
|
+
export const STOREFRONT_EDIT_IDLE_MINUTES = 15;
|
|
374
|
+
/**
|
|
375
|
+
* 편집 작업(파일 쓰기·검사·버전 만들기·편집 미리보기 링크 발급)이 없으면 서버가 편집 환경을 닫는 시간(분). 미리보기 탭만 열어 두면
|
|
376
|
+
* 유휴 잠들기(`STOREFRONT_EDIT_IDLE_MINUTES`)가 걸리지 않으므로 이 한도가 편집 환경을 끝낸다. 작업 트리는 보존된다
|
|
377
|
+
*/
|
|
378
|
+
export const STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES = 30;
|
|
379
|
+
/** 파일 하나의 최대 크기(바이트) */
|
|
380
|
+
export const STOREFRONT_FILE_MAX_BYTES = 1024 * 1024;
|
|
381
|
+
/** 작업 트리 전체 최대 크기(바이트) — 파일 수는 2,000개까지 */
|
|
382
|
+
export const STOREFRONT_WORKSPACE_MAX_BYTES = 20 * 1024 * 1024;
|
|
383
|
+
/** 일괄 쓰기 한 번의 최대 파일 수 */
|
|
384
|
+
export const STOREFRONT_FILE_BATCH_MAX = 50;
|
|
385
|
+
|
|
386
|
+
/** 편집할 수 없는 경로 조각 — 설치물·산출물 폴더 */
|
|
387
|
+
export const STOREFRONT_BLOCKED_PATH_SEGMENTS: readonly string[] = [
|
|
388
|
+
"node_modules",
|
|
389
|
+
"dist",
|
|
390
|
+
".output",
|
|
391
|
+
".tanstack",
|
|
392
|
+
".wrangler",
|
|
393
|
+
".git",
|
|
394
|
+
".nitro",
|
|
395
|
+
".vinxi",
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
/** 경로 조각에 쓸 수 있는 문자 — 편집 Sandbox Worker의 경로 규칙(sandbox-api.md §2.1)과 같다. TanStack Router 파일명(`$id.tsx`·`_layout`·`(group)`·`[.]`)이 들어간다 */
|
|
399
|
+
export const STOREFRONT_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9$_.()[\]@+=,-]+$/;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 보이지 않거나 표시 방향을 바꾸는 문자 — 제로폭(U+200B~U+200D, U+FEFF)과 bidi 제어(U+202A~U+202E, U+2066~U+2069). 비슷해 보이는
|
|
403
|
+
* 경로로 규칙 검사를 피하거나 리뷰어를 속이는 데 쓰인다. 문자 규칙이 이미 막지만 이유를 따로 드러내려고 먼저 본다.
|
|
404
|
+
*/
|
|
405
|
+
const INVISIBLE_OR_BIDI = /[\u200B-\u200D\uFEFF\u202A-\u202E\u2066-\u2069]/;
|
|
406
|
+
|
|
407
|
+
/** 경로 정규화 — NFC. 비교·저장은 이 값으로 한다(같아 보이는 두 경로가 다른 파일이 되지 않게) */
|
|
408
|
+
export function normalizeStorefrontPath(path: string): string {
|
|
409
|
+
return path.normalize("NFC");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 작업 트리 상대 경로인가(정규화한 값 기준) — `/`로 구분하고 `/`로 시작하지 않는다. `.`·`..`·빈 조각, 조각 문자 규칙
|
|
414
|
+
* (`STOREFRONT_PATH_SEGMENT_PATTERN`, 공백·역슬래시·제어·제로폭·bidi 문자 불가) 밖, 설치물·산출물 폴더
|
|
415
|
+
* (`STOREFRONT_BLOCKED_PATH_SEGMENTS`), `.env`로 시작하는 조각(파일·폴더)은 받지 않는다. 최대 256자·깊이 16.
|
|
416
|
+
* 어디에 쓸 수 있는지(`src/**`·`public/**`·루트 설정 파일)는 편집 Sandbox가 판정한다(400 `INVALID_FILE_PATH`).
|
|
417
|
+
* 편집 Sandbox Worker가 같은 규칙의 복제본을 들고 있다(api `test/storefront-hosting-parity.spec.ts`가 대조한다).
|
|
418
|
+
*/
|
|
419
|
+
export function isEditableStorefrontPath(path: string): boolean {
|
|
420
|
+
if (path.length === 0 || path.length > 256) return false;
|
|
421
|
+
if (INVISIBLE_OR_BIDI.test(path)) return false;
|
|
422
|
+
if (path !== normalizeStorefrontPath(path)) return false;
|
|
423
|
+
if (path.startsWith("/")) return false;
|
|
424
|
+
const segments = path.split("/");
|
|
425
|
+
if (segments.length > 16) return false;
|
|
426
|
+
for (const segment of segments) {
|
|
427
|
+
if (segment === "" || segment === "." || segment === "..") return false;
|
|
428
|
+
if (!STOREFRONT_PATH_SEGMENT_PATTERN.test(segment)) return false;
|
|
429
|
+
if (STOREFRONT_BLOCKED_PATH_SEGMENTS.includes(segment)) return false;
|
|
430
|
+
// `.env`로 시작하는 조각은 어디에 있든 받지 않는다(폴더 이름 포함, 편집 Sandbox `paths.ts`와 같다)
|
|
431
|
+
if (segment.startsWith(".env")) return false;
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** 경로 입력 — NFC로 정규화한 뒤 규칙을 본다 */
|
|
437
|
+
export const storefrontFilePathSchema = z
|
|
438
|
+
.string()
|
|
439
|
+
.transform(normalizeStorefrontPath)
|
|
440
|
+
.refine(isEditableStorefrontPath, "편집할 수 없는 경로입니다")
|
|
441
|
+
.describe("작업 트리 상대 경로 (예: `src/routes/index.tsx`). NFC로 정규화한다");
|
|
442
|
+
|
|
443
|
+
/** 파일 해시 — 내용 바이트의 SHA-256 hex(소문자 64자) */
|
|
444
|
+
export const storefrontFileHashSchema = z
|
|
445
|
+
.string()
|
|
446
|
+
.regex(/^[0-9a-f]{64}$/, "SHA-256 hex여야 합니다")
|
|
447
|
+
.describe("내용 바이트의 SHA-256 (소문자 hex)");
|
|
448
|
+
|
|
449
|
+
export const storefrontFileEncodingSchema = z
|
|
450
|
+
.enum(["utf8", "base64"])
|
|
451
|
+
.describe("`utf8` 텍스트 · `base64` 바이너리(이미지 등)");
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* 편집 환경 상태 — `STARTING`은 컨테이너를 띄우고 작업 트리를 복원해 dev 서버를 올리는 중이다(보통 30초 안, 최대
|
|
455
|
+
* 90초). `RUNNING`이면 미리보기와 파일 쓰기가 바로 반영된다(HMR).
|
|
456
|
+
*/
|
|
457
|
+
export const storefrontEditSessionStatusSchema = z
|
|
458
|
+
.enum(["STARTING", "RUNNING"])
|
|
459
|
+
.describe("`STARTING` 띄우는 중 · `RUNNING` 사용 중");
|
|
460
|
+
|
|
461
|
+
/** 편집 환경을 띄우는 단계 — 콘솔 진행 표시용이다. 값은 늘어날 수 있다 */
|
|
462
|
+
export const STOREFRONT_EDIT_START_STAGES = ["CONTAINER", "RESTORE", "DEV_SERVER"] as const;
|
|
463
|
+
|
|
464
|
+
export const storefrontEditSessionSchema = z.object({
|
|
465
|
+
sessionId: z.string(),
|
|
466
|
+
status: storefrontEditSessionStatusSchema,
|
|
467
|
+
startStage: z
|
|
468
|
+
.string()
|
|
469
|
+
.nullable()
|
|
470
|
+
.describe(
|
|
471
|
+
"`STARTING`일 때 지금 단계 (`CONTAINER` 컨테이너 기동 · `RESTORE` 작업 트리 복원 · `DEV_SERVER` 개발 서버 기동). 늘어날 수 있다. `RUNNING`이면 null",
|
|
472
|
+
),
|
|
473
|
+
startedAt: z.string().describe("연 시각 (ISO 8601)"),
|
|
474
|
+
lastEditedAt: z
|
|
475
|
+
.string()
|
|
476
|
+
.nullable()
|
|
477
|
+
.describe("이 편집 환경에서 마지막으로 파일을 바꾼 시각. 없으면 null"),
|
|
478
|
+
lastActiveAt: z.string().describe("마지막 활동 시각 (파일 도구·검사·미리보기)"),
|
|
479
|
+
idleExpiresAt: z
|
|
480
|
+
.string()
|
|
481
|
+
.describe(
|
|
482
|
+
`이 시각까지 활동이 없으면 잠든다 (마지막 활동 + ${STOREFRONT_EDIT_IDLE_MINUTES}분). 잠들어도 작업 트리는 보존된다`,
|
|
483
|
+
),
|
|
484
|
+
closesAt: z
|
|
485
|
+
.string()
|
|
486
|
+
.describe(
|
|
487
|
+
`이 시각까지 편집 작업(파일 쓰기·검사·버전 만들기·미리보기 링크 발급)이 없으면 서버가 편집 환경을 닫는다 (마지막 편집 작업 + ${STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES}분). 미리보기만 보고 있어도 닫힌다. 작업 트리는 보존된다`,
|
|
488
|
+
),
|
|
489
|
+
});
|
|
490
|
+
export type StorefrontEditSession = z.infer<typeof storefrontEditSessionSchema>;
|
|
491
|
+
|
|
492
|
+
/** `GET·POST·DELETE /v1/storefront/site/editing` — 편집 환경 상태 */
|
|
493
|
+
export const storefrontEditingSchema = z.object({
|
|
494
|
+
enabled: z.boolean().describe("편집을 쓸 수 있는가 (플랫폼 스위치와 Sandbox 설정)"),
|
|
495
|
+
session: storefrontEditSessionSchema
|
|
496
|
+
.nullable()
|
|
497
|
+
.describe("열린 편집 환경. 잠들었거나 닫았으면 null"),
|
|
498
|
+
capacity: z.object({
|
|
499
|
+
limit: z.number().int().describe("플랫폼 전체 동시 편집 환경 상한"),
|
|
500
|
+
inUse: z.number().int().describe("지금 열린 편집 환경 수"),
|
|
501
|
+
}),
|
|
502
|
+
preview: storefrontEditPreviewSchema.describe(
|
|
503
|
+
"편집 미리보기 주소 (사이트의 `editPreview`와 같다)",
|
|
504
|
+
),
|
|
505
|
+
workingTree: z.object({
|
|
506
|
+
modified: z
|
|
507
|
+
.boolean()
|
|
508
|
+
.describe("템플릿에서 한 번이라도 고쳤는가 (사이트 `sourceMode`가 `CUSTOM`)"),
|
|
509
|
+
lastEditedAt: z.string().nullable().describe("마지막으로 파일을 바꾼 시각"),
|
|
510
|
+
}),
|
|
511
|
+
});
|
|
512
|
+
export type StorefrontEditing = z.infer<typeof storefrontEditingSchema>;
|
|
513
|
+
|
|
514
|
+
// ─── 파일 ─────────────────────────────────────────────────
|
|
515
|
+
|
|
516
|
+
export const storefrontFileEntrySchema = z.object({
|
|
517
|
+
path: z.string(),
|
|
518
|
+
size: z.number().int().min(0).describe("바이트 수"),
|
|
519
|
+
hash: storefrontFileHashSchema,
|
|
520
|
+
});
|
|
521
|
+
export type StorefrontFileEntry = z.infer<typeof storefrontFileEntrySchema>;
|
|
522
|
+
|
|
523
|
+
/** `GET /v1/storefront/site/files?prefix=` — 작업 트리 파일 목록(편집 환경이 없어도 된다. 편집 환경을 깨우지 않는다) */
|
|
524
|
+
export const storefrontFileListSchema = z.object({
|
|
525
|
+
files: z.array(storefrontFileEntrySchema).describe("경로 사전순"),
|
|
526
|
+
totalBytes: z.number().int().min(0).describe("작업 트리 전체 바이트 수"),
|
|
527
|
+
});
|
|
528
|
+
export type StorefrontFileList = z.infer<typeof storefrontFileListSchema>;
|
|
529
|
+
|
|
530
|
+
export const storefrontFileListQuerySchema = z.object({
|
|
531
|
+
prefix: z
|
|
532
|
+
.string()
|
|
533
|
+
.max(256)
|
|
534
|
+
.optional()
|
|
535
|
+
.describe("이 접두사로 시작하는 경로만 (예: `src/routes/`)"),
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
export const storefrontFileContentQuerySchema = z.object({
|
|
539
|
+
path: z.string().describe("읽을 파일 경로"),
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
/** `GET /v1/storefront/site/files/content?path=` — 편집 환경이 없어도 된다 */
|
|
543
|
+
export const storefrontFileContentSchema = z.object({
|
|
544
|
+
path: z.string(),
|
|
545
|
+
content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
|
|
546
|
+
encoding: storefrontFileEncodingSchema,
|
|
547
|
+
hash: storefrontFileHashSchema,
|
|
548
|
+
size: z.number().int().min(0),
|
|
549
|
+
});
|
|
550
|
+
export type StorefrontFileContent = z.infer<typeof storefrontFileContentSchema>;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* `POST /v1/storefront/site/files/batch` — 여러 파일을 한 번에 쓴다. 편집 환경이 없어도 된다(잠든 작업 트리에 바로 쓰고,
|
|
554
|
+
* 편집 환경이 떠 있으면 미리보기에 곧바로 반영된다). `expectedHash`: 생략하면 조건 없이 쓴다 · `null`이면 파일이 없어야 한다(새 파일) · 값이면 지금
|
|
555
|
+
* 해시가 같아야 한다. 조건을 **모두** 확인한 뒤 쓰고, 하나라도 어긋나면 아무것도 쓰지 않는다(409 `FILE_CHANGED`,
|
|
556
|
+
* `error.details.conflicts`).
|
|
557
|
+
*/
|
|
558
|
+
export const writeStorefrontFilesRequestSchema = z.object({
|
|
559
|
+
files: z
|
|
560
|
+
.array(
|
|
561
|
+
z.object({
|
|
562
|
+
path: storefrontFilePathSchema,
|
|
563
|
+
content: z.string().describe("내용 (`encoding`에 따라 텍스트 또는 base64)"),
|
|
564
|
+
encoding: storefrontFileEncodingSchema.optional().describe("기본 `utf8`"),
|
|
565
|
+
expectedHash: storefrontFileHashSchema
|
|
566
|
+
.nullable()
|
|
567
|
+
.optional()
|
|
568
|
+
.describe("쓰기 조건. 생략 = 조건 없음 · null = 새 파일 · 값 = 지금 해시가 같을 때만"),
|
|
569
|
+
}),
|
|
570
|
+
)
|
|
571
|
+
.min(1)
|
|
572
|
+
.max(STOREFRONT_FILE_BATCH_MAX)
|
|
573
|
+
.refine((files) => new Set(files.map((file) => file.path)).size === files.length, {
|
|
574
|
+
message: "같은 경로를 두 번 실을 수 없습니다",
|
|
575
|
+
}),
|
|
576
|
+
});
|
|
577
|
+
export type WriteStorefrontFilesRequest = z.infer<typeof writeStorefrontFilesRequestSchema>;
|
|
578
|
+
|
|
579
|
+
export const storefrontFileWriteResultSchema = z.object({
|
|
580
|
+
files: z.array(storefrontFileEntrySchema).describe("쓴 파일의 새 해시·크기 (요청 순서)"),
|
|
581
|
+
});
|
|
582
|
+
export type StorefrontFileWriteResult = z.infer<typeof storefrontFileWriteResultSchema>;
|
|
583
|
+
|
|
584
|
+
/** 409 `FILE_CHANGED`의 `error.details` */
|
|
585
|
+
export const storefrontFileConflictDetailsSchema = z.object({
|
|
586
|
+
conflicts: z.array(
|
|
587
|
+
z.object({
|
|
588
|
+
path: z.string(),
|
|
589
|
+
currentHash: storefrontFileHashSchema.nullable().describe("지금 해시. 파일이 없으면 null"),
|
|
590
|
+
}),
|
|
591
|
+
),
|
|
592
|
+
});
|
|
593
|
+
export type StorefrontFileConflictDetails = z.infer<typeof storefrontFileConflictDetailsSchema>;
|
|
594
|
+
|
|
595
|
+
/** `POST /v1/storefront/site/files/delete` — 없는 파일이면 `deleted: false`(조건을 주면 409 `FILE_CHANGED`) */
|
|
596
|
+
export const deleteStorefrontFileRequestSchema = z.object({
|
|
597
|
+
path: storefrontFilePathSchema,
|
|
598
|
+
expectedHash: storefrontFileHashSchema.optional().describe("주면 지금 해시가 같을 때만 지운다"),
|
|
599
|
+
});
|
|
600
|
+
export type DeleteStorefrontFileRequest = z.infer<typeof deleteStorefrontFileRequestSchema>;
|
|
601
|
+
|
|
602
|
+
export const storefrontFileDeleteResultSchema = z.object({
|
|
603
|
+
path: z.string(),
|
|
604
|
+
deleted: z.boolean(),
|
|
605
|
+
});
|
|
606
|
+
export type StorefrontFileDeleteResult = z.infer<typeof storefrontFileDeleteResultSchema>;
|
|
607
|
+
|
|
608
|
+
/** `POST /v1/storefront/site/files/move` — 이름 바꾸기·옮기기 */
|
|
609
|
+
export const moveStorefrontFileRequestSchema = z.object({
|
|
610
|
+
from: storefrontFilePathSchema,
|
|
611
|
+
to: storefrontFilePathSchema,
|
|
612
|
+
expectedHash: storefrontFileHashSchema.optional().describe("주면 `from`의 지금 해시가 같을 때만"),
|
|
613
|
+
overwrite: z
|
|
614
|
+
.boolean()
|
|
615
|
+
.optional()
|
|
616
|
+
.describe("`to`에 파일이 있으면 덮는가 (기본 false → 409 `FILE_EXISTS`)"),
|
|
617
|
+
});
|
|
618
|
+
export type MoveStorefrontFileRequest = z.infer<typeof moveStorefrontFileRequestSchema>;
|
|
619
|
+
|
|
620
|
+
export const storefrontFileMoveResultSchema = z.object({
|
|
621
|
+
from: z.string(),
|
|
622
|
+
to: z.string(),
|
|
623
|
+
hash: storefrontFileHashSchema,
|
|
624
|
+
size: z.number().int().min(0),
|
|
625
|
+
});
|
|
626
|
+
export type StorefrontFileMoveResult = z.infer<typeof storefrontFileMoveResultSchema>;
|
|
627
|
+
|
|
628
|
+
/** `POST /v1/storefront/site/files/search` — 텍스트 파일 내용 검색(편집 환경이 없어도 된다) */
|
|
629
|
+
/** 정규식 검색의 패턴 길이 상한 — 편집 Sandbox가 실행 비용을 막으려고 더 좁게 받는다(sandbox-api §4.6) */
|
|
630
|
+
export const STOREFRONT_SEARCH_REGEX_MAX_LENGTH = 100;
|
|
631
|
+
|
|
632
|
+
export const searchStorefrontFilesRequestSchema = z
|
|
633
|
+
.object({
|
|
634
|
+
query: z.string().min(1).max(200),
|
|
635
|
+
regex: z
|
|
636
|
+
.boolean()
|
|
637
|
+
.optional()
|
|
638
|
+
.describe(
|
|
639
|
+
"`query`를 정규식으로 본다 (기본 false = 글자 그대로). 정규식은 100자까지이고 중첩 수량자(`(a+)+`)·반복되는 선택(`(a|b)+`)·역참조·뒤 보기, 가변 수량자(`*`·`+`·`?`·`{n,m}`, 넓은 범위는 2로 센다) 차수 4 초과는 400 `INVALID_REQUEST`다. 차수가 클수록 긴 줄은 건너뛰고 비용이 크면 `truncated`로 끊는다",
|
|
640
|
+
),
|
|
641
|
+
caseSensitive: z.boolean().optional().describe("대소문자를 가리는가 (기본 false)"),
|
|
642
|
+
pathPrefix: z.string().max(256).optional(),
|
|
643
|
+
maxResults: z.number().int().min(1).max(200).optional().describe("기본 50"),
|
|
644
|
+
})
|
|
645
|
+
.refine((body) => !body.regex || body.query.length <= STOREFRONT_SEARCH_REGEX_MAX_LENGTH, {
|
|
646
|
+
message: `정규식은 ${STOREFRONT_SEARCH_REGEX_MAX_LENGTH}자까지입니다`,
|
|
647
|
+
path: ["query"],
|
|
648
|
+
});
|
|
649
|
+
export type SearchStorefrontFilesRequest = z.infer<typeof searchStorefrontFilesRequestSchema>;
|
|
650
|
+
|
|
651
|
+
export const storefrontFileSearchResultSchema = z.object({
|
|
652
|
+
matches: z.array(
|
|
653
|
+
z.object({
|
|
654
|
+
path: z.string(),
|
|
655
|
+
line: z.number().int().min(1),
|
|
656
|
+
column: z.number().int().min(1).optional(),
|
|
657
|
+
text: z.string().describe("일치한 줄 (일치 앞뒤 200자까지)"),
|
|
658
|
+
}),
|
|
659
|
+
),
|
|
660
|
+
truncated: z.boolean().describe("`maxResults`에서 끊었는가"),
|
|
661
|
+
});
|
|
662
|
+
export type StorefrontFileSearchResult = z.infer<typeof storefrontFileSearchResultSchema>;
|
|
663
|
+
|
|
664
|
+
// ─── 검사 ─────────────────────────────────────────────────
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* `POST /v1/storefront/site/checks` (202) — 작업 트리에 명령 하나를 돌린다(`typecheck` 타입 검사 · `lint` · `test`). 편집 환경이
|
|
668
|
+
* 없어도 된다(일회용 검사 컨테이너에서 돈다). 검사는 30초를 넘길 수 있어 **항상 202로 검사 id만 주고** 결과는
|
|
669
|
+
* `GET /v1/storefront/site/checks/{checkId}`로 조회한다(권장 2~3초 간격). 사이트당 끝나지 않은 검사는 하나다 — 같은 종류가 돌고
|
|
670
|
+
* 있으면 그 검사를 다시 돌려주고, 다른 종류면 409 `CHECK_RUNNING`(`error.details.checkId`)이다. 출력은 꼬리만 준다.
|
|
671
|
+
*/
|
|
672
|
+
export const runStorefrontCheckRequestSchema = z.object({
|
|
673
|
+
check: z.enum(["typecheck", "lint", "test"]),
|
|
674
|
+
});
|
|
675
|
+
export type RunStorefrontCheckRequest = z.infer<typeof runStorefrontCheckRequestSchema>;
|
|
676
|
+
|
|
677
|
+
export const storefrontCheckStatusSchema = z
|
|
678
|
+
.enum(["QUEUED", "RUNNING", "PASSED", "FAILED", "TIMED_OUT", "UNKNOWN"])
|
|
679
|
+
.describe(
|
|
680
|
+
"`QUEUED` 대기 · `RUNNING` 실행 중 · `PASSED` 통과 · `FAILED` 실패(명령 실패 또는 `errorCode`) · `TIMED_OUT` 마감(150초)에 걸려 끊김 · `UNKNOWN` 결과를 모른다(다시 실행하지 않는다, 새 검사를 요청한다)",
|
|
681
|
+
);
|
|
682
|
+
export type StorefrontCheckStatus = z.infer<typeof storefrontCheckStatusSchema>;
|
|
683
|
+
|
|
684
|
+
/** 끝난 검사 상태 */
|
|
685
|
+
export const STOREFRONT_CHECK_TERMINAL_STATUSES: readonly StorefrontCheckStatus[] = [
|
|
686
|
+
"PASSED",
|
|
687
|
+
"FAILED",
|
|
688
|
+
"TIMED_OUT",
|
|
689
|
+
"UNKNOWN",
|
|
690
|
+
];
|
|
691
|
+
|
|
692
|
+
export const storefrontCheckSchema = z.object({
|
|
693
|
+
checkId: z.string(),
|
|
694
|
+
check: z.string().describe("`typecheck`·`lint`·`test`"),
|
|
695
|
+
status: storefrontCheckStatusSchema,
|
|
696
|
+
exitCode: z.number().int().nullable().describe("명령 종료 코드. 명령이 끝나지 않았으면 null"),
|
|
697
|
+
durationMs: z.number().int().min(0).nullable().describe("명령 시간. 끝나지 않았으면 null"),
|
|
698
|
+
outputTail: z.string().nullable().describe("출력 꼬리 (최대 16KiB). 끝나지 않았으면 null"),
|
|
699
|
+
errorCode: z
|
|
700
|
+
.string()
|
|
701
|
+
.nullable()
|
|
702
|
+
.describe(
|
|
703
|
+
"명령 밖의 실패 이유 (`CHECK_BUSY`·`SANDBOX_UNAVAILABLE`·`RESULT_UNKNOWN`, 늘어날 수 있다). 명령 결과로 끝났으면 null",
|
|
704
|
+
),
|
|
705
|
+
message: z.string().nullable().describe("`errorCode`가 있을 때 사용자용 안내"),
|
|
706
|
+
createdAt: z.string(),
|
|
707
|
+
startedAt: z.string().nullable(),
|
|
708
|
+
finishedAt: z.string().nullable(),
|
|
709
|
+
});
|
|
710
|
+
export type StorefrontCheck = z.infer<typeof storefrontCheckSchema>;
|
|
711
|
+
|
|
712
|
+
/** 규칙 심각도 — `error`는 버전 발행을 막는다(빌드 `VERIFY` 실패), `warning`은 알리기만 한다. 없으면 `error`로 본다 */
|
|
713
|
+
export const storefrontRuleSeveritySchema = z.enum(["error", "warning"]);
|
|
714
|
+
export type StorefrontRuleSeverity = z.infer<typeof storefrontRuleSeveritySchema>;
|
|
715
|
+
|
|
716
|
+
/** 규칙 하나의 검사 결과 — MCP 계측(`mcpRuleResultSchema`)과 같은 모양에 심각도를 더한다 */
|
|
717
|
+
export const storefrontRuleResultSchema = z.object({
|
|
718
|
+
ruleId: z.string(),
|
|
719
|
+
status: z.enum(["pass", "violation"]),
|
|
720
|
+
count: z.number().int().min(0),
|
|
721
|
+
severity: storefrontRuleSeveritySchema.optional(),
|
|
722
|
+
});
|
|
723
|
+
export type StorefrontRuleResult = z.infer<typeof storefrontRuleResultSchema>;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* 규칙 검증 보고서 — `@sayren/mcp`의 `verify_storefront`와 같은 모양이다(원천 `apps/mcp/src/verify.ts`). 위반마다
|
|
727
|
+
* 고칠 자리(`file`·`line`)와 방법(`fix`·`example`)을 싣는다. `POST /v1/storefront/site/verify`(편집 환경이 잠들어 있어도
|
|
728
|
+
* 된다)와 빌드의 `verify`가 이 모양이다.
|
|
729
|
+
*/
|
|
730
|
+
export const storefrontVerifyReportSchema = z.object({
|
|
731
|
+
ok: z.boolean().describe("차단(`error`) 위반이 없는가"),
|
|
732
|
+
checkedFiles: z.number().int().min(0),
|
|
733
|
+
summary: z.object({
|
|
734
|
+
rules: z.number().int(),
|
|
735
|
+
passed: z.number().int(),
|
|
736
|
+
violated: z.number().int(),
|
|
737
|
+
findings: z.number().int(),
|
|
738
|
+
}),
|
|
739
|
+
results: z.array(storefrontRuleResultSchema),
|
|
740
|
+
findings: z.array(
|
|
741
|
+
z.object({
|
|
742
|
+
ruleId: z.string(),
|
|
743
|
+
rule: z.string(),
|
|
744
|
+
file: z.string(),
|
|
745
|
+
line: z.number().int().optional(),
|
|
746
|
+
detail: z.string(),
|
|
747
|
+
cause: z.string(),
|
|
748
|
+
fix: z.string(),
|
|
749
|
+
example: z.string(),
|
|
750
|
+
doc: z.string(),
|
|
751
|
+
docUrl: z.string(),
|
|
752
|
+
templateFile: z.string().optional(),
|
|
753
|
+
severity: storefrontRuleSeveritySchema.optional(),
|
|
754
|
+
}),
|
|
755
|
+
),
|
|
756
|
+
message: z.string(),
|
|
757
|
+
nextSteps: z.array(z.string()),
|
|
758
|
+
});
|
|
759
|
+
export type StorefrontVerifyReport = z.infer<typeof storefrontVerifyReportSchema>;
|
|
760
|
+
|
|
761
|
+
// ─── 빌드 ─────────────────────────────────────────────────
|
|
762
|
+
|
|
763
|
+
export const storefrontBuildStatusSchema = z
|
|
764
|
+
.enum(["QUEUED", "RUNNING", "SUCCEEDED", "FAILED", "TIMED_OUT"])
|
|
765
|
+
.describe(
|
|
766
|
+
"`QUEUED` 대기 · `RUNNING` 빌드 중 · `SUCCEEDED` 성공(버전 `READY`) · `FAILED` 실패 · `TIMED_OUT` 시간 초과(10분)",
|
|
767
|
+
);
|
|
768
|
+
export type StorefrontBuildStatus = z.infer<typeof storefrontBuildStatusSchema>;
|
|
769
|
+
|
|
770
|
+
/** 끝난 빌드 상태 */
|
|
771
|
+
export const STOREFRONT_BUILD_TERMINAL_STATUSES: readonly StorefrontBuildStatus[] = [
|
|
772
|
+
"SUCCEEDED",
|
|
773
|
+
"FAILED",
|
|
774
|
+
"TIMED_OUT",
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* 빌드 단계 — 이 순서로 돈다. 첫 실패에서 멈추지만 규칙 검증(`VERIFY`)은 실패해도 타입 검사까지 이어서 해 한 번에 알려 준다.
|
|
779
|
+
* `UPLOAD`는 사이트 버전 Worker 업로드다(성공하면 발행할 수 있다). 값은 늘어날 수 있다.
|
|
780
|
+
*/
|
|
781
|
+
export const STOREFRONT_BUILD_STAGES = [
|
|
782
|
+
"VERIFY",
|
|
783
|
+
"RESTORE",
|
|
784
|
+
"INSTALL",
|
|
785
|
+
"TYPECHECK",
|
|
786
|
+
"BUILD",
|
|
787
|
+
"BUNDLE",
|
|
788
|
+
"PACK",
|
|
789
|
+
"UPLOAD",
|
|
790
|
+
] as const;
|
|
791
|
+
|
|
792
|
+
export const storefrontBuildSchema = z.object({
|
|
793
|
+
buildId: z.string(),
|
|
794
|
+
versionId: z.string(),
|
|
795
|
+
versionNumber: z.number().int().min(1),
|
|
796
|
+
status: storefrontBuildStatusSchema,
|
|
797
|
+
failureStage: z
|
|
798
|
+
.string()
|
|
799
|
+
.nullable()
|
|
800
|
+
.describe(
|
|
801
|
+
"실패한 단계 (`VERIFY`·`RESTORE`·`INSTALL`·`TYPECHECK`·`BUILD`·`BUNDLE`·`PACK`·`UPLOAD`, 늘어날 수 있다). 실패가 아니면 null",
|
|
802
|
+
),
|
|
803
|
+
errorCode: z
|
|
804
|
+
.string()
|
|
805
|
+
.nullable()
|
|
806
|
+
.describe(
|
|
807
|
+
"실패 코드 (`RULES_VIOLATED`·`INSTALL_FAILED`·`TYPECHECK_FAILED`·`BUILD_FAILED`·`BUNDLE_FAILED`·`ARTIFACT_INVALID`·`UPLOAD_FAILED`·`SOURCE_NOT_FOUND`·`CAPACITY_EXHAUSTED`·`INTERNAL`, 늘어날 수 있다). 실패가 아니면 null",
|
|
808
|
+
),
|
|
809
|
+
logTail: z.string().nullable().describe("실패 단계 출력 꼬리 (최대 8KB). 없으면 null"),
|
|
810
|
+
stages: z
|
|
811
|
+
.array(
|
|
812
|
+
z.object({
|
|
813
|
+
stage: z.string(),
|
|
814
|
+
ok: z.boolean(),
|
|
815
|
+
durationMs: z.number().int().min(0),
|
|
816
|
+
}),
|
|
817
|
+
)
|
|
818
|
+
.describe("끝난 단계 (도는 순서)"),
|
|
819
|
+
verify: storefrontVerifyReportSchema
|
|
820
|
+
.nullable()
|
|
821
|
+
.describe("규칙 검증 보고서(위반 상세는 50건까지). 그 단계까지 가지 못했으면 null"),
|
|
822
|
+
queuedAt: z.string(),
|
|
823
|
+
startedAt: z.string().nullable(),
|
|
824
|
+
finishedAt: z.string().nullable(),
|
|
825
|
+
durationMs: z.number().int().nullable().describe("빌드 시간 (시작 → 끝)"),
|
|
826
|
+
});
|
|
827
|
+
export type StorefrontBuild = z.infer<typeof storefrontBuildSchema>;
|
|
828
|
+
|
|
829
|
+
export const storefrontBuildListQuerySchema = z.object({
|
|
830
|
+
limit: z.coerce.number().int().min(1).max(50).optional().describe("기본 20, 최신 먼저"),
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* `POST /v1/storefront/site/versions` — 지금 작업 트리를 고정해 버전을 만든다(빌드 시작). 사이트당 끝나지 않은 빌드는
|
|
835
|
+
* 하나다(409 `STOREFRONT_BUILD_IN_PROGRESS`). 편집 환경이 없어도 된다.
|
|
836
|
+
*/
|
|
837
|
+
export const createStorefrontVersionRequestSchema = z.object({
|
|
838
|
+
message: z.string().max(200).optional().describe("버전 메모 (에이전트는 바꾼 내용 요약)"),
|
|
839
|
+
});
|
|
840
|
+
export type CreateStorefrontVersionRequest = z.infer<typeof createStorefrontVersionRequestSchema>;
|
|
841
|
+
|
|
842
|
+
/** 버전 만들기 응답 (201) — 버전은 `BUILDING`, 빌드는 `QUEUED`로 시작한다. 결과는 `GET builds/{buildId}`로 본다 */
|
|
843
|
+
export const storefrontVersionCreatedSchema = z.object({
|
|
844
|
+
version: storefrontVersionSchema,
|
|
845
|
+
build: storefrontBuildSchema,
|
|
846
|
+
});
|
|
847
|
+
export type StorefrontVersionCreated = z.infer<typeof storefrontVersionCreatedSchema>;
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크. 콘솔에서 로그인한 OWNER·ADMIN만
|
|
851
|
+
* 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
|
|
852
|
+
* 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
|
|
853
|
+
*/
|
|
854
|
+
export const createStorefrontEditPreviewLinkRequestSchema =
|
|
855
|
+
createStorefrontPreviewLinkRequestSchema;
|
|
856
|
+
export type CreateStorefrontEditPreviewLinkRequest = CreateStorefrontPreviewLinkRequest;
|