sayren 0.13.0 → 0.14.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/boilerplate/.boilerplate-meta.json +44 -0
- package/dist/boilerplate/.claude/skills/sayren/SKILL.md +18 -0
- package/dist/boilerplate/.mcp.json +8 -0
- package/dist/boilerplate/CLAUDE.md +26 -0
- package/dist/boilerplate/README.md +12 -0
- package/dist/boilerplate/_gitignore +8 -0
- package/dist/boilerplate/biome.json +45 -0
- package/dist/boilerplate/messages/en.json +9 -0
- package/dist/boilerplate/messages/ko.json +9 -0
- package/dist/boilerplate/package.json +46 -0
- package/dist/boilerplate/public/_headers +5 -0
- package/dist/boilerplate/public/favicon.svg +1 -0
- package/dist/boilerplate/src/hooks/use-catalog.ts +28 -0
- package/dist/boilerplate/src/i18n/index.ts +99 -0
- package/dist/boilerplate/src/i18n/locale.server.ts +10 -0
- package/dist/boilerplate/src/lib/analytics.ts +60 -0
- package/dist/boilerplate/src/lib/api.server.ts +27 -0
- package/dist/boilerplate/src/lib/api.ts +7 -0
- package/dist/boilerplate/src/lib/config.server.ts +23 -0
- package/dist/boilerplate/src/lib/config.test.ts +16 -0
- package/dist/boilerplate/src/lib/config.ts +19 -0
- package/dist/boilerplate/src/lib/cookies.server.ts +35 -0
- package/dist/boilerplate/src/lib/format.ts +19 -0
- package/dist/boilerplate/src/lib/queries.ts +61 -0
- package/dist/boilerplate/src/routeTree.gen.ts +69 -0
- package/dist/boilerplate/src/router.tsx +44 -0
- package/dist/boilerplate/src/routes/__root.tsx +119 -0
- package/dist/boilerplate/src/routes/index.tsx +52 -0
- package/dist/boilerplate/src/start.ts +20 -0
- package/dist/boilerplate/src/styles.css +29 -0
- package/dist/boilerplate/src/theme.css +38 -0
- package/dist/boilerplate/tsconfig.json +16 -0
- package/dist/boilerplate/vite.config.ts +12 -0
- package/dist/boilerplate/vitest.config.ts +5 -0
- package/dist/index.mjs +353 -10
- package/package.json +6 -5
package/dist/index.mjs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { stdin, stdout } from "node:process";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
|
-
import { APP_DEPLOY_KEY_PATTERN, APP_README_MAX_BYTES, APP_SECRET_PATTERNS, APP_SERVER_BUNDLE_MAX_GZIP_BYTES, APP_UI_MAX_BYTES, APP_UI_MAX_FILES, ApiError, ApprovalPendingError, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_SOURCE_BLOB_BATCH_MAX, appIconProblem, appManifestProblems, appManifestSchema, createStoreClient, normalizeStorefrontPath } from "@sayren/store-sdk";
|
|
5
|
+
import { APP_DEPLOY_KEY_PATTERN, APP_README_MAX_BYTES, APP_SECRET_PATTERNS, APP_SERVER_BUNDLE_MAX_GZIP_BYTES, APP_UI_MAX_BYTES, APP_UI_MAX_FILES, ApiError, ApprovalPendingError, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_SOURCE_BLOB_BATCH_MAX, STORE_CODE_PATTERN, appIconProblem, appManifestProblems, appManifestSchema, createStoreClient, normalizeStorefrontPath } from "@sayren/store-sdk";
|
|
6
6
|
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { createServer } from "node:http";
|
|
8
8
|
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
9
|
-
import { pathToFileURL } from "node:url";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
10
|
import { gzipSync } from "node:zlib";
|
|
11
11
|
import { createHash, randomBytes } from "node:crypto";
|
|
12
12
|
|
|
@@ -86,7 +86,7 @@ function isAccountToken(token) {
|
|
|
86
86
|
return accountTokenClaimsOf(token) !== null;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
|
-
* 폐기된 옛 상점 API 토큰(`sy_pat_…`) — #71 R1 뒤 api가 받지 않는다(401
|
|
89
|
+
* 폐기된 옛 상점 API 토큰(`sy_pat_…`) — #71 R1 뒤 api가 받지 않는다(일반 401, #157). 받는 쪽은 api에 보내지 않고
|
|
90
90
|
* 계정 액세스 토큰을 만들라고 바로 안내한다.
|
|
91
91
|
*/
|
|
92
92
|
function isRetiredStorePat(token) {
|
|
@@ -2751,6 +2751,42 @@ const mcp_rule_tenant_header_why = ((inputs = {}, options = {}) => {
|
|
|
2751
2751
|
return ko_mcp_rule_tenant_header_why(inputs);
|
|
2752
2752
|
});
|
|
2753
2753
|
|
|
2754
|
+
//#endregion
|
|
2755
|
+
//#region ../mcp/src/paraglide/messages/mcp_template_catalog_version_missing.js
|
|
2756
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
2757
|
+
/** @typedef {{ name: NonNullable<unknown> }} Mcp_Template_Catalog_Version_MissingInputs */
|
|
2758
|
+
const ko_mcp_template_catalog_version_missing = (i) => {
|
|
2759
|
+
return `catalog에 ${i?.name} 버전이 없습니다`;
|
|
2760
|
+
};
|
|
2761
|
+
/**
|
|
2762
|
+
|
|
2763
|
+
* @param {Mcp_Template_Catalog_Version_MissingInputs} inputs
|
|
2764
|
+
* @param {{ locale?: "ko" }} options
|
|
2765
|
+
* @returns {LocalizedString}
|
|
2766
|
+
*/
|
|
2767
|
+
const mcp_template_catalog_version_missing = ((inputs, options = {}) => {
|
|
2768
|
+
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
2769
|
+
return ko_mcp_template_catalog_version_missing(inputs);
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
//#endregion
|
|
2773
|
+
//#region ../mcp/src/paraglide/messages/mcp_template_published_version_unknown.js
|
|
2774
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
2775
|
+
/** @typedef {{ name: NonNullable<unknown> }} Mcp_Template_Published_Version_UnknownInputs */
|
|
2776
|
+
const ko_mcp_template_published_version_unknown = (i) => {
|
|
2777
|
+
return `${i?.name}의 배포 버전을 모르겠습니다`;
|
|
2778
|
+
};
|
|
2779
|
+
/**
|
|
2780
|
+
|
|
2781
|
+
* @param {Mcp_Template_Published_Version_UnknownInputs} inputs
|
|
2782
|
+
* @param {{ locale?: "ko" }} options
|
|
2783
|
+
* @returns {LocalizedString}
|
|
2784
|
+
*/
|
|
2785
|
+
const mcp_template_published_version_unknown = ((inputs, options = {}) => {
|
|
2786
|
+
experimentalStaticLocale$1 ?? options.locale ?? getLocale$1();
|
|
2787
|
+
return ko_mcp_template_published_version_unknown(inputs);
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2754
2790
|
//#endregion
|
|
2755
2791
|
//#region ../mcp/src/paraglide/messages/mcp_verify_analytics_ids.js
|
|
2756
2792
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -5529,6 +5565,136 @@ const cli_continue_confirm = ((inputs = {}, options = {}) => {
|
|
|
5529
5565
|
return ko_cli_continue_confirm(inputs);
|
|
5530
5566
|
});
|
|
5531
5567
|
|
|
5568
|
+
//#endregion
|
|
5569
|
+
//#region src/paraglide/messages/cli_create_dir_not_empty.js
|
|
5570
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5571
|
+
/** @typedef {{ dir: NonNullable<unknown> }} Cli_Create_Dir_Not_EmptyInputs */
|
|
5572
|
+
const ko_cli_create_dir_not_empty = (i) => {
|
|
5573
|
+
return `폴더가 비어 있지 않습니다: ${i?.dir}. 새 폴더 이름을 주십시오`;
|
|
5574
|
+
};
|
|
5575
|
+
/**
|
|
5576
|
+
|
|
5577
|
+
* @param {Cli_Create_Dir_Not_EmptyInputs} inputs
|
|
5578
|
+
* @param {{ locale?: "ko" }} options
|
|
5579
|
+
* @returns {LocalizedString}
|
|
5580
|
+
*/
|
|
5581
|
+
const cli_create_dir_not_empty = ((inputs, options = {}) => {
|
|
5582
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5583
|
+
return ko_cli_create_dir_not_empty(inputs);
|
|
5584
|
+
});
|
|
5585
|
+
|
|
5586
|
+
//#endregion
|
|
5587
|
+
//#region src/paraglide/messages/cli_create_done.js
|
|
5588
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5589
|
+
/** @typedef {{ dir: NonNullable<unknown>, count: NonNullable<unknown>, storeCode: NonNullable<unknown> }} Cli_Create_DoneInputs */
|
|
5590
|
+
const ko_cli_create_done = (i) => {
|
|
5591
|
+
return `보일러플레이트를 만들었습니다 — ${i?.dir} (파일 ${i?.count}개, .env에 상점 ${i?.storeCode} 연결 값)
|
|
5592
|
+
다음:
|
|
5593
|
+
cd ${i?.dir}
|
|
5594
|
+
npm install
|
|
5595
|
+
npm run dev`;
|
|
5596
|
+
};
|
|
5597
|
+
/**
|
|
5598
|
+
|
|
5599
|
+
* @param {Cli_Create_DoneInputs} inputs
|
|
5600
|
+
* @param {{ locale?: "ko" }} options
|
|
5601
|
+
* @returns {LocalizedString}
|
|
5602
|
+
*/
|
|
5603
|
+
const cli_create_done = ((inputs, options = {}) => {
|
|
5604
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5605
|
+
return ko_cli_create_done(inputs);
|
|
5606
|
+
});
|
|
5607
|
+
|
|
5608
|
+
//#endregion
|
|
5609
|
+
//#region src/paraglide/messages/cli_create_name_invalid.js
|
|
5610
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5611
|
+
/** @typedef {{ name: NonNullable<unknown> }} Cli_Create_Name_InvalidInputs */
|
|
5612
|
+
const ko_cli_create_name_invalid = (i) => {
|
|
5613
|
+
return `폴더 이름은 영문 소문자·숫자·.·_·-로 쓰고 영문 소문자나 숫자로 시작합니다: ${i?.name}`;
|
|
5614
|
+
};
|
|
5615
|
+
/**
|
|
5616
|
+
|
|
5617
|
+
* @param {Cli_Create_Name_InvalidInputs} inputs
|
|
5618
|
+
* @param {{ locale?: "ko" }} options
|
|
5619
|
+
* @returns {LocalizedString}
|
|
5620
|
+
*/
|
|
5621
|
+
const cli_create_name_invalid = ((inputs, options = {}) => {
|
|
5622
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5623
|
+
return ko_cli_create_name_invalid(inputs);
|
|
5624
|
+
});
|
|
5625
|
+
|
|
5626
|
+
//#endregion
|
|
5627
|
+
//#region src/paraglide/messages/cli_create_prompt_store_code.js
|
|
5628
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5629
|
+
/** @typedef {{}} Cli_Create_Prompt_Store_CodeInputs */
|
|
5630
|
+
const ko_cli_create_prompt_store_code = () => {
|
|
5631
|
+
return `상점 코드(상점 플랫폼 › 설정 › 상점 정보): `;
|
|
5632
|
+
};
|
|
5633
|
+
/**
|
|
5634
|
+
|
|
5635
|
+
* @param {Cli_Create_Prompt_Store_CodeInputs} inputs
|
|
5636
|
+
* @param {{ locale?: "ko" }} options
|
|
5637
|
+
* @returns {LocalizedString}
|
|
5638
|
+
*/
|
|
5639
|
+
const cli_create_prompt_store_code = ((inputs = {}, options = {}) => {
|
|
5640
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5641
|
+
return ko_cli_create_prompt_store_code(inputs);
|
|
5642
|
+
});
|
|
5643
|
+
|
|
5644
|
+
//#endregion
|
|
5645
|
+
//#region src/paraglide/messages/cli_create_store_code_invalid.js
|
|
5646
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5647
|
+
/** @typedef {{ storeCode: NonNullable<unknown> }} Cli_Create_Store_Code_InvalidInputs */
|
|
5648
|
+
const ko_cli_create_store_code_invalid = (i) => {
|
|
5649
|
+
return `상점 코드 형식이 아닙니다: ${i?.storeCode}. 영문 소문자·숫자·-로 3~30자입니다`;
|
|
5650
|
+
};
|
|
5651
|
+
/**
|
|
5652
|
+
|
|
5653
|
+
* @param {Cli_Create_Store_Code_InvalidInputs} inputs
|
|
5654
|
+
* @param {{ locale?: "ko" }} options
|
|
5655
|
+
* @returns {LocalizedString}
|
|
5656
|
+
*/
|
|
5657
|
+
const cli_create_store_code_invalid = ((inputs, options = {}) => {
|
|
5658
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5659
|
+
return ko_cli_create_store_code_invalid(inputs);
|
|
5660
|
+
});
|
|
5661
|
+
|
|
5662
|
+
//#endregion
|
|
5663
|
+
//#region src/paraglide/messages/cli_create_store_code_required.js
|
|
5664
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5665
|
+
/** @typedef {{}} Cli_Create_Store_Code_RequiredInputs */
|
|
5666
|
+
const ko_cli_create_store_code_required = () => {
|
|
5667
|
+
return `상점 코드를 받지 못했습니다. --store-code <상점 코드>로 주십시오(비대화형은 필수입니다)`;
|
|
5668
|
+
};
|
|
5669
|
+
/**
|
|
5670
|
+
|
|
5671
|
+
* @param {Cli_Create_Store_Code_RequiredInputs} inputs
|
|
5672
|
+
* @param {{ locale?: "ko" }} options
|
|
5673
|
+
* @returns {LocalizedString}
|
|
5674
|
+
*/
|
|
5675
|
+
const cli_create_store_code_required = ((inputs = {}, options = {}) => {
|
|
5676
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5677
|
+
return ko_cli_create_store_code_required(inputs);
|
|
5678
|
+
});
|
|
5679
|
+
|
|
5680
|
+
//#endregion
|
|
5681
|
+
//#region src/paraglide/messages/cli_create_usage.js
|
|
5682
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5683
|
+
/** @typedef {{}} Cli_Create_UsageInputs */
|
|
5684
|
+
const ko_cli_create_usage = () => {
|
|
5685
|
+
return `폴더 이름을 주십시오: npx sayren create <이름> [--store-code <상점 코드>]`;
|
|
5686
|
+
};
|
|
5687
|
+
/**
|
|
5688
|
+
|
|
5689
|
+
* @param {Cli_Create_UsageInputs} inputs
|
|
5690
|
+
* @param {{ locale?: "ko" }} options
|
|
5691
|
+
* @returns {LocalizedString}
|
|
5692
|
+
*/
|
|
5693
|
+
const cli_create_usage = ((inputs = {}, options = {}) => {
|
|
5694
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5695
|
+
return ko_cli_create_usage(inputs);
|
|
5696
|
+
});
|
|
5697
|
+
|
|
5532
5698
|
//#endregion
|
|
5533
5699
|
//#region src/paraglide/messages/cli_credentials_git_tracked.js
|
|
5534
5700
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -8835,6 +9001,7 @@ const ko_cli_usage = (i) => {
|
|
|
8835
9001
|
npx sayren login --paste # 액세스 토큰을 입력받아 저장(셸 기록에 남지 않습니다)
|
|
8836
9002
|
npx sayren logout
|
|
8837
9003
|
npx sayren whoami
|
|
9004
|
+
npx sayren create <이름> [--store-code <상점 코드>] # 스토어프론트 보일러플레이트를 새 폴더에 만듭니다(로그인 없음)
|
|
8838
9005
|
npx sayren init [폴더] [--template <id>] [--store <storeId>] [--yes]
|
|
8839
9006
|
npx sayren pull --project <storeId> [폴더] [--force [--yes]]
|
|
8840
9007
|
npx sayren deploy [--no-publish] [--skip-build] [--message <메모>] [--force [--yes]]
|
|
@@ -8847,6 +9014,7 @@ const ko_cli_usage = (i) => {
|
|
|
8847
9014
|
login Sayren 계정으로 로그인합니다. 로그인 토큰은 사용자 설정 폴더(~/.config/sayren/credentials.json)에 둡니다
|
|
8848
9015
|
logout 로그인을 폐기하고 이 컴퓨터의 토큰을 지웁니다
|
|
8849
9016
|
whoami 쓰는 계정·토큰 종류·상점 범위
|
|
9017
|
+
create 스토어프론트 보일러플레이트(상점 이름·상품 목록 홈, SDK·문구·분석·에이전트 안내)를 <이름> 폴더에 만들고 .env에 상점 연결 값을 씁니다. 호스팅 사이트는 만들지 않습니다
|
|
8850
9018
|
init 템플릿을 골라 호스팅 사이트를 만들고 pull과 같은 폴더(기본: 상점 코드)로 받습니다. 사이트가 이미 있으면 만들지 않습니다
|
|
8851
9019
|
pull 호스팅 중인 사이트의 작업 트리를 폴더(기본: 상점 코드)로 받고 .mcp.json·.env·.sayren/state.json을 씁니다
|
|
8852
9020
|
deploy 로컬 검사 → 로컬 빌드 → 작업 트리 올리기 → 빌드 → 발행. 원격이 pull 이후 바뀌었으면 덮어쓰지 않습니다
|
|
@@ -8863,6 +9031,7 @@ const ko_cli_usage = (i) => {
|
|
|
8863
9031
|
옵션:
|
|
8864
9032
|
--project <storeId> pull: 받을 상점(--store와 같습니다)
|
|
8865
9033
|
--template <id> init: 템플릿 id(비대화형은 필수)
|
|
9034
|
+
--store-code <코드> create: 연결할 상점 코드(없으면 묻습니다, 비대화형은 필수)
|
|
8866
9035
|
--dir <폴더> 작업 폴더(기본: 지금 폴더, pull·init은 상점 코드)
|
|
8867
9036
|
--force pull: 같은 사이트를 받은 폴더에 원격을 다시 받습니다(지울·덮을 파일을 보여 주고 묻습니다)
|
|
8868
9037
|
deploy: 원격 변경·열린 편집 환경을 무시하고 덮어씁니다(확인을 묻습니다)
|
|
@@ -9784,7 +9953,7 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
|
|
|
9784
9953
|
|
|
9785
9954
|
//#endregion
|
|
9786
9955
|
//#region package.json
|
|
9787
|
-
var version = "0.
|
|
9956
|
+
var version = "0.14.0";
|
|
9788
9957
|
|
|
9789
9958
|
//#endregion
|
|
9790
9959
|
//#region src/client.ts
|
|
@@ -11131,7 +11300,7 @@ function verifySources(files, options = {}) {
|
|
|
11131
11300
|
if (cookieFiles.length > 0 && !cookieFiles.some((file) => /httpOnly\s*:\s*true/.test(file.content))) inProject("buyer-token-cookie", mcp_verify_buyer_token_cookie_missing());
|
|
11132
11301
|
if (!has(".refresh(")) inProject("buyer-session-refresh", mcp_verify_buyer_session_refresh());
|
|
11133
11302
|
if (has(".session(") && !has(".signUp(")) inProject("buyer-signup", mcp_verify_buyer_signup());
|
|
11134
|
-
if (!matches(/\b(auth|authFor\(\))
|
|
11303
|
+
if (!matches(/\b(auth|authFor\(\))\s*\.signOut\(|\.signOut\(\s*accessToken\b/)) inProject("buyer-signout", mcp_verify_buyer_signout());
|
|
11135
11304
|
if (!has("reconsentRequired")) inProject("buyer-reconsent", mcp_verify_buyer_reconsent());
|
|
11136
11305
|
}
|
|
11137
11306
|
if (has("startPayment(") && !has("checkoutAvailable")) inProject("checkout-availability", mcp_verify_checkout_availability());
|
|
@@ -11468,7 +11637,7 @@ const WRITABLE_ROOT_FILES = new Set([
|
|
|
11468
11637
|
]);
|
|
11469
11638
|
/**
|
|
11470
11639
|
* 읽기만 되는 루트 파일 — 의존성은 전용 경로로만 바꾼다(레지스트리 프록시와 잠금 파일을 함께 맞춘다).
|
|
11471
|
-
* `CLAUDE.md`·`.mcp.json`은 템플릿이 싣는 에이전트 안내·MCP 연결 설정이다(2026-10-14
|
|
11640
|
+
* `CLAUDE.md`·`.mcp.json`은 템플릿이 싣는 에이전트 안내·MCP 연결 설정이다(2026-10-14 blank, #271부터 보일러플레이트 `boilerplate`) — 편집 도구는 바꾸지 않는다
|
|
11472
11641
|
*/
|
|
11473
11642
|
const READONLY_ROOT_FILES = new Set([
|
|
11474
11643
|
"package.json",
|
|
@@ -11651,7 +11820,7 @@ function isNeverUpload(path) {
|
|
|
11651
11820
|
return segments.some((segment) => segment.startsWith(".env") || segment === ".sayren") || name === ".mcp.json" || name === ".sayrenrc";
|
|
11652
11821
|
}
|
|
11653
11822
|
/** 걸어 들어가지 않는 폴더 — 설치물·산출물·캐시·VCS(경로 규칙이 어차피 막는다. 크기 때문에 읽지도 않는다) */
|
|
11654
|
-
const SKIP_DIRS = new Set([
|
|
11823
|
+
const SKIP_DIRS$1 = new Set([
|
|
11655
11824
|
"node_modules",
|
|
11656
11825
|
"dist",
|
|
11657
11826
|
".output",
|
|
@@ -11672,7 +11841,7 @@ function collectTree(root) {
|
|
|
11672
11841
|
const full = join(dir, entry.name);
|
|
11673
11842
|
const rel = normalizeStorefrontPath(relative(root, full).split(sep).join("/"));
|
|
11674
11843
|
if (entry.isDirectory()) {
|
|
11675
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
11844
|
+
if (SKIP_DIRS$1.has(entry.name)) continue;
|
|
11676
11845
|
walk(full);
|
|
11677
11846
|
continue;
|
|
11678
11847
|
}
|
|
@@ -11883,7 +12052,7 @@ function lockImporterSpecifiers(lock) {
|
|
|
11883
12052
|
//#region src/guide-skill.ts
|
|
11884
12053
|
/**
|
|
11885
12054
|
* 받은 폴더의 에이전트 안내 스킬(2026-10-16 사용자 결정) — 셀러의 Claude Code가 기능·화면을 고치기 전에 sayren 중앙 스킬을 `npx sayren skill`로 읽게 한다(스킬은 이 하나만 설치한다).
|
|
11886
|
-
*
|
|
12055
|
+
* 보일러플레이트(`npx sayren create`, #271)가 싣는 `packages/cli/boilerplate/.claude/skills/sayren/SKILL.md`와 같은 내용이다(`guide-skill.test.ts`가 고정한다).
|
|
11887
12056
|
*/
|
|
11888
12057
|
const GUIDE_SKILL_PATH = ".claude/skills/sayren/SKILL.md";
|
|
11889
12058
|
const GUIDE_SKILL = "---\nname: sayren\ndescription: Load before adding, implementing or changing any feature or page of this sayren storefront. Lists the current sayren skills and loads the ones the task needs.\n---\n\n# sayren skills\n\nThe sayren skills are maintained centrally and change without updating this project.\nDo not rely on memory or earlier copies. Load them at the start of each task.\n\n1. List the skills (name and when to use each):\n `npx -y sayren@latest skill list`\n2. Read every skill that matches the task, in full, and follow it:\n `npx -y sayren@latest skill <name>`\n\nIf the sayren MCP server is connected, the tools `list_skills` and `get_skill` return the same content.\n\nFollow the project's `CLAUDE.md` first. The skills add the sayren-specific details.\n";
|
|
@@ -12702,6 +12871,174 @@ function relativeOf(io, path) {
|
|
|
12702
12871
|
return relative(io.cwd, path) || ".";
|
|
12703
12872
|
}
|
|
12704
12873
|
|
|
12874
|
+
//#endregion
|
|
12875
|
+
//#region ../mcp/src/template-package.ts
|
|
12876
|
+
/** package.json의 기준 템플릿 표식 필드 — 원천은 이 함수다(api `template-marker.ts`가 같은 모양을 읽는다) */
|
|
12877
|
+
function withTemplateMarker(pkg, marker) {
|
|
12878
|
+
const current = pkg.sayren;
|
|
12879
|
+
pkg.sayren = {
|
|
12880
|
+
...current && typeof current === "object" && !Array.isArray(current) ? current : {},
|
|
12881
|
+
template: marker.id,
|
|
12882
|
+
templateVersion: marker.version
|
|
12883
|
+
};
|
|
12884
|
+
return pkg;
|
|
12885
|
+
}
|
|
12886
|
+
const DEP_FIELDS = [
|
|
12887
|
+
"dependencies",
|
|
12888
|
+
"devDependencies",
|
|
12889
|
+
"peerDependencies"
|
|
12890
|
+
];
|
|
12891
|
+
function renderTemplatePackageJson(raw, meta, options = {}) {
|
|
12892
|
+
const pkg = JSON.parse(raw);
|
|
12893
|
+
for (const field of DEP_FIELDS) {
|
|
12894
|
+
const deps = pkg[field];
|
|
12895
|
+
if (!deps) continue;
|
|
12896
|
+
for (const [name, spec] of Object.entries(deps)) deps[name] = resolveSpec(name, spec, meta);
|
|
12897
|
+
}
|
|
12898
|
+
if (options.name) pkg.name = options.name;
|
|
12899
|
+
const marker = options.template ?? meta.template;
|
|
12900
|
+
if (marker) withTemplateMarker(pkg, marker);
|
|
12901
|
+
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
12902
|
+
}
|
|
12903
|
+
function resolveSpec(name, spec, meta) {
|
|
12904
|
+
if (spec === "catalog:" || spec === "catalog:default") {
|
|
12905
|
+
const version$1 = meta.catalog[name];
|
|
12906
|
+
if (!version$1) throw new Error(mcp_template_catalog_version_missing({ name }));
|
|
12907
|
+
return version$1;
|
|
12908
|
+
}
|
|
12909
|
+
if (spec.startsWith("workspace:")) return workspaceRange(name, spec.slice(10), meta);
|
|
12910
|
+
return spec;
|
|
12911
|
+
}
|
|
12912
|
+
/**
|
|
12913
|
+
* `workspace:` 뒤 표기 → 셀러 프로젝트의 범위(#232). 템플릿은 SDK를 범위(`workspace:^0.35.0`)로 적고 `workspace:`만 뗀다 —
|
|
12914
|
+
* SDK patch가 올라도 풀어 쓴 package.json·첫 작업 트리가 그대로라 템플릿 버전을 올리지 않는다. 범위가 아닌 `*`·`^`·`~`만
|
|
12915
|
+
* 지금 워크스페이스 버전으로 푼다(`*`는 `^{버전}`)
|
|
12916
|
+
*/
|
|
12917
|
+
function workspaceRange(name, rest, meta) {
|
|
12918
|
+
if (rest !== "*" && rest !== "^" && rest !== "~" && rest !== "") return rest;
|
|
12919
|
+
const version$1 = meta.versions[name];
|
|
12920
|
+
if (!version$1) throw new Error(mcp_template_published_version_unknown({ name }));
|
|
12921
|
+
return `${rest === "~" ? "~" : "^"}${version$1}`;
|
|
12922
|
+
}
|
|
12923
|
+
/** pnpm-workspace.yaml의 최상위 `catalog:` 블록만 읽는다(` key: value`, 따옴표 허용) */
|
|
12924
|
+
function parseCatalog(yaml) {
|
|
12925
|
+
const out = {};
|
|
12926
|
+
let inCatalog = false;
|
|
12927
|
+
for (const line of yaml.split("\n")) {
|
|
12928
|
+
if (/^catalog:\s*$/.test(line)) {
|
|
12929
|
+
inCatalog = true;
|
|
12930
|
+
continue;
|
|
12931
|
+
}
|
|
12932
|
+
if (!inCatalog) continue;
|
|
12933
|
+
if (/^\S/.test(line)) break;
|
|
12934
|
+
const match = line.match(/^\s+["']?([^"':]+)["']?\s*:\s*["']?([^"'#\s]+)["']?/);
|
|
12935
|
+
if (match?.[1] && match[2]) out[match[1].trim()] = match[2];
|
|
12936
|
+
}
|
|
12937
|
+
return out;
|
|
12938
|
+
}
|
|
12939
|
+
function readWorkspaceVersions(packagesDir) {
|
|
12940
|
+
const out = {};
|
|
12941
|
+
for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
|
|
12942
|
+
if (!entry.isDirectory()) continue;
|
|
12943
|
+
const file = join(packagesDir, entry.name, "package.json");
|
|
12944
|
+
if (!existsSync(file)) continue;
|
|
12945
|
+
const pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
12946
|
+
if (pkg.name && pkg.version) out[pkg.name] = pkg.version;
|
|
12947
|
+
}
|
|
12948
|
+
return out;
|
|
12949
|
+
}
|
|
12950
|
+
|
|
12951
|
+
//#endregion
|
|
12952
|
+
//#region src/create-command.ts
|
|
12953
|
+
/**
|
|
12954
|
+
* `npx sayren create <이름>`(#271) — 스토어프론트 보일러플레이트를 새 폴더에 쓴다. 원천은 `packages/cli/boilerplate` 하나이고
|
|
12955
|
+
* 호스팅 「처음부터」 템플릿(카탈로그 `boilerplate`)도 같은 폴더에서 만든다. 로그인·사이트 만들기는 하지 않는다(사이트는 `init`).
|
|
12956
|
+
* 상점 연결 값은 `--store-code` → 묻기(대화형) 순서이고 `.env`에 `SAYREN_API_URL`·`SAYREN_STORE_CODE`로 쓴다.
|
|
12957
|
+
*/
|
|
12958
|
+
/** 보일러플레이트 위치 — 게시본은 빌드가 복사한 `dist/boilerplate`, 저장소에서 바로 돌리면 `packages/cli/boilerplate` */
|
|
12959
|
+
function boilerplateRoot() {
|
|
12960
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12961
|
+
const candidates = [join(here, "boilerplate"), join(here, "..", "boilerplate")];
|
|
12962
|
+
const found = candidates.find((dir) => existsSync(join(dir, "package.json")));
|
|
12963
|
+
if (!found) throw new Error(`boilerplate not found: ${candidates.join(", ")}`);
|
|
12964
|
+
return found;
|
|
12965
|
+
}
|
|
12966
|
+
/** 빌드가 남기는 버전 표(`scripts/copy-boilerplate.mjs`) — 게시본에는 pnpm-workspace.yaml이 없다 */
|
|
12967
|
+
const BOILERPLATE_META_FILE = ".boilerplate-meta.json";
|
|
12968
|
+
function metaOf(root) {
|
|
12969
|
+
const bundled = join(root, BOILERPLATE_META_FILE);
|
|
12970
|
+
if (existsSync(bundled)) return JSON.parse(readFileSync(bundled, "utf8"));
|
|
12971
|
+
const repo = join(root, "..", "..", "..");
|
|
12972
|
+
return {
|
|
12973
|
+
catalog: parseCatalog(readFileSync(join(repo, "pnpm-workspace.yaml"), "utf8")),
|
|
12974
|
+
versions: readWorkspaceVersions(join(repo, "packages"))
|
|
12975
|
+
};
|
|
12976
|
+
}
|
|
12977
|
+
/** 싣지 않는 것 — 설치물·산출물·로컬 env. 점 파일은 에이전트 안내(`.claude/`·`.mcp.json`)와 `.gitignore`만 */
|
|
12978
|
+
const SKIP_DIRS = new Set([
|
|
12979
|
+
"node_modules",
|
|
12980
|
+
"dist",
|
|
12981
|
+
".output",
|
|
12982
|
+
".tanstack",
|
|
12983
|
+
".nitro",
|
|
12984
|
+
".wrangler"
|
|
12985
|
+
]);
|
|
12986
|
+
const DOT_KEPT = new Set([
|
|
12987
|
+
".claude",
|
|
12988
|
+
".mcp.json",
|
|
12989
|
+
".gitignore"
|
|
12990
|
+
]);
|
|
12991
|
+
/** 보일러플레이트 파일 — 상대 경로 → 내용. 게시본의 `_gitignore`는 `.gitignore`로 되돌린다(npm이 `.gitignore`를 싣지 않는다) */
|
|
12992
|
+
function boilerplateFiles(root, name) {
|
|
12993
|
+
const files = /* @__PURE__ */ new Map();
|
|
12994
|
+
const walk = (dir) => {
|
|
12995
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
12996
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
12997
|
+
if (entry.name.startsWith(".") && !DOT_KEPT.has(entry.name)) continue;
|
|
12998
|
+
const full = join(dir, entry.name);
|
|
12999
|
+
if (entry.isDirectory()) walk(full);
|
|
13000
|
+
else if (entry.isFile()) {
|
|
13001
|
+
const path = relative(root, full).split(sep).join("/");
|
|
13002
|
+
files.set(path === "_gitignore" ? ".gitignore" : path, readFileSync(full));
|
|
13003
|
+
}
|
|
13004
|
+
}
|
|
13005
|
+
};
|
|
13006
|
+
walk(root);
|
|
13007
|
+
const pkg = files.get("package.json");
|
|
13008
|
+
if (!pkg) throw new Error("boilerplate package.json missing");
|
|
13009
|
+
files.set("package.json", Buffer.from(renderTemplatePackageJson(pkg.toString("utf8"), metaOf(root), { name })));
|
|
13010
|
+
return files;
|
|
13011
|
+
}
|
|
13012
|
+
/** npm 패키지 이름으로도 쓰는 폴더 이름 — 범위(@scope/) 없는 이름만 */
|
|
13013
|
+
const NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,213}$/;
|
|
13014
|
+
async function create(flags, io) {
|
|
13015
|
+
const name = flags.name?.trim();
|
|
13016
|
+
if (!name) throw new CliError(cli_create_usage());
|
|
13017
|
+
if (!NAME_PATTERN.test(name)) throw new CliError(cli_create_name_invalid({ name }));
|
|
13018
|
+
const target = resolve(io.cwd, name);
|
|
13019
|
+
if (existsSync(target) && (!statSync(target).isDirectory() || readdirSync(target).length > 0)) throw new CliError(cli_create_dir_not_empty({ dir: name }));
|
|
13020
|
+
const storeCode = (flags.storeCode ?? (io.ask ? await io.ask(cli_create_prompt_store_code()) : null))?.trim();
|
|
13021
|
+
if (!storeCode) throw new CliError(cli_create_store_code_required());
|
|
13022
|
+
if (!STORE_CODE_PATTERN.test(storeCode)) throw new CliError(cli_create_store_code_invalid({ storeCode }));
|
|
13023
|
+
const { apiOrigin } = resolveApiOrigin(flags, target, null, io.env);
|
|
13024
|
+
const files = boilerplateFiles(boilerplateRoot(), name);
|
|
13025
|
+
for (const [path, bytes] of files) {
|
|
13026
|
+
const file = join(target, path);
|
|
13027
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
13028
|
+
writeFileSync(file, bytes);
|
|
13029
|
+
}
|
|
13030
|
+
updateEnvFile(target, {
|
|
13031
|
+
SAYREN_API_URL: `${apiOrigin}/storefront/v1`,
|
|
13032
|
+
SAYREN_STORE_CODE: storeCode
|
|
13033
|
+
});
|
|
13034
|
+
io.out(cli_create_done({
|
|
13035
|
+
dir: name,
|
|
13036
|
+
count: files.size,
|
|
13037
|
+
storeCode
|
|
13038
|
+
}));
|
|
13039
|
+
return 0;
|
|
13040
|
+
}
|
|
13041
|
+
|
|
12705
13042
|
//#endregion
|
|
12706
13043
|
//#region src/skill-commands.ts
|
|
12707
13044
|
/**
|
|
@@ -12742,6 +13079,7 @@ const VALUE_FLAGS = new Set([
|
|
|
12742
13079
|
"store",
|
|
12743
13080
|
"accounts-origin",
|
|
12744
13081
|
"template",
|
|
13082
|
+
"store-code",
|
|
12745
13083
|
"key",
|
|
12746
13084
|
"name",
|
|
12747
13085
|
"port",
|
|
@@ -12842,6 +13180,11 @@ async function main(argv, io) {
|
|
|
12842
13180
|
tool: str(parsed.flags.tool),
|
|
12843
13181
|
yes: parsed.flags.yes === true
|
|
12844
13182
|
}, io);
|
|
13183
|
+
case "create": return create({
|
|
13184
|
+
name: parsed.positional[0],
|
|
13185
|
+
storeCode: str(parsed.flags["store-code"]),
|
|
13186
|
+
apiOrigin: str(parsed.flags["api-origin"])
|
|
13187
|
+
}, io);
|
|
12845
13188
|
case "init": return init({
|
|
12846
13189
|
...common,
|
|
12847
13190
|
template: str(parsed.flags.template),
|
|
@@ -12893,6 +13236,7 @@ function defaultIo() {
|
|
|
12893
13236
|
out: (line) => console.log(line),
|
|
12894
13237
|
err: (line) => console.error(line),
|
|
12895
13238
|
confirm: stdin.isTTY ? async (question) => /^(y|yes|예|네)$/i.test((await ask(`${question} (y/N) `) ?? "").trim()) : null,
|
|
13239
|
+
ask: stdin.isTTY ? ask : null,
|
|
12896
13240
|
sleep: (ms) => new Promise((resolve$1) => setTimeout(resolve$1, ms)),
|
|
12897
13241
|
buildTimeoutMs: 20 * 6e4,
|
|
12898
13242
|
pollMs: 3e3,
|
|
@@ -12935,7 +13279,6 @@ function openBrowser(url) {
|
|
|
12935
13279
|
function describeError(error) {
|
|
12936
13280
|
if (error instanceof CliError) return error.message;
|
|
12937
13281
|
if (error instanceof ApiError) {
|
|
12938
|
-
if (error.status === 401 && error.code === "TOKEN_TYPE_RETIRED") return cli_retired_token();
|
|
12939
13282
|
if (error.status === 401) return cli_error_unauthorized();
|
|
12940
13283
|
if (error.status === 403 && error.code === "STORE_NOT_GRANTED") return cli_error_store_not_granted();
|
|
12941
13284
|
if (error.status === 403 && error.code === "INSUFFICIENT_SCOPE") return cli_error_insufficient_scope();
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sayren",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"imports": {
|
|
6
6
|
"#paraglide/*": "./src/paraglide/*"
|
|
7
7
|
},
|
|
8
|
-
"description": "sayren CLI —
|
|
8
|
+
"description": "sayren CLI — 스토어프론트 보일러플레이트(create)·호스팅 스토어프론트(init·pull·deploy)와 서드파티 앱(app init·dev·deploy·release)",
|
|
9
9
|
"bin": {
|
|
10
10
|
"sayren": "./dist/index.mjs"
|
|
11
11
|
},
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"esbuild": "^0.28.2",
|
|
20
|
-
"@sayren/store-sdk": "^0.
|
|
20
|
+
"@sayren/store-sdk": "^0.53.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@biomejs/biome": "^2.5.14",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"tsx": "^4.20.3",
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
30
|
"vitest": "^5.0.1",
|
|
31
|
-
"@sayren/
|
|
31
|
+
"@sayren/app": "0.9.0",
|
|
32
|
+
"@sayren/mcp": "0.29.3"
|
|
32
33
|
},
|
|
33
34
|
"publishConfig": {
|
|
34
35
|
"access": "public"
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"scripts": {
|
|
42
43
|
"dev": "tsx src/index.ts",
|
|
43
44
|
"i18n": "paraglide-js compile --project ./project.inlang --outdir ./src/paraglide --strategy baseLocale --emit-ts-declarations --no-emit-readme --no-emit-prettier-ignore --silent",
|
|
44
|
-
"build": "pnpm i18n && tsdown",
|
|
45
|
+
"build": "pnpm i18n && tsdown && node scripts/copy-boilerplate.mjs",
|
|
45
46
|
"lint": "biome check --write . && tsc --noEmit",
|
|
46
47
|
"typecheck": "tsc --noEmit",
|
|
47
48
|
"test": "vitest run"
|