sayren 0.4.0 → 0.6.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.mjs +2016 -166
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
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 { ApiError, ApprovalPendingError, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_SOURCE_BLOB_BATCH_MAX, 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, 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
|
-
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
8
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
9
7
|
import { createServer } from "node:http";
|
|
8
|
+
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { gzipSync } from "node:zlib";
|
|
11
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
10
12
|
|
|
11
13
|
//#region ../i18n/src/paraglide/runtime.js
|
|
12
14
|
/** @type {any} */
|
|
@@ -592,185 +594,1063 @@ function defineCustomClientStrategy(strategy$1, handler) {
|
|
|
592
594
|
* }} MessageMarkupTag
|
|
593
595
|
*/
|
|
594
596
|
/**
|
|
595
|
-
* Type-level schema for all markup tags in a message.
|
|
596
|
-
*
|
|
597
|
-
* @typedef {Record<string, MessageMarkupTag>} MessageMarkupSchema
|
|
597
|
+
* Type-level schema for all markup tags in a message.
|
|
598
|
+
*
|
|
599
|
+
* @typedef {Record<string, MessageMarkupTag>} MessageMarkupSchema
|
|
600
|
+
*/
|
|
601
|
+
/**
|
|
602
|
+
* Type-only metadata attached to compiled message functions.
|
|
603
|
+
*
|
|
604
|
+
* @template Inputs
|
|
605
|
+
* @template Options
|
|
606
|
+
* @template {MessageMarkupSchema} [Markup = MessageMarkupSchema]
|
|
607
|
+
* @typedef {{
|
|
608
|
+
* readonly __paraglide?: {
|
|
609
|
+
* inputs: Inputs;
|
|
610
|
+
* options: Options;
|
|
611
|
+
* markup: Markup;
|
|
612
|
+
* };
|
|
613
|
+
* }} MessageMetadata
|
|
614
|
+
*/
|
|
615
|
+
/**
|
|
616
|
+
* A compiled, framework-neutral message part.
|
|
617
|
+
*
|
|
618
|
+
* @typedef {{
|
|
619
|
+
* type: "text";
|
|
620
|
+
* value: string;
|
|
621
|
+
* } | {
|
|
622
|
+
* type: "markup-start";
|
|
623
|
+
* name: string;
|
|
624
|
+
* options: MessageMarkupOptions;
|
|
625
|
+
* attributes: MessageMarkupAttributes;
|
|
626
|
+
* } | {
|
|
627
|
+
* type: "markup-end";
|
|
628
|
+
* name: string;
|
|
629
|
+
* options: MessageMarkupOptions;
|
|
630
|
+
* attributes: MessageMarkupAttributes;
|
|
631
|
+
* } | {
|
|
632
|
+
* type: "markup-standalone";
|
|
633
|
+
* name: string;
|
|
634
|
+
* options: MessageMarkupOptions;
|
|
635
|
+
* attributes: MessageMarkupAttributes;
|
|
636
|
+
* }} MessagePart
|
|
637
|
+
*/
|
|
638
|
+
/**
|
|
639
|
+
* A message function is a message for a specific locale.
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* m.hello({ name: 'world' })
|
|
643
|
+
*
|
|
644
|
+
* @typedef {(inputs?: Record<string, never>) => LocalizedString} MessageFunction
|
|
645
|
+
*/
|
|
646
|
+
/**
|
|
647
|
+
* A message bundle function that selects the message to be returned.
|
|
648
|
+
*
|
|
649
|
+
* Uses `getLocale()` under the hood to determine the locale with an option.
|
|
650
|
+
*
|
|
651
|
+
* @template {string} T
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* * m.hello({ name: 'world' }, { locale: "en" })
|
|
655
|
+
*
|
|
656
|
+
* @typedef {(params: Record<string, never>, options: { locale: T }) => LocalizedString} MessageBundleFunction
|
|
657
|
+
*/
|
|
658
|
+
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region ../i18n/src/locale.ts
|
|
661
|
+
/**
|
|
662
|
+
* 로케일 판정(이슈 #73) — 1순위 쿼리 `?lang=`, 2순위 `Accept-Language`(브라우저는 `navigator.languages`), 마지막 `ko`.
|
|
663
|
+
* 순서는 컴파일 옵션(`scripts/compile.mjs`의 strategy)이 정하고, 여기서는 Paraglide에 없는 쿼리 판정만 등록한다.
|
|
664
|
+
* api는 같은 순서를 `@nestjs/i18n` 리졸버(Query → AcceptLanguage)로 맞춘다.
|
|
665
|
+
*/
|
|
666
|
+
const LOCALE_QUERY_PARAM = "lang";
|
|
667
|
+
/** `?lang=ko-KR` → `ko`. 지원하지 않는 값·없으면 undefined(다음 판정으로 넘어간다) */
|
|
668
|
+
function localeFromSearch(search) {
|
|
669
|
+
const value = new URLSearchParams(search).get(LOCALE_QUERY_PARAM);
|
|
670
|
+
if (!value) return void 0;
|
|
671
|
+
return toLocale(value) ?? toLocale(value.split("-")[0]);
|
|
672
|
+
}
|
|
673
|
+
defineCustomServerStrategy("custom-query", { getLocale: (request) => request ? localeFromSearch(new URL(request.url).search) : void 0 });
|
|
674
|
+
/** 브라우저의 현재 주소 쿼리 — DOM 타입에 기대지 않는다(DOM lib 없는 소비자, 예: mcp-gateway도 이 소스를 타입 검사한다) */
|
|
675
|
+
function currentSearch() {
|
|
676
|
+
return globalThis.location?.search;
|
|
677
|
+
}
|
|
678
|
+
defineCustomClientStrategy("custom-query", {
|
|
679
|
+
getLocale: () => {
|
|
680
|
+
const search = currentSearch();
|
|
681
|
+
return search === void 0 ? void 0 : localeFromSearch(search);
|
|
682
|
+
},
|
|
683
|
+
setLocale: () => {}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region ../i18n/src/paraglide/messages/cli_access_token_expired.js
|
|
688
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
689
|
+
/** @typedef {{}} Cli_Access_Token_ExpiredInputs */
|
|
690
|
+
const ko_cli_access_token_expired = () => {
|
|
691
|
+
return `액세스 토큰이 만료됐습니다. 새 토큰을 만들거나 npx sayren login으로 다시 로그인하십시오`;
|
|
692
|
+
};
|
|
693
|
+
/**
|
|
694
|
+
|
|
695
|
+
* @param {Cli_Access_Token_ExpiredInputs} inputs
|
|
696
|
+
* @param {{ locale?: "ko" }} options
|
|
697
|
+
* @returns {LocalizedString}
|
|
698
|
+
*/
|
|
699
|
+
const cli_access_token_expired = ((inputs = {}, options = {}) => {
|
|
700
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
701
|
+
return ko_cli_access_token_expired(inputs);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
//#endregion
|
|
705
|
+
//#region ../i18n/src/paraglide/messages/cli_account_mcp_written.js
|
|
706
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
707
|
+
/** @typedef {{ file: NonNullable<unknown>, mcpFile: NonNullable<unknown> }} Cli_Account_Mcp_WrittenInputs */
|
|
708
|
+
const ko_cli_account_mcp_written = (i) => {
|
|
709
|
+
return `${i?.file} 작성 — 원격 MCP 주소와 상점. ${i?.mcpFile}에는 토큰을 쓰지 않았습니다 — MCP 클라이언트가 연결할 때 Sayren 계정으로 로그인합니다`;
|
|
710
|
+
};
|
|
711
|
+
/**
|
|
712
|
+
|
|
713
|
+
* @param {Cli_Account_Mcp_WrittenInputs} inputs
|
|
714
|
+
* @param {{ locale?: "ko" }} options
|
|
715
|
+
* @returns {LocalizedString}
|
|
716
|
+
*/
|
|
717
|
+
const cli_account_mcp_written = ((inputs, options = {}) => {
|
|
718
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
719
|
+
return ko_cli_account_mcp_written(inputs);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region ../i18n/src/paraglide/messages/cli_accounts_origin_unknown.js
|
|
724
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
725
|
+
/** @typedef {{ origin: NonNullable<unknown> }} Cli_Accounts_Origin_UnknownInputs */
|
|
726
|
+
const ko_cli_accounts_origin_unknown = (i) => {
|
|
727
|
+
return `이 api 주소(${i?.origin})의 계정 서버를 모릅니다. --accounts-origin 또는 SAYREN_ACCOUNTS_ORIGIN으로 주십시오`;
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
730
|
+
|
|
731
|
+
* @param {Cli_Accounts_Origin_UnknownInputs} inputs
|
|
732
|
+
* @param {{ locale?: "ko" }} options
|
|
733
|
+
* @returns {LocalizedString}
|
|
734
|
+
*/
|
|
735
|
+
const cli_accounts_origin_unknown = ((inputs, options = {}) => {
|
|
736
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
737
|
+
return ko_cli_accounts_origin_unknown(inputs);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region ../i18n/src/paraglide/messages/cli_accounts_unreachable.js
|
|
742
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
743
|
+
/** @typedef {{}} Cli_Accounts_UnreachableInputs */
|
|
744
|
+
const ko_cli_accounts_unreachable = () => {
|
|
745
|
+
return `계정 서버에 연결하지 못했습니다. 네트워크를 확인하십시오`;
|
|
746
|
+
};
|
|
747
|
+
/**
|
|
748
|
+
|
|
749
|
+
* @param {Cli_Accounts_UnreachableInputs} inputs
|
|
750
|
+
* @param {{ locale?: "ko" }} options
|
|
751
|
+
* @returns {LocalizedString}
|
|
752
|
+
*/
|
|
753
|
+
const cli_accounts_unreachable = ((inputs = {}, options = {}) => {
|
|
754
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
755
|
+
return ko_cli_accounts_unreachable(inputs);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region ../i18n/src/paraglide/messages/cli_app_bridge_missing.js
|
|
760
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
761
|
+
/** @typedef {{}} Cli_App_Bridge_MissingInputs */
|
|
762
|
+
const ko_cli_app_bridge_missing = () => {
|
|
763
|
+
return `앱 폴더에 @sayren/app이 설치돼 있지 않습니다(ui/_sayren/ui.js를 만들 수 없습니다). npm install을 먼저 하십시오`;
|
|
764
|
+
};
|
|
765
|
+
/**
|
|
766
|
+
|
|
767
|
+
* @param {Cli_App_Bridge_MissingInputs} inputs
|
|
768
|
+
* @param {{ locale?: "ko" }} options
|
|
769
|
+
* @returns {LocalizedString}
|
|
770
|
+
*/
|
|
771
|
+
const cli_app_bridge_missing = ((inputs = {}, options = {}) => {
|
|
772
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
773
|
+
return ko_cli_app_bridge_missing(inputs);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
//#endregion
|
|
777
|
+
//#region ../i18n/src/paraglide/messages/cli_app_build_failed.js
|
|
778
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
779
|
+
/** @typedef {{ stage: NonNullable<unknown>, code: NonNullable<unknown>, tail: NonNullable<unknown> }} Cli_App_Build_FailedInputs */
|
|
780
|
+
const ko_cli_app_build_failed = (i) => {
|
|
781
|
+
return `빌드 실패: ${i?.stage} (${i?.code})
|
|
782
|
+
${i?.tail}`;
|
|
783
|
+
};
|
|
784
|
+
/**
|
|
785
|
+
|
|
786
|
+
* @param {Cli_App_Build_FailedInputs} inputs
|
|
787
|
+
* @param {{ locale?: "ko" }} options
|
|
788
|
+
* @returns {LocalizedString}
|
|
789
|
+
*/
|
|
790
|
+
const cli_app_build_failed = ((inputs, options = {}) => {
|
|
791
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
792
|
+
return ko_cli_app_build_failed(inputs);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region ../i18n/src/paraglide/messages/cli_app_bundle_failed.js
|
|
797
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
798
|
+
/** @typedef {{ tail: NonNullable<unknown> }} Cli_App_Bundle_FailedInputs */
|
|
799
|
+
const ko_cli_app_bundle_failed = (i) => {
|
|
800
|
+
return `서버 번들을 만들지 못했습니다:
|
|
801
|
+
${i?.tail}`;
|
|
802
|
+
};
|
|
803
|
+
/**
|
|
804
|
+
|
|
805
|
+
* @param {Cli_App_Bundle_FailedInputs} inputs
|
|
806
|
+
* @param {{ locale?: "ko" }} options
|
|
807
|
+
* @returns {LocalizedString}
|
|
808
|
+
*/
|
|
809
|
+
const cli_app_bundle_failed = ((inputs, options = {}) => {
|
|
810
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
811
|
+
return ko_cli_app_bundle_failed(inputs);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
//#endregion
|
|
815
|
+
//#region ../i18n/src/paraglide/messages/cli_app_bundle_too_large.js
|
|
816
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
817
|
+
/** @typedef {{ size: NonNullable<unknown> }} Cli_App_Bundle_Too_LargeInputs */
|
|
818
|
+
const ko_cli_app_bundle_too_large = (i) => {
|
|
819
|
+
return `서버 번들이 ${i?.size}(gzip)입니다. 상한은 3MB입니다 (ARTIFACT_TOO_LARGE)`;
|
|
820
|
+
};
|
|
821
|
+
/**
|
|
822
|
+
|
|
823
|
+
* @param {Cli_App_Bundle_Too_LargeInputs} inputs
|
|
824
|
+
* @param {{ locale?: "ko" }} options
|
|
825
|
+
* @returns {LocalizedString}
|
|
826
|
+
*/
|
|
827
|
+
const cli_app_bundle_too_large = ((inputs, options = {}) => {
|
|
828
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
829
|
+
return ko_cli_app_bundle_too_large(inputs);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
//#endregion
|
|
833
|
+
//#region ../i18n/src/paraglide/messages/cli_app_default_export_missing.js
|
|
834
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
835
|
+
/** @typedef {{}} Cli_App_Default_Export_MissingInputs */
|
|
836
|
+
const ko_cli_app_default_export_missing = () => {
|
|
837
|
+
return `src/server.ts가 defineApp(...)을 기본 내보내기(export default)해야 합니다`;
|
|
838
|
+
};
|
|
839
|
+
/**
|
|
840
|
+
|
|
841
|
+
* @param {Cli_App_Default_Export_MissingInputs} inputs
|
|
842
|
+
* @param {{ locale?: "ko" }} options
|
|
843
|
+
* @returns {LocalizedString}
|
|
844
|
+
*/
|
|
845
|
+
const cli_app_default_export_missing = ((inputs = {}, options = {}) => {
|
|
846
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
847
|
+
return ko_cli_app_default_export_missing(inputs);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region ../i18n/src/paraglide/messages/cli_app_deploy_done.js
|
|
852
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
853
|
+
/** @typedef {{ number: NonNullable<unknown> }} Cli_App_Deploy_DoneInputs */
|
|
854
|
+
const ko_cli_app_deploy_done = (i) => {
|
|
855
|
+
return `버전 ${i?.number}을 만들었습니다. 적용하려면: npx sayren app release ${i?.number}`;
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
|
|
859
|
+
* @param {Cli_App_Deploy_DoneInputs} inputs
|
|
860
|
+
* @param {{ locale?: "ko" }} options
|
|
861
|
+
* @returns {LocalizedString}
|
|
862
|
+
*/
|
|
863
|
+
const cli_app_deploy_done = ((inputs, options = {}) => {
|
|
864
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
865
|
+
return ko_cli_app_deploy_done(inputs);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region ../i18n/src/paraglide/messages/cli_app_dev_error.js
|
|
870
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
871
|
+
/** @typedef {{ message: NonNullable<unknown> }} Cli_App_Dev_ErrorInputs */
|
|
872
|
+
const ko_cli_app_dev_error = (i) => {
|
|
873
|
+
return `로컬 실행 오류: ${i?.message}`;
|
|
874
|
+
};
|
|
875
|
+
/**
|
|
876
|
+
|
|
877
|
+
* @param {Cli_App_Dev_ErrorInputs} inputs
|
|
878
|
+
* @param {{ locale?: "ko" }} options
|
|
879
|
+
* @returns {LocalizedString}
|
|
880
|
+
*/
|
|
881
|
+
const cli_app_dev_error = ((inputs, options = {}) => {
|
|
882
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
883
|
+
return ko_cli_app_dev_error(inputs);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
//#endregion
|
|
887
|
+
//#region ../i18n/src/paraglide/messages/cli_app_dev_listening.js
|
|
888
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
889
|
+
/** @typedef {{ port: NonNullable<unknown>, store: NonNullable<unknown>, events: NonNullable<unknown> }} Cli_App_Dev_ListeningInputs */
|
|
890
|
+
const ko_cli_app_dev_listening = (i) => {
|
|
891
|
+
return `앱 로컬 실행 중 http://localhost:${i?.port} — 상점 ${i?.store} · 이벤트 ${i?.events}
|
|
892
|
+
이벤트: npx sayren app trigger ORDER.PAID --order <orderId>
|
|
893
|
+
HTTP: http://localhost:${i?.port}/_http/<경로>
|
|
894
|
+
설정·비밀 값: .env의 SAYREN_APP_SETTING_*·SAYREN_APP_SECRET_*
|
|
895
|
+
종료는 Ctrl-C`;
|
|
896
|
+
};
|
|
897
|
+
/**
|
|
898
|
+
|
|
899
|
+
* @param {Cli_App_Dev_ListeningInputs} inputs
|
|
900
|
+
* @param {{ locale?: "ko" }} options
|
|
901
|
+
* @returns {LocalizedString}
|
|
902
|
+
*/
|
|
903
|
+
const cli_app_dev_listening = ((inputs, options = {}) => {
|
|
904
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
905
|
+
return ko_cli_app_dev_listening(inputs);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
//#endregion
|
|
909
|
+
//#region ../i18n/src/paraglide/messages/cli_app_dev_store_created.js
|
|
910
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
911
|
+
/** @typedef {{ name: NonNullable<unknown>, storeId: NonNullable<unknown> }} Cli_App_Dev_Store_CreatedInputs */
|
|
912
|
+
const ko_cli_app_dev_store_created = (i) => {
|
|
913
|
+
return `개발 상점을 만들었습니다: ${i?.name} (${i?.storeId})`;
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
|
|
917
|
+
* @param {Cli_App_Dev_Store_CreatedInputs} inputs
|
|
918
|
+
* @param {{ locale?: "ko" }} options
|
|
919
|
+
* @returns {LocalizedString}
|
|
920
|
+
*/
|
|
921
|
+
const cli_app_dev_store_created = ((inputs, options = {}) => {
|
|
922
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
923
|
+
return ko_cli_app_dev_store_created(inputs);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region ../i18n/src/paraglide/messages/cli_app_dev_store_empty.js
|
|
928
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
929
|
+
/** @typedef {{}} Cli_App_Dev_Store_EmptyInputs */
|
|
930
|
+
const ko_cli_app_dev_store_empty = () => {
|
|
931
|
+
return `개발 상점이 없습니다. npx sayren app dev-store create <이름>`;
|
|
932
|
+
};
|
|
933
|
+
/**
|
|
934
|
+
|
|
935
|
+
* @param {Cli_App_Dev_Store_EmptyInputs} inputs
|
|
936
|
+
* @param {{ locale?: "ko" }} options
|
|
937
|
+
* @returns {LocalizedString}
|
|
938
|
+
*/
|
|
939
|
+
const cli_app_dev_store_empty = ((inputs = {}, options = {}) => {
|
|
940
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
941
|
+
return ko_cli_app_dev_store_empty(inputs);
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
//#endregion
|
|
945
|
+
//#region ../i18n/src/paraglide/messages/cli_app_dev_store_name_required.js
|
|
946
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
947
|
+
/** @typedef {{}} Cli_App_Dev_Store_Name_RequiredInputs */
|
|
948
|
+
const ko_cli_app_dev_store_name_required = () => {
|
|
949
|
+
return `개발 상점 이름이 필요합니다(2~50자)`;
|
|
950
|
+
};
|
|
951
|
+
/**
|
|
952
|
+
|
|
953
|
+
* @param {Cli_App_Dev_Store_Name_RequiredInputs} inputs
|
|
954
|
+
* @param {{ locale?: "ko" }} options
|
|
955
|
+
* @returns {LocalizedString}
|
|
956
|
+
*/
|
|
957
|
+
const cli_app_dev_store_name_required = ((inputs = {}, options = {}) => {
|
|
958
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
959
|
+
return ko_cli_app_dev_store_name_required(inputs);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
//#endregion
|
|
963
|
+
//#region ../i18n/src/paraglide/messages/cli_app_dir_exists.js
|
|
964
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
965
|
+
/** @typedef {{ dir: NonNullable<unknown> }} Cli_App_Dir_ExistsInputs */
|
|
966
|
+
const ko_cli_app_dir_exists = (i) => {
|
|
967
|
+
return `${i?.dir}에 이미 sayren.app.json이 있습니다`;
|
|
968
|
+
};
|
|
969
|
+
/**
|
|
970
|
+
|
|
971
|
+
* @param {Cli_App_Dir_ExistsInputs} inputs
|
|
972
|
+
* @param {{ locale?: "ko" }} options
|
|
973
|
+
* @returns {LocalizedString}
|
|
974
|
+
*/
|
|
975
|
+
const cli_app_dir_exists = ((inputs, options = {}) => {
|
|
976
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
977
|
+
return ko_cli_app_dir_exists(inputs);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region ../i18n/src/paraglide/messages/cli_app_entry_missing.js
|
|
982
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
983
|
+
/** @typedef {{ entry: NonNullable<unknown> }} Cli_App_Entry_MissingInputs */
|
|
984
|
+
const ko_cli_app_entry_missing = (i) => {
|
|
985
|
+
return `서버 진입 파일(${i?.entry})이 없습니다`;
|
|
986
|
+
};
|
|
987
|
+
/**
|
|
988
|
+
|
|
989
|
+
* @param {Cli_App_Entry_MissingInputs} inputs
|
|
990
|
+
* @param {{ locale?: "ko" }} options
|
|
991
|
+
* @returns {LocalizedString}
|
|
992
|
+
*/
|
|
993
|
+
const cli_app_entry_missing = ((inputs, options = {}) => {
|
|
994
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
995
|
+
return ko_cli_app_entry_missing(inputs);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
//#endregion
|
|
999
|
+
//#region ../i18n/src/paraglide/messages/cli_app_handle_invalid.js
|
|
1000
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1001
|
+
/** @typedef {{}} Cli_App_Handle_InvalidInputs */
|
|
1002
|
+
const ko_cli_app_handle_invalid = () => {
|
|
1003
|
+
return `handle은 소문자·숫자·하이픈 3~40자입니다. 예: npx sayren app init order-mail`;
|
|
1004
|
+
};
|
|
1005
|
+
/**
|
|
1006
|
+
|
|
1007
|
+
* @param {Cli_App_Handle_InvalidInputs} inputs
|
|
1008
|
+
* @param {{ locale?: "ko" }} options
|
|
1009
|
+
* @returns {LocalizedString}
|
|
1010
|
+
*/
|
|
1011
|
+
const cli_app_handle_invalid = ((inputs = {}, options = {}) => {
|
|
1012
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1013
|
+
return ko_cli_app_handle_invalid(inputs);
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
//#endregion
|
|
1017
|
+
//#region ../i18n/src/paraglide/messages/cli_app_handle_mismatch.js
|
|
1018
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1019
|
+
/** @typedef {{ manifest: NonNullable<unknown>, registered: NonNullable<unknown> }} Cli_App_Handle_MismatchInputs */
|
|
1020
|
+
const ko_cli_app_handle_mismatch = (i) => {
|
|
1021
|
+
return `매니페스트 handle(${i?.manifest})이 등록된 앱(${i?.registered})과 다릅니다`;
|
|
1022
|
+
};
|
|
1023
|
+
/**
|
|
1024
|
+
|
|
1025
|
+
* @param {Cli_App_Handle_MismatchInputs} inputs
|
|
1026
|
+
* @param {{ locale?: "ko" }} options
|
|
1027
|
+
* @returns {LocalizedString}
|
|
1028
|
+
*/
|
|
1029
|
+
const cli_app_handle_mismatch = ((inputs, options = {}) => {
|
|
1030
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1031
|
+
return ko_cli_app_handle_mismatch(inputs);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
//#endregion
|
|
1035
|
+
//#region ../i18n/src/paraglide/messages/cli_app_handler_missing.js
|
|
1036
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1037
|
+
/** @typedef {{ events: NonNullable<unknown> }} Cli_App_Handler_MissingInputs */
|
|
1038
|
+
const ko_cli_app_handler_missing = (i) => {
|
|
1039
|
+
return `매니페스트 events에 있지만 이벤트 함수가 없습니다: ${i?.events} (HANDLER_MISSING)`;
|
|
1040
|
+
};
|
|
1041
|
+
/**
|
|
1042
|
+
|
|
1043
|
+
* @param {Cli_App_Handler_MissingInputs} inputs
|
|
1044
|
+
* @param {{ locale?: "ko" }} options
|
|
1045
|
+
* @returns {LocalizedString}
|
|
1046
|
+
*/
|
|
1047
|
+
const cli_app_handler_missing = ((inputs, options = {}) => {
|
|
1048
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1049
|
+
return ko_cli_app_handler_missing(inputs);
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region ../i18n/src/paraglide/messages/cli_app_handler_unlisted.js
|
|
1054
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1055
|
+
/** @typedef {{ events: NonNullable<unknown> }} Cli_App_Handler_UnlistedInputs */
|
|
1056
|
+
const ko_cli_app_handler_unlisted = (i) => {
|
|
1057
|
+
return `이벤트 함수는 있지만 매니페스트 events에 없어 호출되지 않습니다: ${i?.events}`;
|
|
1058
|
+
};
|
|
1059
|
+
/**
|
|
1060
|
+
|
|
1061
|
+
* @param {Cli_App_Handler_UnlistedInputs} inputs
|
|
1062
|
+
* @param {{ locale?: "ko" }} options
|
|
1063
|
+
* @returns {LocalizedString}
|
|
1064
|
+
*/
|
|
1065
|
+
const cli_app_handler_unlisted = ((inputs, options = {}) => {
|
|
1066
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1067
|
+
return ko_cli_app_handler_unlisted(inputs);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region ../i18n/src/paraglide/messages/cli_app_icon_invalid.js
|
|
1072
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1073
|
+
/** @typedef {{ path: NonNullable<unknown>, problem: NonNullable<unknown> }} Cli_App_Icon_InvalidInputs */
|
|
1074
|
+
const ko_cli_app_icon_invalid = (i) => {
|
|
1075
|
+
return `아이콘을 쓸 수 없습니다(${i?.path}): ${i?.problem}`;
|
|
1076
|
+
};
|
|
1077
|
+
/**
|
|
1078
|
+
|
|
1079
|
+
* @param {Cli_App_Icon_InvalidInputs} inputs
|
|
1080
|
+
* @param {{ locale?: "ko" }} options
|
|
1081
|
+
* @returns {LocalizedString}
|
|
1082
|
+
*/
|
|
1083
|
+
const cli_app_icon_invalid = ((inputs, options = {}) => {
|
|
1084
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1085
|
+
return ko_cli_app_icon_invalid(inputs);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
//#endregion
|
|
1089
|
+
//#region ../i18n/src/paraglide/messages/cli_app_icon_missing.js
|
|
1090
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1091
|
+
/** @typedef {{ path: NonNullable<unknown> }} Cli_App_Icon_MissingInputs */
|
|
1092
|
+
const ko_cli_app_icon_missing = (i) => {
|
|
1093
|
+
return `매니페스트 icon의 파일이 없습니다: ${i?.path}`;
|
|
1094
|
+
};
|
|
1095
|
+
/**
|
|
1096
|
+
|
|
1097
|
+
* @param {Cli_App_Icon_MissingInputs} inputs
|
|
1098
|
+
* @param {{ locale?: "ko" }} options
|
|
1099
|
+
* @returns {LocalizedString}
|
|
1100
|
+
*/
|
|
1101
|
+
const cli_app_icon_missing = ((inputs, options = {}) => {
|
|
1102
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1103
|
+
return ko_cli_app_icon_missing(inputs);
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region ../i18n/src/paraglide/messages/cli_app_init_done.js
|
|
1108
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1109
|
+
/** @typedef {{ dir: NonNullable<unknown>, handle: NonNullable<unknown>, appId: NonNullable<unknown> }} Cli_App_Init_DoneInputs */
|
|
1110
|
+
const ko_cli_app_init_done = (i) => {
|
|
1111
|
+
return `앱을 만들었습니다: ${i?.dir} (${i?.handle}, ${i?.appId})
|
|
1112
|
+
|
|
1113
|
+
cd ${i?.dir}
|
|
1114
|
+
npm install
|
|
1115
|
+
npx sayren app dev --store <storeId>
|
|
1116
|
+
npx sayren app deploy -m "첫 버전"`;
|
|
1117
|
+
};
|
|
1118
|
+
/**
|
|
1119
|
+
|
|
1120
|
+
* @param {Cli_App_Init_DoneInputs} inputs
|
|
1121
|
+
* @param {{ locale?: "ko" }} options
|
|
1122
|
+
* @returns {LocalizedString}
|
|
1123
|
+
*/
|
|
1124
|
+
const cli_app_init_done = ((inputs, options = {}) => {
|
|
1125
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1126
|
+
return ko_cli_app_init_done(inputs);
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
//#endregion
|
|
1130
|
+
//#region ../i18n/src/paraglide/messages/cli_app_key_invalid.js
|
|
1131
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1132
|
+
/** @typedef {{}} Cli_App_Key_InvalidInputs */
|
|
1133
|
+
const ko_cli_app_key_invalid = () => {
|
|
1134
|
+
return `앱 배포 키 형식이 아닙니다(sak_…). developer.sayren.app에서 발행한 키를 쓰십시오`;
|
|
1135
|
+
};
|
|
1136
|
+
/**
|
|
1137
|
+
|
|
1138
|
+
* @param {Cli_App_Key_InvalidInputs} inputs
|
|
1139
|
+
* @param {{ locale?: "ko" }} options
|
|
1140
|
+
* @returns {LocalizedString}
|
|
1141
|
+
*/
|
|
1142
|
+
const cli_app_key_invalid = ((inputs = {}, options = {}) => {
|
|
1143
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1144
|
+
return ko_cli_app_key_invalid(inputs);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region ../i18n/src/paraglide/messages/cli_app_listing_none.js
|
|
1149
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1150
|
+
/** @typedef {{}} Cli_App_Listing_NoneInputs */
|
|
1151
|
+
const ko_cli_app_listing_none = () => {
|
|
1152
|
+
return `없음`;
|
|
1153
|
+
};
|
|
1154
|
+
/**
|
|
1155
|
+
|
|
1156
|
+
* @param {Cli_App_Listing_NoneInputs} inputs
|
|
1157
|
+
* @param {{ locale?: "ko" }} options
|
|
1158
|
+
* @returns {LocalizedString}
|
|
1159
|
+
*/
|
|
1160
|
+
const cli_app_listing_none = ((inputs = {}, options = {}) => {
|
|
1161
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1162
|
+
return ko_cli_app_listing_none(inputs);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
//#endregion
|
|
1166
|
+
//#region ../i18n/src/paraglide/messages/cli_app_logs_empty.js
|
|
1167
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1168
|
+
/** @typedef {{}} Cli_App_Logs_EmptyInputs */
|
|
1169
|
+
const ko_cli_app_logs_empty = () => {
|
|
1170
|
+
return `실행 기록이 없습니다`;
|
|
1171
|
+
};
|
|
1172
|
+
/**
|
|
1173
|
+
|
|
1174
|
+
* @param {Cli_App_Logs_EmptyInputs} inputs
|
|
1175
|
+
* @param {{ locale?: "ko" }} options
|
|
1176
|
+
* @returns {LocalizedString}
|
|
1177
|
+
*/
|
|
1178
|
+
const cli_app_logs_empty = ((inputs = {}, options = {}) => {
|
|
1179
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1180
|
+
return ko_cli_app_logs_empty(inputs);
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
//#endregion
|
|
1184
|
+
//#region ../i18n/src/paraglide/messages/cli_app_manifest_invalid.js
|
|
1185
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1186
|
+
/** @typedef {{ problems: NonNullable<unknown> }} Cli_App_Manifest_InvalidInputs */
|
|
1187
|
+
const ko_cli_app_manifest_invalid = (i) => {
|
|
1188
|
+
return `매니페스트가 올바르지 않습니다:
|
|
1189
|
+
${i?.problems}`;
|
|
1190
|
+
};
|
|
1191
|
+
/**
|
|
1192
|
+
|
|
1193
|
+
* @param {Cli_App_Manifest_InvalidInputs} inputs
|
|
1194
|
+
* @param {{ locale?: "ko" }} options
|
|
1195
|
+
* @returns {LocalizedString}
|
|
1196
|
+
*/
|
|
1197
|
+
const cli_app_manifest_invalid = ((inputs, options = {}) => {
|
|
1198
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1199
|
+
return ko_cli_app_manifest_invalid(inputs);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
//#endregion
|
|
1203
|
+
//#region ../i18n/src/paraglide/messages/cli_app_manifest_missing.js
|
|
1204
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1205
|
+
/** @typedef {{ file: NonNullable<unknown> }} Cli_App_Manifest_MissingInputs */
|
|
1206
|
+
const ko_cli_app_manifest_missing = (i) => {
|
|
1207
|
+
return `${i?.file}이 없습니다. 앱 폴더에서 실행하거나 --dir을 주십시오`;
|
|
1208
|
+
};
|
|
1209
|
+
/**
|
|
1210
|
+
|
|
1211
|
+
* @param {Cli_App_Manifest_MissingInputs} inputs
|
|
1212
|
+
* @param {{ locale?: "ko" }} options
|
|
1213
|
+
* @returns {LocalizedString}
|
|
1214
|
+
*/
|
|
1215
|
+
const cli_app_manifest_missing = ((inputs, options = {}) => {
|
|
1216
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1217
|
+
return ko_cli_app_manifest_missing(inputs);
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
//#endregion
|
|
1221
|
+
//#region ../i18n/src/paraglide/messages/cli_app_not_registered.js
|
|
1222
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1223
|
+
/** @typedef {{ file: NonNullable<unknown> }} Cli_App_Not_RegisteredInputs */
|
|
1224
|
+
const ko_cli_app_not_registered = (i) => {
|
|
1225
|
+
return `등록된 앱이 아닙니다(${i?.file}은 있지만 .sayren/app.json이 없습니다). npx sayren app init <handle>로 만든 폴더에서 실행하십시오`;
|
|
1226
|
+
};
|
|
1227
|
+
/**
|
|
1228
|
+
|
|
1229
|
+
* @param {Cli_App_Not_RegisteredInputs} inputs
|
|
1230
|
+
* @param {{ locale?: "ko" }} options
|
|
1231
|
+
* @returns {LocalizedString}
|
|
1232
|
+
*/
|
|
1233
|
+
const cli_app_not_registered = ((inputs, options = {}) => {
|
|
1234
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1235
|
+
return ko_cli_app_not_registered(inputs);
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region ../i18n/src/paraglide/messages/cli_app_port_invalid.js
|
|
1240
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1241
|
+
/** @typedef {{ value: NonNullable<unknown> }} Cli_App_Port_InvalidInputs */
|
|
1242
|
+
const ko_cli_app_port_invalid = (i) => {
|
|
1243
|
+
return `--port가 올바르지 않습니다: ${i?.value}`;
|
|
1244
|
+
};
|
|
1245
|
+
/**
|
|
1246
|
+
|
|
1247
|
+
* @param {Cli_App_Port_InvalidInputs} inputs
|
|
1248
|
+
* @param {{ locale?: "ko" }} options
|
|
1249
|
+
* @returns {LocalizedString}
|
|
1250
|
+
*/
|
|
1251
|
+
const cli_app_port_invalid = ((inputs, options = {}) => {
|
|
1252
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1253
|
+
return ko_cli_app_port_invalid(inputs);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
//#endregion
|
|
1257
|
+
//#region ../i18n/src/paraglide/messages/cli_app_readme_missing.js
|
|
1258
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1259
|
+
/** @typedef {{ path: NonNullable<unknown> }} Cli_App_Readme_MissingInputs */
|
|
1260
|
+
const ko_cli_app_readme_missing = (i) => {
|
|
1261
|
+
return `매니페스트 readme의 파일이 없습니다: ${i?.path}`;
|
|
1262
|
+
};
|
|
1263
|
+
/**
|
|
1264
|
+
|
|
1265
|
+
* @param {Cli_App_Readme_MissingInputs} inputs
|
|
1266
|
+
* @param {{ locale?: "ko" }} options
|
|
1267
|
+
* @returns {LocalizedString}
|
|
1268
|
+
*/
|
|
1269
|
+
const cli_app_readme_missing = ((inputs, options = {}) => {
|
|
1270
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1271
|
+
return ko_cli_app_readme_missing(inputs);
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
//#endregion
|
|
1275
|
+
//#region ../i18n/src/paraglide/messages/cli_app_readme_too_large.js
|
|
1276
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1277
|
+
/** @typedef {{ limit: NonNullable<unknown>, path: NonNullable<unknown> }} Cli_App_Readme_Too_LargeInputs */
|
|
1278
|
+
const ko_cli_app_readme_too_large = (i) => {
|
|
1279
|
+
return `README가 ${i?.limit}KB를 넘습니다: ${i?.path}`;
|
|
1280
|
+
};
|
|
1281
|
+
/**
|
|
1282
|
+
|
|
1283
|
+
* @param {Cli_App_Readme_Too_LargeInputs} inputs
|
|
1284
|
+
* @param {{ locale?: "ko" }} options
|
|
1285
|
+
* @returns {LocalizedString}
|
|
1286
|
+
*/
|
|
1287
|
+
const cli_app_readme_too_large = ((inputs, options = {}) => {
|
|
1288
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1289
|
+
return ko_cli_app_readme_too_large(inputs);
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
//#endregion
|
|
1293
|
+
//#region ../i18n/src/paraglide/messages/cli_app_release_done.js
|
|
1294
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1295
|
+
/** @typedef {{ number: NonNullable<unknown>, applied: NonNullable<unknown>, pending: NonNullable<unknown> }} Cli_App_Release_DoneInputs */
|
|
1296
|
+
const ko_cli_app_release_done = (i) => {
|
|
1297
|
+
return `버전 ${i?.number}이 현재 버전입니다 — 곧바로 적용 ${i?.applied}, 재동의 대기 ${i?.pending}`;
|
|
1298
|
+
};
|
|
1299
|
+
/**
|
|
1300
|
+
|
|
1301
|
+
* @param {Cli_App_Release_DoneInputs} inputs
|
|
1302
|
+
* @param {{ locale?: "ko" }} options
|
|
1303
|
+
* @returns {LocalizedString}
|
|
1304
|
+
*/
|
|
1305
|
+
const cli_app_release_done = ((inputs, options = {}) => {
|
|
1306
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1307
|
+
return ko_cli_app_release_done(inputs);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region ../i18n/src/paraglide/messages/cli_app_secret_deleted.js
|
|
1312
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1313
|
+
/** @typedef {{ name: NonNullable<unknown> }} Cli_App_Secret_DeletedInputs */
|
|
1314
|
+
const ko_cli_app_secret_deleted = (i) => {
|
|
1315
|
+
return `비밀 값 ${i?.name}을 지웠습니다`;
|
|
1316
|
+
};
|
|
1317
|
+
/**
|
|
1318
|
+
|
|
1319
|
+
* @param {Cli_App_Secret_DeletedInputs} inputs
|
|
1320
|
+
* @param {{ locale?: "ko" }} options
|
|
1321
|
+
* @returns {LocalizedString}
|
|
1322
|
+
*/
|
|
1323
|
+
const cli_app_secret_deleted = ((inputs, options = {}) => {
|
|
1324
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1325
|
+
return ko_cli_app_secret_deleted(inputs);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
//#endregion
|
|
1329
|
+
//#region ../i18n/src/paraglide/messages/cli_app_secret_empty.js
|
|
1330
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1331
|
+
/** @typedef {{}} Cli_App_Secret_EmptyInputs */
|
|
1332
|
+
const ko_cli_app_secret_empty = () => {
|
|
1333
|
+
return `비밀 값이 없습니다`;
|
|
1334
|
+
};
|
|
1335
|
+
/**
|
|
1336
|
+
|
|
1337
|
+
* @param {Cli_App_Secret_EmptyInputs} inputs
|
|
1338
|
+
* @param {{ locale?: "ko" }} options
|
|
1339
|
+
* @returns {LocalizedString}
|
|
1340
|
+
*/
|
|
1341
|
+
const cli_app_secret_empty = ((inputs = {}, options = {}) => {
|
|
1342
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1343
|
+
return ko_cli_app_secret_empty(inputs);
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region ../i18n/src/paraglide/messages/cli_app_secret_in_source.js
|
|
1348
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1349
|
+
/** @typedef {{}} Cli_App_Secret_In_SourceInputs */
|
|
1350
|
+
const ko_cli_app_secret_in_source = () => {
|
|
1351
|
+
return `번들에 액세스 토큰·PG 키·개인 키·앱 배포 키 모양의 값이 들어 있습니다. 비밀 값은 npx sayren app secret put <이름>으로 넣으십시오 (SECRET_IN_SOURCE)`;
|
|
1352
|
+
};
|
|
1353
|
+
/**
|
|
1354
|
+
|
|
1355
|
+
* @param {Cli_App_Secret_In_SourceInputs} inputs
|
|
1356
|
+
* @param {{ locale?: "ko" }} options
|
|
1357
|
+
* @returns {LocalizedString}
|
|
1358
|
+
*/
|
|
1359
|
+
const cli_app_secret_in_source = ((inputs = {}, options = {}) => {
|
|
1360
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1361
|
+
return ko_cli_app_secret_in_source(inputs);
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
//#endregion
|
|
1365
|
+
//#region ../i18n/src/paraglide/messages/cli_app_secret_name_required.js
|
|
1366
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1367
|
+
/** @typedef {{}} Cli_App_Secret_Name_RequiredInputs */
|
|
1368
|
+
const ko_cli_app_secret_name_required = () => {
|
|
1369
|
+
return `비밀 값 이름이 필요합니다. 예: npx sayren app secret put RESEND_API_KEY`;
|
|
1370
|
+
};
|
|
1371
|
+
/**
|
|
1372
|
+
|
|
1373
|
+
* @param {Cli_App_Secret_Name_RequiredInputs} inputs
|
|
1374
|
+
* @param {{ locale?: "ko" }} options
|
|
1375
|
+
* @returns {LocalizedString}
|
|
1376
|
+
*/
|
|
1377
|
+
const cli_app_secret_name_required = ((inputs = {}, options = {}) => {
|
|
1378
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1379
|
+
return ko_cli_app_secret_name_required(inputs);
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region ../i18n/src/paraglide/messages/cli_app_secret_saved.js
|
|
1384
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1385
|
+
/** @typedef {{ name: NonNullable<unknown> }} Cli_App_Secret_SavedInputs */
|
|
1386
|
+
const ko_cli_app_secret_saved = (i) => {
|
|
1387
|
+
return `비밀 값 ${i?.name}을 저장했습니다`;
|
|
1388
|
+
};
|
|
1389
|
+
/**
|
|
1390
|
+
|
|
1391
|
+
* @param {Cli_App_Secret_SavedInputs} inputs
|
|
1392
|
+
* @param {{ locale?: "ko" }} options
|
|
1393
|
+
* @returns {LocalizedString}
|
|
1394
|
+
*/
|
|
1395
|
+
const cli_app_secret_saved = ((inputs, options = {}) => {
|
|
1396
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1397
|
+
return ko_cli_app_secret_saved(inputs);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region ../i18n/src/paraglide/messages/cli_app_secret_value_required.js
|
|
1402
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1403
|
+
/** @typedef {{ name: NonNullable<unknown> }} Cli_App_Secret_Value_RequiredInputs */
|
|
1404
|
+
const ko_cli_app_secret_value_required = (i) => {
|
|
1405
|
+
return `값을 표준 입력으로 주십시오. 예: printf '%s' "$VALUE" | npx sayren app secret put ${i?.name}`;
|
|
1406
|
+
};
|
|
1407
|
+
/**
|
|
1408
|
+
|
|
1409
|
+
* @param {Cli_App_Secret_Value_RequiredInputs} inputs
|
|
1410
|
+
* @param {{ locale?: "ko" }} options
|
|
1411
|
+
* @returns {LocalizedString}
|
|
1412
|
+
*/
|
|
1413
|
+
const cli_app_secret_value_required = ((inputs, options = {}) => {
|
|
1414
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1415
|
+
return ko_cli_app_secret_value_required(inputs);
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
//#endregion
|
|
1419
|
+
//#region ../i18n/src/paraglide/messages/cli_app_since_invalid.js
|
|
1420
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1421
|
+
/** @typedef {{ value: NonNullable<unknown> }} Cli_App_Since_InvalidInputs */
|
|
1422
|
+
const ko_cli_app_since_invalid = (i) => {
|
|
1423
|
+
return `--since 형식이 아닙니다: ${i?.value} (예: 30m, 1h, 2d)`;
|
|
1424
|
+
};
|
|
1425
|
+
/**
|
|
1426
|
+
|
|
1427
|
+
* @param {Cli_App_Since_InvalidInputs} inputs
|
|
1428
|
+
* @param {{ locale?: "ko" }} options
|
|
1429
|
+
* @returns {LocalizedString}
|
|
1430
|
+
*/
|
|
1431
|
+
const cli_app_since_invalid = ((inputs, options = {}) => {
|
|
1432
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1433
|
+
return ko_cli_app_since_invalid(inputs);
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region ../i18n/src/paraglide/messages/cli_app_state_invalid.js
|
|
1438
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1439
|
+
/** @typedef {{ file: NonNullable<unknown> }} Cli_App_State_InvalidInputs */
|
|
1440
|
+
const ko_cli_app_state_invalid = (i) => {
|
|
1441
|
+
return `${i?.file}을 읽을 수 없습니다. 파일을 지우고 npx sayren app init을 다시 실행하십시오`;
|
|
1442
|
+
};
|
|
1443
|
+
/**
|
|
1444
|
+
|
|
1445
|
+
* @param {Cli_App_State_InvalidInputs} inputs
|
|
1446
|
+
* @param {{ locale?: "ko" }} options
|
|
1447
|
+
* @returns {LocalizedString}
|
|
1448
|
+
*/
|
|
1449
|
+
const cli_app_state_invalid = ((inputs, options = {}) => {
|
|
1450
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1451
|
+
return ko_cli_app_state_invalid(inputs);
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region ../i18n/src/paraglide/messages/cli_app_status.js
|
|
1456
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1457
|
+
/** @typedef {{ name: NonNullable<unknown>, handle: NonNullable<unknown>, appId: NonNullable<unknown>, status: NonNullable<unknown>, current: NonNullable<unknown>, active: NonNullable<unknown>, pending: NonNullable<unknown> }} Cli_App_StatusInputs */
|
|
1458
|
+
const ko_cli_app_status = (i) => {
|
|
1459
|
+
return `${i?.name} (${i?.handle}, ${i?.appId})
|
|
1460
|
+
상태 ${i?.status} · 현재 버전 ${i?.current} · 설치 ${i?.active} · 재동의 대기 ${i?.pending}
|
|
1461
|
+
최근 버전:`;
|
|
1462
|
+
};
|
|
1463
|
+
/**
|
|
1464
|
+
|
|
1465
|
+
* @param {Cli_App_StatusInputs} inputs
|
|
1466
|
+
* @param {{ locale?: "ko" }} options
|
|
1467
|
+
* @returns {LocalizedString}
|
|
598
1468
|
*/
|
|
1469
|
+
const cli_app_status = ((inputs, options = {}) => {
|
|
1470
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1471
|
+
return ko_cli_app_status(inputs);
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
//#endregion
|
|
1475
|
+
//#region ../i18n/src/paraglide/messages/cli_app_subcommand_unknown.js
|
|
1476
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1477
|
+
/** @typedef {{ command: NonNullable<unknown> }} Cli_App_Subcommand_UnknownInputs */
|
|
1478
|
+
const ko_cli_app_subcommand_unknown = (i) => {
|
|
1479
|
+
return `알 수 없는 앱 명령: ${i?.command}
|
|
1480
|
+
|
|
1481
|
+
사용법: npx sayren app <init|dev|trigger|verify|deploy|release|status|logs|secret|dev-store>`;
|
|
1482
|
+
};
|
|
599
1483
|
/**
|
|
600
|
-
|
|
601
|
-
*
|
|
602
|
-
* @
|
|
603
|
-
* @
|
|
604
|
-
* @template {MessageMarkupSchema} [Markup = MessageMarkupSchema]
|
|
605
|
-
* @typedef {{
|
|
606
|
-
* readonly __paraglide?: {
|
|
607
|
-
* inputs: Inputs;
|
|
608
|
-
* options: Options;
|
|
609
|
-
* markup: Markup;
|
|
610
|
-
* };
|
|
611
|
-
* }} MessageMetadata
|
|
1484
|
+
|
|
1485
|
+
* @param {Cli_App_Subcommand_UnknownInputs} inputs
|
|
1486
|
+
* @param {{ locale?: "ko" }} options
|
|
1487
|
+
* @returns {LocalizedString}
|
|
612
1488
|
*/
|
|
1489
|
+
const cli_app_subcommand_unknown = ((inputs, options = {}) => {
|
|
1490
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1491
|
+
return ko_cli_app_subcommand_unknown(inputs);
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
//#endregion
|
|
1495
|
+
//#region ../i18n/src/paraglide/messages/cli_app_trigger_data_invalid.js
|
|
1496
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1497
|
+
/** @typedef {{}} Cli_App_Trigger_Data_InvalidInputs */
|
|
1498
|
+
const ko_cli_app_trigger_data_invalid = () => {
|
|
1499
|
+
return `--data가 JSON이 아닙니다`;
|
|
1500
|
+
};
|
|
613
1501
|
/**
|
|
614
|
-
|
|
615
|
-
*
|
|
616
|
-
* @
|
|
617
|
-
*
|
|
618
|
-
* value: string;
|
|
619
|
-
* } | {
|
|
620
|
-
* type: "markup-start";
|
|
621
|
-
* name: string;
|
|
622
|
-
* options: MessageMarkupOptions;
|
|
623
|
-
* attributes: MessageMarkupAttributes;
|
|
624
|
-
* } | {
|
|
625
|
-
* type: "markup-end";
|
|
626
|
-
* name: string;
|
|
627
|
-
* options: MessageMarkupOptions;
|
|
628
|
-
* attributes: MessageMarkupAttributes;
|
|
629
|
-
* } | {
|
|
630
|
-
* type: "markup-standalone";
|
|
631
|
-
* name: string;
|
|
632
|
-
* options: MessageMarkupOptions;
|
|
633
|
-
* attributes: MessageMarkupAttributes;
|
|
634
|
-
* }} MessagePart
|
|
1502
|
+
|
|
1503
|
+
* @param {Cli_App_Trigger_Data_InvalidInputs} inputs
|
|
1504
|
+
* @param {{ locale?: "ko" }} options
|
|
1505
|
+
* @returns {LocalizedString}
|
|
635
1506
|
*/
|
|
1507
|
+
const cli_app_trigger_data_invalid = ((inputs = {}, options = {}) => {
|
|
1508
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1509
|
+
return ko_cli_app_trigger_data_invalid(inputs);
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region ../i18n/src/paraglide/messages/cli_app_trigger_failed.js
|
|
1514
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1515
|
+
/** @typedef {{ status: NonNullable<unknown> }} Cli_App_Trigger_FailedInputs */
|
|
1516
|
+
const ko_cli_app_trigger_failed = (i) => {
|
|
1517
|
+
return `로컬 실행에 보내지 못했습니다(HTTP ${i?.status}). npx sayren app dev가 떠 있는지 확인하십시오`;
|
|
1518
|
+
};
|
|
636
1519
|
/**
|
|
637
|
-
|
|
638
|
-
*
|
|
639
|
-
* @
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
* @typedef {(inputs?: Record<string, never>) => LocalizedString} MessageFunction
|
|
1520
|
+
|
|
1521
|
+
* @param {Cli_App_Trigger_FailedInputs} inputs
|
|
1522
|
+
* @param {{ locale?: "ko" }} options
|
|
1523
|
+
* @returns {LocalizedString}
|
|
643
1524
|
*/
|
|
1525
|
+
const cli_app_trigger_failed = ((inputs, options = {}) => {
|
|
1526
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1527
|
+
return ko_cli_app_trigger_failed(inputs);
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region ../i18n/src/paraglide/messages/cli_app_trigger_type_required.js
|
|
1532
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1533
|
+
/** @typedef {{}} Cli_App_Trigger_Type_RequiredInputs */
|
|
1534
|
+
const ko_cli_app_trigger_type_required = () => {
|
|
1535
|
+
return `이벤트 종류가 필요합니다. 예: npx sayren app trigger ORDER.PAID --order ord_…`;
|
|
1536
|
+
};
|
|
644
1537
|
/**
|
|
645
|
-
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
* @template {string} T
|
|
650
|
-
*
|
|
651
|
-
* @example
|
|
652
|
-
* * m.hello({ name: 'world' }, { locale: "en" })
|
|
653
|
-
*
|
|
654
|
-
* @typedef {(params: Record<string, never>, options: { locale: T }) => LocalizedString} MessageBundleFunction
|
|
1538
|
+
|
|
1539
|
+
* @param {Cli_App_Trigger_Type_RequiredInputs} inputs
|
|
1540
|
+
* @param {{ locale?: "ko" }} options
|
|
1541
|
+
* @returns {LocalizedString}
|
|
655
1542
|
*/
|
|
1543
|
+
const cli_app_trigger_type_required = ((inputs = {}, options = {}) => {
|
|
1544
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1545
|
+
return ko_cli_app_trigger_type_required(inputs);
|
|
1546
|
+
});
|
|
656
1547
|
|
|
657
1548
|
//#endregion
|
|
658
|
-
//#region ../i18n/src/
|
|
1549
|
+
//#region ../i18n/src/paraglide/messages/cli_app_ui_index_missing.js
|
|
1550
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1551
|
+
/** @typedef {{ dir: NonNullable<unknown> }} Cli_App_Ui_Index_MissingInputs */
|
|
1552
|
+
const ko_cli_app_ui_index_missing = (i) => {
|
|
1553
|
+
return `매니페스트에 ui.adminPage가 있지만 ${i?.dir}/index.html이 없습니다`;
|
|
1554
|
+
};
|
|
659
1555
|
/**
|
|
660
|
-
|
|
661
|
-
*
|
|
662
|
-
*
|
|
1556
|
+
|
|
1557
|
+
* @param {Cli_App_Ui_Index_MissingInputs} inputs
|
|
1558
|
+
* @param {{ locale?: "ko" }} options
|
|
1559
|
+
* @returns {LocalizedString}
|
|
663
1560
|
*/
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const value = new URLSearchParams(search).get(LOCALE_QUERY_PARAM);
|
|
668
|
-
if (!value) return void 0;
|
|
669
|
-
return toLocale(value) ?? toLocale(value.split("-")[0]);
|
|
670
|
-
}
|
|
671
|
-
defineCustomServerStrategy("custom-query", { getLocale: (request) => request ? localeFromSearch(new URL(request.url).search) : void 0 });
|
|
672
|
-
/** 브라우저의 현재 주소 쿼리 — DOM 타입에 기대지 않는다(DOM lib 없는 소비자, 예: mcp-gateway도 이 소스를 타입 검사한다) */
|
|
673
|
-
function currentSearch() {
|
|
674
|
-
return globalThis.location?.search;
|
|
675
|
-
}
|
|
676
|
-
defineCustomClientStrategy("custom-query", {
|
|
677
|
-
getLocale: () => {
|
|
678
|
-
const search = currentSearch();
|
|
679
|
-
return search === void 0 ? void 0 : localeFromSearch(search);
|
|
680
|
-
},
|
|
681
|
-
setLocale: () => {}
|
|
1561
|
+
const cli_app_ui_index_missing = ((inputs, options = {}) => {
|
|
1562
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1563
|
+
return ko_cli_app_ui_index_missing(inputs);
|
|
682
1564
|
});
|
|
683
1565
|
|
|
684
1566
|
//#endregion
|
|
685
|
-
//#region ../i18n/src/
|
|
1567
|
+
//#region ../i18n/src/paraglide/messages/cli_app_ui_too_large.js
|
|
1568
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1569
|
+
/** @typedef {{ limit: NonNullable<unknown> }} Cli_App_Ui_Too_LargeInputs */
|
|
1570
|
+
const ko_cli_app_ui_too_large = (i) => {
|
|
1571
|
+
return `앱 화면 파일이 상한(${i?.limit})을 넘습니다 (ARTIFACT_TOO_LARGE)`;
|
|
1572
|
+
};
|
|
686
1573
|
/**
|
|
687
|
-
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
* ```ts
|
|
692
|
-
* export const ROLE_LABEL = lazyMessages<StoreRole>({ OWNER: m.store_shell_role_owner, ... });
|
|
693
|
-
* ```
|
|
1574
|
+
|
|
1575
|
+
* @param {Cli_App_Ui_Too_LargeInputs} inputs
|
|
1576
|
+
* @param {{ locale?: "ko" }} options
|
|
1577
|
+
* @returns {LocalizedString}
|
|
694
1578
|
*/
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
get: () => table[key]()
|
|
700
|
-
});
|
|
701
|
-
return Object.freeze(labels);
|
|
702
|
-
}
|
|
1579
|
+
const cli_app_ui_too_large = ((inputs, options = {}) => {
|
|
1580
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
1581
|
+
return ko_cli_app_ui_too_large(inputs);
|
|
1582
|
+
});
|
|
703
1583
|
|
|
704
1584
|
//#endregion
|
|
705
|
-
//#region ../i18n/src/paraglide/messages/
|
|
1585
|
+
//#region ../i18n/src/paraglide/messages/cli_app_uploading.js
|
|
706
1586
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
707
|
-
/** @typedef {{}}
|
|
708
|
-
const
|
|
709
|
-
return
|
|
1587
|
+
/** @typedef {{ size: NonNullable<unknown>, ui: NonNullable<unknown> }} Cli_App_UploadingInputs */
|
|
1588
|
+
const ko_cli_app_uploading = (i) => {
|
|
1589
|
+
return `올리는 중 — 서버 번들 ${i?.size}(gzip), 화면 파일 ${i?.ui}개`;
|
|
710
1590
|
};
|
|
711
1591
|
/**
|
|
712
1592
|
|
|
713
|
-
* @param {
|
|
1593
|
+
* @param {Cli_App_UploadingInputs} inputs
|
|
714
1594
|
* @param {{ locale?: "ko" }} options
|
|
715
1595
|
* @returns {LocalizedString}
|
|
716
1596
|
*/
|
|
717
|
-
const
|
|
1597
|
+
const cli_app_uploading = ((inputs, options = {}) => {
|
|
718
1598
|
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
719
|
-
return
|
|
1599
|
+
return ko_cli_app_uploading(inputs);
|
|
720
1600
|
});
|
|
721
1601
|
|
|
722
1602
|
//#endregion
|
|
723
|
-
//#region ../i18n/src/paraglide/messages/
|
|
1603
|
+
//#region ../i18n/src/paraglide/messages/cli_app_verify_ok.js
|
|
724
1604
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
725
|
-
/** @typedef {{
|
|
726
|
-
const
|
|
727
|
-
return
|
|
1605
|
+
/** @typedef {{ events: NonNullable<unknown>, http: NonNullable<unknown>, size: NonNullable<unknown>, ui: NonNullable<unknown>, icon: NonNullable<unknown>, readme: NonNullable<unknown> }} Cli_App_Verify_OkInputs */
|
|
1606
|
+
const ko_cli_app_verify_ok = (i) => {
|
|
1607
|
+
return `검사 통과 — 이벤트 ${i?.events} · HTTP ${i?.http} · 서버 번들 ${i?.size}(gzip) · 화면 파일 ${i?.ui}개 · 아이콘 ${i?.icon} · README ${i?.readme}`;
|
|
728
1608
|
};
|
|
729
1609
|
/**
|
|
730
1610
|
|
|
731
|
-
* @param {
|
|
1611
|
+
* @param {Cli_App_Verify_OkInputs} inputs
|
|
732
1612
|
* @param {{ locale?: "ko" }} options
|
|
733
1613
|
* @returns {LocalizedString}
|
|
734
1614
|
*/
|
|
735
|
-
const
|
|
1615
|
+
const cli_app_verify_ok = ((inputs, options = {}) => {
|
|
736
1616
|
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
737
|
-
return
|
|
1617
|
+
return ko_cli_app_verify_ok(inputs);
|
|
738
1618
|
});
|
|
739
1619
|
|
|
740
1620
|
//#endregion
|
|
741
|
-
//#region ../i18n/src/paraglide/messages/
|
|
1621
|
+
//#region ../i18n/src/paraglide/messages/cli_app_version_number_required.js
|
|
742
1622
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
743
|
-
/** @typedef {{
|
|
744
|
-
const
|
|
745
|
-
return
|
|
1623
|
+
/** @typedef {{}} Cli_App_Version_Number_RequiredInputs */
|
|
1624
|
+
const ko_cli_app_version_number_required = () => {
|
|
1625
|
+
return `버전 번호가 필요합니다. 예: npx sayren app release 1`;
|
|
746
1626
|
};
|
|
747
1627
|
/**
|
|
748
1628
|
|
|
749
|
-
* @param {
|
|
1629
|
+
* @param {Cli_App_Version_Number_RequiredInputs} inputs
|
|
750
1630
|
* @param {{ locale?: "ko" }} options
|
|
751
1631
|
* @returns {LocalizedString}
|
|
752
1632
|
*/
|
|
753
|
-
const
|
|
1633
|
+
const cli_app_version_number_required = ((inputs = {}, options = {}) => {
|
|
754
1634
|
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
755
|
-
return
|
|
1635
|
+
return ko_cli_app_version_number_required(inputs);
|
|
756
1636
|
});
|
|
757
1637
|
|
|
758
1638
|
//#endregion
|
|
759
|
-
//#region ../i18n/src/paraglide/messages/
|
|
1639
|
+
//#region ../i18n/src/paraglide/messages/cli_app_warning.js
|
|
760
1640
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
761
|
-
/** @typedef {{}}
|
|
762
|
-
const
|
|
763
|
-
return
|
|
1641
|
+
/** @typedef {{ message: NonNullable<unknown> }} Cli_App_WarningInputs */
|
|
1642
|
+
const ko_cli_app_warning = (i) => {
|
|
1643
|
+
return `경고: ${i?.message}`;
|
|
764
1644
|
};
|
|
765
1645
|
/**
|
|
766
1646
|
|
|
767
|
-
* @param {
|
|
1647
|
+
* @param {Cli_App_WarningInputs} inputs
|
|
768
1648
|
* @param {{ locale?: "ko" }} options
|
|
769
1649
|
* @returns {LocalizedString}
|
|
770
1650
|
*/
|
|
771
|
-
const
|
|
1651
|
+
const cli_app_warning = ((inputs, options = {}) => {
|
|
772
1652
|
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
773
|
-
return
|
|
1653
|
+
return ko_cli_app_warning(inputs);
|
|
774
1654
|
});
|
|
775
1655
|
|
|
776
1656
|
//#endregion
|
|
@@ -4332,6 +5212,7 @@ const ko_cli_usage = (i) => {
|
|
|
4332
5212
|
npx sayren deploy [--no-publish] [--skip-build] [--message <메모>] [--force [--yes]]
|
|
4333
5213
|
npx sayren status
|
|
4334
5214
|
npx sayren verify
|
|
5215
|
+
npx sayren app <명령> # 서드파티 앱(init·dev·trigger·verify·deploy·release·status·logs·secret·dev-store)
|
|
4335
5216
|
|
|
4336
5217
|
명령:
|
|
4337
5218
|
login Sayren 계정으로 로그인합니다. 로그인 토큰은 사용자 설정 폴더(~/.config/sayren/credentials.json)에 둡니다
|
|
@@ -4342,6 +5223,7 @@ const ko_cli_usage = (i) => {
|
|
|
4342
5223
|
deploy 로컬 검사 → 로컬 빌드 → 작업 트리 올리기 → 빌드 → 발행. 원격이 pull 이후 바뀌었으면 덮어쓰지 않습니다
|
|
4343
5224
|
status 사이트·발행 버전·최근 빌드·편집 환경
|
|
4344
5225
|
verify 로컬 규칙·경로·크기 검사(서버 빌드와 같은 규칙)
|
|
5226
|
+
app 서드파티 앱 — init <handle>로 만들고 dev로 로컬 실행, deploy로 버전을 올리고 release로 적용합니다. 자세히: https://docs.sayren.app/guides/apps-getting-started
|
|
4345
5227
|
|
|
4346
5228
|
토큰은 Sayren 계정 토큰만 받고 --token → SAYREN_TOKEN 환경 변수 → 로그인(credentials.json) 순서로 읽습니다.
|
|
4347
5229
|
계정 토큰은 계정에 발급됩니다 — 상점은 --store → SAYREN_STORE_ID → 폴더 설정 순서로 고릅니다(하나면 자동).
|
|
@@ -4364,6 +5246,14 @@ const ko_cli_usage = (i) => {
|
|
|
4364
5246
|
--api-origin <주소> api 주소(기본 https://api.sayren.app, SAYREN_API_ORIGIN). https만, localhost만 http
|
|
4365
5247
|
--accounts-origin <주소> 계정 서버 주소(기본: api 주소에 맞춘 값, SAYREN_ACCOUNTS_ORIGIN)
|
|
4366
5248
|
--token <토큰> 토큰을 직접 줍니다. 셸 기록·프로세스 목록에 남으니 login·SAYREN_TOKEN을 권합니다
|
|
5249
|
+
--key <키> app: 앱 배포 키(developer.sayren.app에서 발행, SAYREN_APP_KEY). CI에서 deploy·release·status·logs·secret에 씁니다
|
|
5250
|
+
-m, --message <메모> app deploy: 버전 메모
|
|
5251
|
+
--name <이름> app init: 앱 이름(기본: handle)
|
|
5252
|
+
--port <포트> app dev·trigger: 로컬 포트(기본 4020)
|
|
5253
|
+
--since <기간> app logs: 최근 기간만(30m·1h·2d)
|
|
5254
|
+
--tail app logs: 새 기록을 계속 봅니다
|
|
5255
|
+
--data <json> app trigger: 이벤트 데이터
|
|
5256
|
+
--order <orderId> app trigger: 주문 id를 데이터에 넣습니다
|
|
4367
5257
|
--lang <로케일> 메시지 언어(기본: SAYREN_LANG → 시스템 로케일 → ko)
|
|
4368
5258
|
|
|
4369
5259
|
종료 코드: 0 성공 · 1 실패 · 3 승인 대기(에이전트 토큰)`;
|
|
@@ -5437,7 +6327,7 @@ const mcp_rule_checkout_availability_title = ((inputs = {}, options = {}) => {
|
|
|
5437
6327
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5438
6328
|
/** @typedef {{}} Mcp_Rule_Checkout_Availability_WhyInputs */
|
|
5439
6329
|
const ko_mcp_rule_checkout_availability_why = () => {
|
|
5440
|
-
return
|
|
6330
|
+
return `결제수단이 없거나 공개한 쇼핑몰의 실결제가 아직 열리지 않은 상점은 결제 요청이 409 \`PAYMENT_NOT_CONFIGURED\`다. 버튼을 그대로 두면 구매자가 담고 주문서까지 가서야 결제가 안 된다는 것을 안다`;
|
|
5441
6331
|
};
|
|
5442
6332
|
/**
|
|
5443
6333
|
|
|
@@ -6314,6 +7204,82 @@ const mcp_rule_payment_return_route_why = ((inputs = {}, options = {}) => {
|
|
|
6314
7204
|
return ko_mcp_rule_payment_return_route_why(inputs);
|
|
6315
7205
|
});
|
|
6316
7206
|
|
|
7207
|
+
//#endregion
|
|
7208
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_product_link_example.js
|
|
7209
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
7210
|
+
/** @typedef {{}} Mcp_Rule_Product_Link_ExampleInputs */
|
|
7211
|
+
const ko_mcp_rule_product_link_example = () => {
|
|
7212
|
+
return `import { productPathParam } from "../lib/product-path";
|
|
7213
|
+
|
|
7214
|
+
<Link to="/products/$productId" params={{ productId: productPathParam(product) }}>
|
|
7215
|
+
{product.name}
|
|
7216
|
+
</Link>`;
|
|
7217
|
+
};
|
|
7218
|
+
/**
|
|
7219
|
+
|
|
7220
|
+
* @param {Mcp_Rule_Product_Link_ExampleInputs} inputs
|
|
7221
|
+
* @param {{ locale?: "ko" }} options
|
|
7222
|
+
* @returns {LocalizedString}
|
|
7223
|
+
*/
|
|
7224
|
+
const mcp_rule_product_link_example = ((inputs = {}, options = {}) => {
|
|
7225
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
7226
|
+
return ko_mcp_rule_product_link_example(inputs);
|
|
7227
|
+
});
|
|
7228
|
+
|
|
7229
|
+
//#endregion
|
|
7230
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_product_link_fix.js
|
|
7231
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
7232
|
+
/** @typedef {{}} Mcp_Rule_Product_Link_FixInputs */
|
|
7233
|
+
const ko_mcp_rule_product_link_fix = () => {
|
|
7234
|
+
return `링크의 경로 값을 \`productPathParam(product)\`(\`src/lib/product-path.ts\`)로 만든다. 상품번호가 있으면 번호, 없는 옛 응답이면 상품 id다. 장바구니 담기·주문서 같은 서버 요청에는 계속 \`productId\`를 보낸다`;
|
|
7235
|
+
};
|
|
7236
|
+
/**
|
|
7237
|
+
|
|
7238
|
+
* @param {Mcp_Rule_Product_Link_FixInputs} inputs
|
|
7239
|
+
* @param {{ locale?: "ko" }} options
|
|
7240
|
+
* @returns {LocalizedString}
|
|
7241
|
+
*/
|
|
7242
|
+
const mcp_rule_product_link_fix = ((inputs = {}, options = {}) => {
|
|
7243
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
7244
|
+
return ko_mcp_rule_product_link_fix(inputs);
|
|
7245
|
+
});
|
|
7246
|
+
|
|
7247
|
+
//#endregion
|
|
7248
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_product_link_title.js
|
|
7249
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
7250
|
+
/** @typedef {{}} Mcp_Rule_Product_Link_TitleInputs */
|
|
7251
|
+
const ko_mcp_rule_product_link_title = () => {
|
|
7252
|
+
return `상품 링크는 상품번호 주소로 그린다(\`/products/{productNo}\`)`;
|
|
7253
|
+
};
|
|
7254
|
+
/**
|
|
7255
|
+
|
|
7256
|
+
* @param {Mcp_Rule_Product_Link_TitleInputs} inputs
|
|
7257
|
+
* @param {{ locale?: "ko" }} options
|
|
7258
|
+
* @returns {LocalizedString}
|
|
7259
|
+
*/
|
|
7260
|
+
const mcp_rule_product_link_title = ((inputs = {}, options = {}) => {
|
|
7261
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
7262
|
+
return ko_mcp_rule_product_link_title(inputs);
|
|
7263
|
+
});
|
|
7264
|
+
|
|
7265
|
+
//#endregion
|
|
7266
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_product_link_why.js
|
|
7267
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
7268
|
+
/** @typedef {{}} Mcp_Rule_Product_Link_WhyInputs */
|
|
7269
|
+
const ko_mcp_rule_product_link_why = () => {
|
|
7270
|
+
return `상품 주소는 상점 안에서 유일한 숫자 상품번호다. 상품 id(\`prod_…\`) 주소도 열리지만 상세 화면이 상품번호 주소로 301 이동시키므로 링크마다 왕복이 한 번 더 생기고 공유·검색 주소가 둘로 갈린다`;
|
|
7271
|
+
};
|
|
7272
|
+
/**
|
|
7273
|
+
|
|
7274
|
+
* @param {Mcp_Rule_Product_Link_WhyInputs} inputs
|
|
7275
|
+
* @param {{ locale?: "ko" }} options
|
|
7276
|
+
* @returns {LocalizedString}
|
|
7277
|
+
*/
|
|
7278
|
+
const mcp_rule_product_link_why = ((inputs = {}, options = {}) => {
|
|
7279
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
7280
|
+
return ko_mcp_rule_product_link_why(inputs);
|
|
7281
|
+
});
|
|
7282
|
+
|
|
6317
7283
|
//#endregion
|
|
6318
7284
|
//#region ../i18n/src/paraglide/messages/mcp_rule_server_only_env_example.js
|
|
6319
7285
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -7063,6 +8029,24 @@ const mcp_verify_payment_return_route = ((inputs = {}, options = {}) => {
|
|
|
7063
8029
|
return ko_mcp_verify_payment_return_route(inputs);
|
|
7064
8030
|
});
|
|
7065
8031
|
|
|
8032
|
+
//#endregion
|
|
8033
|
+
//#region ../i18n/src/paraglide/messages/mcp_verify_product_link.js
|
|
8034
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
8035
|
+
/** @typedef {{}} Mcp_Verify_Product_LinkInputs */
|
|
8036
|
+
const ko_mcp_verify_product_link = () => {
|
|
8037
|
+
return `상품 링크의 경로 값이 상품 id다 — \`productPathParam(product)\`로 상품번호 주소를 쓴다`;
|
|
8038
|
+
};
|
|
8039
|
+
/**
|
|
8040
|
+
|
|
8041
|
+
* @param {Mcp_Verify_Product_LinkInputs} inputs
|
|
8042
|
+
* @param {{ locale?: "ko" }} options
|
|
8043
|
+
* @returns {LocalizedString}
|
|
8044
|
+
*/
|
|
8045
|
+
const mcp_verify_product_link = ((inputs = {}, options = {}) => {
|
|
8046
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
8047
|
+
return ko_mcp_verify_product_link(inputs);
|
|
8048
|
+
});
|
|
8049
|
+
|
|
7066
8050
|
//#endregion
|
|
7067
8051
|
//#region ../i18n/src/paraglide/messages/mcp_verify_server_only_env.js
|
|
7068
8052
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -7153,6 +8137,26 @@ const mcp_verify_whole_project = ((inputs = {}, options = {}) => {
|
|
|
7153
8137
|
return ko_mcp_verify_whole_project(inputs);
|
|
7154
8138
|
});
|
|
7155
8139
|
|
|
8140
|
+
//#endregion
|
|
8141
|
+
//#region ../i18n/src/lazy-messages.ts
|
|
8142
|
+
/**
|
|
8143
|
+
* 라벨 표 — 값을 읽을 때마다 메시지 함수를 불러 그 순간의 로케일 문구를 돌려준다(이슈 #73).
|
|
8144
|
+
* 모듈을 읽을 때 문구를 만들면 서버 렌더링에서 요청 로케일이 아니라 기동 시점 로케일로 굳는다.
|
|
8145
|
+
* 타입은 `Record<K, string>`이라 호출부(`LABEL[key]`, `Object.entries(LABEL)`)를 바꾸지 않는다.
|
|
8146
|
+
*
|
|
8147
|
+
* ```ts
|
|
8148
|
+
* export const ROLE_LABEL = lazyMessages<StoreRole>({ OWNER: m.store_shell_role_owner, ... });
|
|
8149
|
+
* ```
|
|
8150
|
+
*/
|
|
8151
|
+
function lazyMessages(table) {
|
|
8152
|
+
const labels = {};
|
|
8153
|
+
for (const key of Object.keys(table)) Object.defineProperty(labels, key, {
|
|
8154
|
+
enumerable: true,
|
|
8155
|
+
get: () => table[key]()
|
|
8156
|
+
});
|
|
8157
|
+
return Object.freeze(labels);
|
|
8158
|
+
}
|
|
8159
|
+
|
|
7156
8160
|
//#endregion
|
|
7157
8161
|
//#region ../i18n/src/process-locale.ts
|
|
7158
8162
|
/**
|
|
@@ -7202,47 +8206,6 @@ function takeLangFlag(argv) {
|
|
|
7202
8206
|
};
|
|
7203
8207
|
}
|
|
7204
8208
|
|
|
7205
|
-
//#endregion
|
|
7206
|
-
//#region package.json
|
|
7207
|
-
var version = "0.4.0";
|
|
7208
|
-
|
|
7209
|
-
//#endregion
|
|
7210
|
-
//#region src/client.ts
|
|
7211
|
-
/**
|
|
7212
|
-
* 관리 API 클라이언트 — `@sayren/store-sdk`(계약의 원천)를 쓴다. User-Agent `sayren-cli/{버전}`은 api가 버전의 만든 경로
|
|
7213
|
-
* (`createdVia: CLI`)를 적는 표시다(권한과 무관하다. 에이전트용 액세스 토큰이면 행위자는 여전히 에이전트이고 발행은 승인 대기다).
|
|
7214
|
-
* 토큰은 헤더에만 싣고 화면·로그에 찍지 않는다.
|
|
7215
|
-
*/
|
|
7216
|
-
const CLI_VERSION = version;
|
|
7217
|
-
const CLI_USER_AGENT = `sayren-cli/${CLI_VERSION}`;
|
|
7218
|
-
/**
|
|
7219
|
-
* 계정 토큰(#71)을 Bearer로, 고른 상점(`auth.storeId`)을 `X-Store-Id`로 싣는다. 401이면 `refresh`(자격 증명 파일의 refresh 회전)를
|
|
7220
|
-
* 한 번 부른 뒤 다시 보낸다. 토큰·상점은 함수로 읽어 갱신·선택된 값을 따라간다.
|
|
7221
|
-
*/
|
|
7222
|
-
function clientOf(auth, fetchImpl = fetch, refresh) {
|
|
7223
|
-
return createStoreClient({
|
|
7224
|
-
baseUrl: `${auth.apiOrigin}/v1`,
|
|
7225
|
-
accessToken: () => auth.token,
|
|
7226
|
-
storeId: () => auth.storeId ?? null,
|
|
7227
|
-
...refresh ? { refresh } : {},
|
|
7228
|
-
fetch: (input, init$1) => {
|
|
7229
|
-
const headers = new Headers(init$1?.headers);
|
|
7230
|
-
headers.set("user-agent", CLI_USER_AGENT);
|
|
7231
|
-
return fetchImpl(input, {
|
|
7232
|
-
...init$1,
|
|
7233
|
-
headers
|
|
7234
|
-
});
|
|
7235
|
-
}
|
|
7236
|
-
});
|
|
7237
|
-
}
|
|
7238
|
-
/** 오류 코드 — `ApiError`가 아니면 null */
|
|
7239
|
-
function codeOf(error) {
|
|
7240
|
-
return error instanceof ApiError ? error.code : null;
|
|
7241
|
-
}
|
|
7242
|
-
function detailsOf(error) {
|
|
7243
|
-
return error instanceof ApiError ? error.details ?? {} : {};
|
|
7244
|
-
}
|
|
7245
|
-
|
|
7246
8209
|
//#endregion
|
|
7247
8210
|
//#region ../mcp/src/account-token.ts
|
|
7248
8211
|
/**
|
|
@@ -8212,6 +9175,855 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
|
|
|
8212
9175
|
return auth.storeId;
|
|
8213
9176
|
}
|
|
8214
9177
|
|
|
9178
|
+
//#endregion
|
|
9179
|
+
//#region package.json
|
|
9180
|
+
var version = "0.6.0";
|
|
9181
|
+
|
|
9182
|
+
//#endregion
|
|
9183
|
+
//#region src/client.ts
|
|
9184
|
+
/**
|
|
9185
|
+
* 관리 API 클라이언트 — `@sayren/store-sdk`(계약의 원천)를 쓴다. User-Agent `sayren-cli/{버전}`은 api가 버전의 만든 경로
|
|
9186
|
+
* (`createdVia: CLI`)를 적는 표시다(권한과 무관하다. 에이전트용 액세스 토큰이면 행위자는 여전히 에이전트이고 발행은 승인 대기다).
|
|
9187
|
+
* 토큰은 헤더에만 싣고 화면·로그에 찍지 않는다.
|
|
9188
|
+
*/
|
|
9189
|
+
const CLI_VERSION = version;
|
|
9190
|
+
const CLI_USER_AGENT = `sayren-cli/${CLI_VERSION}`;
|
|
9191
|
+
/**
|
|
9192
|
+
* 계정 토큰(#71)을 Bearer로, 고른 상점(`auth.storeId`)을 `X-Store-Id`로 싣는다. 401이면 `refresh`(자격 증명 파일의 refresh 회전)를
|
|
9193
|
+
* 한 번 부른 뒤 다시 보낸다. 토큰·상점은 함수로 읽어 갱신·선택된 값을 따라간다.
|
|
9194
|
+
*/
|
|
9195
|
+
function clientOf(auth, fetchImpl = fetch, refresh) {
|
|
9196
|
+
return createStoreClient({
|
|
9197
|
+
baseUrl: `${auth.apiOrigin}/v1`,
|
|
9198
|
+
accessToken: () => auth.token,
|
|
9199
|
+
storeId: () => auth.storeId ?? null,
|
|
9200
|
+
...refresh ? { refresh } : {},
|
|
9201
|
+
fetch: (input, init$1) => {
|
|
9202
|
+
const headers = new Headers(init$1?.headers);
|
|
9203
|
+
headers.set("user-agent", CLI_USER_AGENT);
|
|
9204
|
+
return fetchImpl(input, {
|
|
9205
|
+
...init$1,
|
|
9206
|
+
headers
|
|
9207
|
+
});
|
|
9208
|
+
}
|
|
9209
|
+
});
|
|
9210
|
+
}
|
|
9211
|
+
/** 오류 코드 — `ApiError`가 아니면 null */
|
|
9212
|
+
function codeOf(error) {
|
|
9213
|
+
return error instanceof ApiError ? error.code : null;
|
|
9214
|
+
}
|
|
9215
|
+
function detailsOf(error) {
|
|
9216
|
+
return error instanceof ApiError ? error.details ?? {} : {};
|
|
9217
|
+
}
|
|
9218
|
+
|
|
9219
|
+
//#endregion
|
|
9220
|
+
//#region src/app-commands.ts
|
|
9221
|
+
/**
|
|
9222
|
+
* `sayren app …`(이슈 #22, 매뉴얼 「앱 만들기」) — 앱 폴더(`sayren.app.json`)에서 돈다. 서버는 코드를 빌드하지 않는다(A1):
|
|
9223
|
+
* CLI가 esbuild로 서버 번들 한 파일을 만들어 올리고 api가 `DEPENDENCIES`·`VERIFY`·`BUNDLE`·`UPLOAD`를 돈다. 앱 화면(`ui/`)은
|
|
9224
|
+
* 정적 파일 그대로 올린다.
|
|
9225
|
+
*
|
|
9226
|
+
* 인증은 둘이다. 앱 배포 키(`--key`·`SAYREN_APP_KEY`, developer.sayren.app에서 발행, `sak_…`)는 그 앱의 경로에만 쓰이고(CI 용도),
|
|
9227
|
+
* 그 밖에는 `sayren login`의 계정 토큰이다. 앱 등록(`init`)·개발 상점은 계정 토큰만 된다.
|
|
9228
|
+
*/
|
|
9229
|
+
const APP_MANIFEST_FILE = "sayren.app.json";
|
|
9230
|
+
const APP_STATE_FILE = `${STATE_DIR}/app.json`;
|
|
9231
|
+
const APP_DEV_PORT = 4020;
|
|
9232
|
+
const SERVER_ENTRY_CANDIDATES = [
|
|
9233
|
+
"src/server.ts",
|
|
9234
|
+
"src/server.js",
|
|
9235
|
+
"src/server.mjs"
|
|
9236
|
+
];
|
|
9237
|
+
const UI_DIR = "ui";
|
|
9238
|
+
const SETTING_ENV_PREFIX = "SAYREN_APP_SETTING_";
|
|
9239
|
+
const SECRET_ENV_PREFIX = "SAYREN_APP_SECRET_";
|
|
9240
|
+
/** `dev`의 개발 설치 토큰을 만료 몇 초 전에 다시 받는가 */
|
|
9241
|
+
const DEV_TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
9242
|
+
function readAppState(dir) {
|
|
9243
|
+
const path = join(dir, APP_STATE_FILE);
|
|
9244
|
+
if (!existsSync(path)) return null;
|
|
9245
|
+
try {
|
|
9246
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
9247
|
+
if (typeof parsed.appId === "string" && typeof parsed.handle === "string" && typeof parsed.apiOrigin === "string") return {
|
|
9248
|
+
appId: parsed.appId,
|
|
9249
|
+
handle: parsed.handle,
|
|
9250
|
+
apiOrigin: parsed.apiOrigin
|
|
9251
|
+
};
|
|
9252
|
+
} catch {}
|
|
9253
|
+
throw new CliError(cli_app_state_invalid({ file: APP_STATE_FILE }));
|
|
9254
|
+
}
|
|
9255
|
+
function writeAppState(dir, state) {
|
|
9256
|
+
mkdirSync(join(dir, STATE_DIR), { recursive: true });
|
|
9257
|
+
writeFileSync(join(dir, APP_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`, { mode: 384 });
|
|
9258
|
+
}
|
|
9259
|
+
/** 매니페스트 원문(JSON)과 검증 결과 — 문제가 있으면 CliError */
|
|
9260
|
+
function loadManifest(dir) {
|
|
9261
|
+
const path = join(dir, APP_MANIFEST_FILE);
|
|
9262
|
+
if (!existsSync(path)) throw new CliError(cli_app_manifest_missing({ file: APP_MANIFEST_FILE }));
|
|
9263
|
+
let raw;
|
|
9264
|
+
try {
|
|
9265
|
+
raw = JSON.parse(readFileSync(path, "utf8"));
|
|
9266
|
+
} catch (error) {
|
|
9267
|
+
throw new CliError(cli_app_manifest_invalid({ problems: `- ${error instanceof Error ? error.message : String(error)}` }));
|
|
9268
|
+
}
|
|
9269
|
+
const parsed = appManifestSchema.safeParse(raw);
|
|
9270
|
+
if (!parsed.success) throw new CliError(cli_app_manifest_invalid({ problems: parsed.error.issues.map((issue) => `- ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n") }));
|
|
9271
|
+
const problems = appManifestProblems(parsed.data);
|
|
9272
|
+
if (problems.length) throw new CliError(cli_app_manifest_invalid({ problems: problems.map((p) => `- ${p.path}: ${p.message} (${p.code})`).join("\n") }));
|
|
9273
|
+
return {
|
|
9274
|
+
raw,
|
|
9275
|
+
manifest: parsed.data
|
|
9276
|
+
};
|
|
9277
|
+
}
|
|
9278
|
+
function appDir(flags, io) {
|
|
9279
|
+
return resolve(io.cwd, flags.dir ?? ".");
|
|
9280
|
+
}
|
|
9281
|
+
function requireState(dir) {
|
|
9282
|
+
const state = readAppState(dir);
|
|
9283
|
+
if (!state) throw new CliError(cli_app_not_registered({ file: APP_MANIFEST_FILE }));
|
|
9284
|
+
return state;
|
|
9285
|
+
}
|
|
9286
|
+
/** 배포 키(`--key` → `SAYREN_APP_KEY`)가 있으면 그것, 없으면 계정 토큰(`resolveAuth`) */
|
|
9287
|
+
async function developerAuth(flags, dir, io, options = {}) {
|
|
9288
|
+
const state = readAppState(dir);
|
|
9289
|
+
const key = flags.key?.trim() || io.env.SAYREN_APP_KEY?.trim();
|
|
9290
|
+
if (key && !options.accountOnly) {
|
|
9291
|
+
if (!APP_DEPLOY_KEY_PATTERN.test(key)) throw new CliError(cli_app_key_invalid());
|
|
9292
|
+
const { apiOrigin } = resolveApiOrigin({ apiOrigin: flags.apiOrigin ?? state?.apiOrigin }, dir, null, io.env);
|
|
9293
|
+
return {
|
|
9294
|
+
apiOrigin,
|
|
9295
|
+
bearer: key,
|
|
9296
|
+
kind: "key"
|
|
9297
|
+
};
|
|
9298
|
+
}
|
|
9299
|
+
const auth = resolveAuth({
|
|
9300
|
+
...flags,
|
|
9301
|
+
apiOrigin: flags.apiOrigin ?? state?.apiOrigin
|
|
9302
|
+
}, dir, null, io.env, credentialTokenReader(io.env));
|
|
9303
|
+
const refresh = await readyAuth(auth, io);
|
|
9304
|
+
return {
|
|
9305
|
+
apiOrigin: auth.apiOrigin,
|
|
9306
|
+
get bearer() {
|
|
9307
|
+
return auth.token;
|
|
9308
|
+
},
|
|
9309
|
+
kind: "account",
|
|
9310
|
+
...refresh ? { refresh } : {}
|
|
9311
|
+
};
|
|
9312
|
+
}
|
|
9313
|
+
async function devApi(auth, io, method, path, body, retried = false) {
|
|
9314
|
+
const res = await io.fetch(`${auth.apiOrigin}/platform/v1/developer${path}`, {
|
|
9315
|
+
method,
|
|
9316
|
+
headers: {
|
|
9317
|
+
authorization: `Bearer ${auth.bearer}`,
|
|
9318
|
+
"user-agent": CLI_USER_AGENT,
|
|
9319
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
9320
|
+
},
|
|
9321
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
9322
|
+
});
|
|
9323
|
+
if (res.status === 204) return void 0;
|
|
9324
|
+
const text = await res.text();
|
|
9325
|
+
let json = {};
|
|
9326
|
+
try {
|
|
9327
|
+
json = text ? JSON.parse(text) : {};
|
|
9328
|
+
} catch {
|
|
9329
|
+
throw new ApiError(res.status, "INVALID_RESPONSE", text.slice(0, 200));
|
|
9330
|
+
}
|
|
9331
|
+
if (res.ok && json.meta?.isSuccess !== false) return json.data;
|
|
9332
|
+
if (res.status === 401 && auth.refresh && !retried && await auth.refresh()) return devApi(auth, io, method, path, body, true);
|
|
9333
|
+
throw new ApiError(res.status, json.error?.code ?? json.meta?.code ?? "ERROR", json.error?.message ?? json.meta?.message ?? `HTTP ${res.status}`, void 0, json, json.error?.details);
|
|
9334
|
+
}
|
|
9335
|
+
function serverEntryOf(dir) {
|
|
9336
|
+
const found = SERVER_ENTRY_CANDIDATES.find((candidate) => existsSync(join(dir, candidate)));
|
|
9337
|
+
if (!found) throw new CliError(cli_app_entry_missing({ entry: SERVER_ENTRY_CANDIDATES[0] }));
|
|
9338
|
+
return join(dir, found);
|
|
9339
|
+
}
|
|
9340
|
+
/** esbuild로 서버 번들을 만든다. 앱 의존성은 앱 폴더의 `node_modules`에서 푼다(설치돼 있어야 한다) */
|
|
9341
|
+
async function bundleServer(dir) {
|
|
9342
|
+
const entry = serverEntryOf(dir);
|
|
9343
|
+
const esbuild = await import("esbuild");
|
|
9344
|
+
try {
|
|
9345
|
+
const file = (await esbuild.build({
|
|
9346
|
+
entryPoints: [entry],
|
|
9347
|
+
bundle: true,
|
|
9348
|
+
write: false,
|
|
9349
|
+
format: "esm",
|
|
9350
|
+
platform: "browser",
|
|
9351
|
+
target: "es2022",
|
|
9352
|
+
conditions: [
|
|
9353
|
+
"workerd",
|
|
9354
|
+
"worker",
|
|
9355
|
+
"browser"
|
|
9356
|
+
],
|
|
9357
|
+
mainFields: [
|
|
9358
|
+
"browser",
|
|
9359
|
+
"module",
|
|
9360
|
+
"main"
|
|
9361
|
+
],
|
|
9362
|
+
absWorkingDir: dir,
|
|
9363
|
+
logLevel: "silent",
|
|
9364
|
+
legalComments: "none",
|
|
9365
|
+
define: { "process.env.NODE_ENV": "\"production\"" }
|
|
9366
|
+
})).outputFiles?.[0];
|
|
9367
|
+
if (!file) throw new CliError(cli_app_bundle_failed({ tail: "(빈 산출물)" }));
|
|
9368
|
+
return file.text;
|
|
9369
|
+
} catch (error) {
|
|
9370
|
+
if (error instanceof CliError) throw error;
|
|
9371
|
+
const tail = error && typeof error === "object" && "errors" in error ? (error.errors ?? []).map((e) => e.location ? `${e.location.file}:${e.location.line}: ${e.text}` : e.text).join("\n") : error instanceof Error ? error.message : String(error);
|
|
9372
|
+
throw new CliError(cli_app_bundle_failed({ tail }));
|
|
9373
|
+
}
|
|
9374
|
+
}
|
|
9375
|
+
/** 번들을 불러 `defineApp` 메타데이터를 읽는다 — `HANDLER_MISSING`·기본 내보내기 검사. `dev`도 이 모듈을 실행한다 */
|
|
9376
|
+
async function loadBundle(dir, server) {
|
|
9377
|
+
const out = join(dir, STATE_DIR, "build");
|
|
9378
|
+
mkdirSync(out, { recursive: true });
|
|
9379
|
+
const file = join(out, `server.${Date.now().toString(36)}.mjs`);
|
|
9380
|
+
writeFileSync(file, server);
|
|
9381
|
+
let mod;
|
|
9382
|
+
try {
|
|
9383
|
+
mod = await import(pathToFileURL(file).href);
|
|
9384
|
+
} catch (error) {
|
|
9385
|
+
throw new CliError(cli_app_bundle_failed({ tail: error instanceof Error ? error.message : String(error) }));
|
|
9386
|
+
}
|
|
9387
|
+
const app = mod.default;
|
|
9388
|
+
const meta = app?.__sayrenApp;
|
|
9389
|
+
if (!app || typeof app.fetch !== "function" || !meta) throw new CliError(cli_app_default_export_missing());
|
|
9390
|
+
return {
|
|
9391
|
+
app,
|
|
9392
|
+
meta: {
|
|
9393
|
+
events: [...meta.events],
|
|
9394
|
+
http: [...meta.http]
|
|
9395
|
+
}
|
|
9396
|
+
};
|
|
9397
|
+
}
|
|
9398
|
+
function collectListing(dir, manifest) {
|
|
9399
|
+
let icon = null;
|
|
9400
|
+
if (manifest.icon) {
|
|
9401
|
+
const file = join(dir, manifest.icon);
|
|
9402
|
+
if (!existsSync(file)) throw new CliError(cli_app_icon_missing({ path: manifest.icon }));
|
|
9403
|
+
const bytes = readFileSync(file);
|
|
9404
|
+
const contentType = /\.svg$/i.test(manifest.icon) ? "image/svg+xml" : "image/png";
|
|
9405
|
+
const problem = appIconProblem(bytes, contentType);
|
|
9406
|
+
if (problem) throw new CliError(cli_app_icon_invalid({
|
|
9407
|
+
path: manifest.icon,
|
|
9408
|
+
problem
|
|
9409
|
+
}));
|
|
9410
|
+
icon = {
|
|
9411
|
+
path: manifest.icon,
|
|
9412
|
+
contentType,
|
|
9413
|
+
bytes
|
|
9414
|
+
};
|
|
9415
|
+
}
|
|
9416
|
+
const readmePath = manifest.readme ?? "README.md";
|
|
9417
|
+
let readme = null;
|
|
9418
|
+
if (existsSync(join(dir, readmePath))) {
|
|
9419
|
+
const text = readFileSync(join(dir, readmePath), "utf8");
|
|
9420
|
+
if (Buffer.byteLength(text, "utf8") > APP_README_MAX_BYTES) throw new CliError(cli_app_readme_too_large({
|
|
9421
|
+
path: readmePath,
|
|
9422
|
+
limit: String(APP_README_MAX_BYTES / 1024)
|
|
9423
|
+
}));
|
|
9424
|
+
readme = {
|
|
9425
|
+
path: readmePath,
|
|
9426
|
+
text
|
|
9427
|
+
};
|
|
9428
|
+
} else if (manifest.readme) throw new CliError(cli_app_readme_missing({ path: manifest.readme }));
|
|
9429
|
+
return {
|
|
9430
|
+
icon,
|
|
9431
|
+
readme
|
|
9432
|
+
};
|
|
9433
|
+
}
|
|
9434
|
+
/** 로컬 검사 — 매니페스트, 번들, 이벤트 함수 존재, 비밀 값 형식, 크기. 서버 `VERIFY`·`BUNDLE`과 같은 규칙 */
|
|
9435
|
+
async function verifyApp(dir) {
|
|
9436
|
+
const { raw, manifest } = loadManifest(dir);
|
|
9437
|
+
const server = await bundleServer(dir);
|
|
9438
|
+
if (APP_SECRET_PATTERNS.find((pattern) => pattern.test(server))) throw new CliError(cli_app_secret_in_source());
|
|
9439
|
+
const gzipBytes = gzipSync(server).byteLength;
|
|
9440
|
+
if (gzipBytes > APP_SERVER_BUNDLE_MAX_GZIP_BYTES) throw new CliError(cli_app_bundle_too_large({ size: formatMb(gzipBytes) }));
|
|
9441
|
+
const { app, meta } = await loadBundle(dir, server);
|
|
9442
|
+
const missing = manifest.events.filter((event) => !meta.events.includes(event));
|
|
9443
|
+
if (missing.length) throw new CliError(cli_app_handler_missing({ events: missing.join(", ") }));
|
|
9444
|
+
const warnings = [];
|
|
9445
|
+
const listed = manifest.events;
|
|
9446
|
+
const extra = meta.events.filter((event) => !listed.includes(event));
|
|
9447
|
+
if (extra.length) warnings.push(cli_app_handler_unlisted({ events: extra.join(", ") }));
|
|
9448
|
+
const ui = manifest.ui?.adminPage ? collectUi(dir) : [];
|
|
9449
|
+
if (manifest.ui?.adminPage && !ui.some((file) => file.path === "index.html")) throw new CliError(cli_app_ui_index_missing({ dir: UI_DIR }));
|
|
9450
|
+
const listing = collectListing(dir, manifest);
|
|
9451
|
+
return {
|
|
9452
|
+
manifest,
|
|
9453
|
+
raw,
|
|
9454
|
+
bundle: {
|
|
9455
|
+
server,
|
|
9456
|
+
gzipBytes,
|
|
9457
|
+
meta
|
|
9458
|
+
},
|
|
9459
|
+
app,
|
|
9460
|
+
ui,
|
|
9461
|
+
listing,
|
|
9462
|
+
warnings
|
|
9463
|
+
};
|
|
9464
|
+
}
|
|
9465
|
+
function collectUi(dir) {
|
|
9466
|
+
const root = join(dir, UI_DIR);
|
|
9467
|
+
if (!existsSync(root)) throw new CliError(cli_app_ui_index_missing({ dir: UI_DIR }));
|
|
9468
|
+
const files = [];
|
|
9469
|
+
const walk = (current) => {
|
|
9470
|
+
for (const name of readdirSync(current)) {
|
|
9471
|
+
if (name.startsWith(".") || name === "node_modules" || name === "_sayren") continue;
|
|
9472
|
+
const full = join(current, name);
|
|
9473
|
+
if (statSync(full).isDirectory()) walk(full);
|
|
9474
|
+
else files.push({
|
|
9475
|
+
path: relative(root, full).split(sep).join("/"),
|
|
9476
|
+
bytes: readFileSync(full)
|
|
9477
|
+
});
|
|
9478
|
+
}
|
|
9479
|
+
};
|
|
9480
|
+
walk(root);
|
|
9481
|
+
const bridge = join(dir, "node_modules/@sayren/app/dist/ui.mjs");
|
|
9482
|
+
if (!existsSync(bridge)) throw new CliError(cli_app_bridge_missing());
|
|
9483
|
+
files.push({
|
|
9484
|
+
path: "_sayren/ui.js",
|
|
9485
|
+
bytes: readFileSync(bridge)
|
|
9486
|
+
});
|
|
9487
|
+
if (files.length > APP_UI_MAX_FILES) throw new CliError(cli_app_ui_too_large({ limit: `${APP_UI_MAX_FILES} files` }));
|
|
9488
|
+
if (files.reduce((sum, file) => sum + file.bytes.byteLength, 0) > APP_UI_MAX_BYTES) throw new CliError(cli_app_ui_too_large({ limit: formatMb(APP_UI_MAX_BYTES) }));
|
|
9489
|
+
return files;
|
|
9490
|
+
}
|
|
9491
|
+
function formatMb(bytes) {
|
|
9492
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
9493
|
+
}
|
|
9494
|
+
const HANDLE_RE = /^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$/;
|
|
9495
|
+
function initFiles(handle, name) {
|
|
9496
|
+
const manifest = {
|
|
9497
|
+
handle,
|
|
9498
|
+
name,
|
|
9499
|
+
description: "",
|
|
9500
|
+
scopes: ["order:r"],
|
|
9501
|
+
network: { hosts: [] },
|
|
9502
|
+
events: ["ORDER.PAID"],
|
|
9503
|
+
settings: {}
|
|
9504
|
+
};
|
|
9505
|
+
return {
|
|
9506
|
+
[APP_MANIFEST_FILE]: `${JSON.stringify(manifest, null, 2)}\n`,
|
|
9507
|
+
"package.json": `${JSON.stringify({
|
|
9508
|
+
name: handle,
|
|
9509
|
+
private: true,
|
|
9510
|
+
type: "module",
|
|
9511
|
+
scripts: {
|
|
9512
|
+
dev: "sayren app dev",
|
|
9513
|
+
verify: "sayren app verify",
|
|
9514
|
+
deploy: "sayren app deploy"
|
|
9515
|
+
},
|
|
9516
|
+
dependencies: { "@sayren/app": `^${CLI_VERSION}` },
|
|
9517
|
+
devDependencies: {
|
|
9518
|
+
sayren: `^${CLI_VERSION}`,
|
|
9519
|
+
typescript: "^5.9.0"
|
|
9520
|
+
}
|
|
9521
|
+
}, null, 2)}\n`,
|
|
9522
|
+
"tsconfig.json": `${JSON.stringify({
|
|
9523
|
+
compilerOptions: {
|
|
9524
|
+
target: "ES2022",
|
|
9525
|
+
module: "ESNext",
|
|
9526
|
+
moduleResolution: "bundler",
|
|
9527
|
+
strict: true,
|
|
9528
|
+
noEmit: true,
|
|
9529
|
+
lib: ["ES2022", "DOM"],
|
|
9530
|
+
skipLibCheck: true
|
|
9531
|
+
},
|
|
9532
|
+
include: ["src"]
|
|
9533
|
+
}, null, 2)}\n`,
|
|
9534
|
+
"src/server.ts": `import { defineApp } from "@sayren/app";
|
|
9535
|
+
|
|
9536
|
+
export default defineApp({
|
|
9537
|
+
events: {
|
|
9538
|
+
"ORDER.PAID": async (event, ctx) => {
|
|
9539
|
+
const { orderId } = event.data as { orderId: string };
|
|
9540
|
+
const order = await ctx.api.orders.get(orderId);
|
|
9541
|
+
ctx.log.info("결제된 주문", order.orderNo, order.totalAmount);
|
|
9542
|
+
},
|
|
9543
|
+
},
|
|
9544
|
+
});
|
|
9545
|
+
`,
|
|
9546
|
+
".gitignore": "node_modules/\ndist/\n.env\n.sayren/\n",
|
|
9547
|
+
"README.md": `# ${name}\n\n이 앱이 하는 일을 한두 문장으로 씁니다.\n\n## 설정\n\n설치한 뒤 채울 설정을 설명합니다.\n`,
|
|
9548
|
+
".env.example": `# 로컬 실행(sayren app dev)의 설정·비밀 값\n# ${SETTING_ENV_PREFIX}<설정 키>=값\n# ${SECRET_ENV_PREFIX}<비밀 값 이름>=값\n`
|
|
9549
|
+
};
|
|
9550
|
+
}
|
|
9551
|
+
async function appInit(handle, flags, io) {
|
|
9552
|
+
if (!handle || !HANDLE_RE.test(handle)) throw new CliError(cli_app_handle_invalid());
|
|
9553
|
+
const dir = resolve(io.cwd, flags.dir ?? handle);
|
|
9554
|
+
if (existsSync(join(dir, APP_MANIFEST_FILE))) throw new CliError(cli_app_dir_exists({ dir: relative(io.cwd, dir) || "." }));
|
|
9555
|
+
const name = flags.name?.trim() || handle;
|
|
9556
|
+
const auth = await developerAuth(flags, dir, io, { accountOnly: true });
|
|
9557
|
+
const app = await devApi(auth, io, "POST", "/apps", {
|
|
9558
|
+
handle,
|
|
9559
|
+
name
|
|
9560
|
+
});
|
|
9561
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
9562
|
+
for (const [path, content] of Object.entries(initFiles(handle, name))) {
|
|
9563
|
+
const full = join(dir, path);
|
|
9564
|
+
mkdirSync(join(full, ".."), { recursive: true });
|
|
9565
|
+
if (!existsSync(full)) writeFileSync(full, content);
|
|
9566
|
+
}
|
|
9567
|
+
writeAppState(dir, {
|
|
9568
|
+
appId: app.appId,
|
|
9569
|
+
handle: app.handle,
|
|
9570
|
+
apiOrigin: auth.apiOrigin
|
|
9571
|
+
});
|
|
9572
|
+
io.out(cli_app_init_done({
|
|
9573
|
+
dir: relative(io.cwd, dir) || ".",
|
|
9574
|
+
appId: app.appId,
|
|
9575
|
+
handle
|
|
9576
|
+
}));
|
|
9577
|
+
return 0;
|
|
9578
|
+
}
|
|
9579
|
+
async function appVerify(flags, io) {
|
|
9580
|
+
const outcome = await verifyApp(appDir(flags, io));
|
|
9581
|
+
for (const warning of outcome.warnings) io.err(cli_app_warning({ message: warning }));
|
|
9582
|
+
io.out(cli_app_verify_ok({
|
|
9583
|
+
events: outcome.bundle.meta.events.join(", ") || "-",
|
|
9584
|
+
http: outcome.bundle.meta.http.join(", ") || "-",
|
|
9585
|
+
size: formatMb(outcome.bundle.gzipBytes),
|
|
9586
|
+
ui: String(outcome.ui.length),
|
|
9587
|
+
icon: outcome.listing.icon?.path ?? cli_app_listing_none(),
|
|
9588
|
+
readme: outcome.listing.readme?.path ?? cli_app_listing_none()
|
|
9589
|
+
}));
|
|
9590
|
+
return 0;
|
|
9591
|
+
}
|
|
9592
|
+
function printBuild(version$1, io) {
|
|
9593
|
+
for (const stage of version$1.build?.stages ?? []) io.out(` ${stage.ok ? "✓" : "✗"} ${stage.stage} ${stage.durationMs}ms`);
|
|
9594
|
+
}
|
|
9595
|
+
async function appDeploy(flags, io) {
|
|
9596
|
+
const dir = appDir(flags, io);
|
|
9597
|
+
const state = requireState(dir);
|
|
9598
|
+
const outcome = await verifyApp(dir);
|
|
9599
|
+
if (outcome.manifest.handle !== state.handle) throw new CliError(cli_app_handle_mismatch({
|
|
9600
|
+
manifest: outcome.manifest.handle,
|
|
9601
|
+
registered: state.handle
|
|
9602
|
+
}));
|
|
9603
|
+
for (const warning of outcome.warnings) io.err(cli_app_warning({ message: warning }));
|
|
9604
|
+
const auth = await developerAuth(flags, dir, io);
|
|
9605
|
+
const packageJson = existsSync(join(dir, "package.json")) ? readFileSync(join(dir, "package.json"), "utf8") : "{}";
|
|
9606
|
+
const lockfile = existsSync(join(dir, "pnpm-lock.yaml")) ? readFileSync(join(dir, "pnpm-lock.yaml"), "utf8") : null;
|
|
9607
|
+
io.out(cli_app_uploading({
|
|
9608
|
+
size: formatMb(outcome.bundle.gzipBytes),
|
|
9609
|
+
ui: String(outcome.ui.length)
|
|
9610
|
+
}));
|
|
9611
|
+
const version$1 = await devApi(auth, io, "PUT", `/apps/${state.appId}/versions`, {
|
|
9612
|
+
manifest: outcome.raw,
|
|
9613
|
+
packageJson,
|
|
9614
|
+
lockfile,
|
|
9615
|
+
server: Buffer.from(outcome.bundle.server, "utf8").toString("base64"),
|
|
9616
|
+
ui: outcome.ui.map((file) => ({
|
|
9617
|
+
path: file.path,
|
|
9618
|
+
content: file.bytes.toString("base64")
|
|
9619
|
+
})),
|
|
9620
|
+
listing: {
|
|
9621
|
+
icon: outcome.listing.icon ? {
|
|
9622
|
+
contentType: outcome.listing.icon.contentType,
|
|
9623
|
+
content: outcome.listing.icon.bytes.toString("base64")
|
|
9624
|
+
} : null,
|
|
9625
|
+
readme: outcome.listing.readme?.text ?? null
|
|
9626
|
+
},
|
|
9627
|
+
...flags.message ? { message: flags.message } : {}
|
|
9628
|
+
});
|
|
9629
|
+
printBuild(version$1, io);
|
|
9630
|
+
if (version$1.status !== "READY") {
|
|
9631
|
+
io.err(cli_app_build_failed({
|
|
9632
|
+
stage: version$1.build?.failureStage ?? "-",
|
|
9633
|
+
code: version$1.build?.errorCode ?? "-",
|
|
9634
|
+
tail: version$1.build?.logTail ?? ""
|
|
9635
|
+
}));
|
|
9636
|
+
return 1;
|
|
9637
|
+
}
|
|
9638
|
+
io.out(cli_app_deploy_done({ number: String(version$1.number) }));
|
|
9639
|
+
return 0;
|
|
9640
|
+
}
|
|
9641
|
+
async function appRelease(numberRaw, flags, io) {
|
|
9642
|
+
const number = Number(numberRaw);
|
|
9643
|
+
if (!Number.isInteger(number) || number < 1) throw new CliError(cli_app_version_number_required());
|
|
9644
|
+
const dir = appDir(flags, io);
|
|
9645
|
+
const state = requireState(dir);
|
|
9646
|
+
const result = await devApi(await developerAuth(flags, dir, io), io, "POST", `/apps/${state.appId}/versions/${number}/release`);
|
|
9647
|
+
io.out(cli_app_release_done({
|
|
9648
|
+
number: String(result.version.number),
|
|
9649
|
+
applied: String(result.applied),
|
|
9650
|
+
pending: String(result.pendingReconsent)
|
|
9651
|
+
}));
|
|
9652
|
+
return 0;
|
|
9653
|
+
}
|
|
9654
|
+
async function appStatus(flags, io) {
|
|
9655
|
+
const dir = appDir(flags, io);
|
|
9656
|
+
const state = requireState(dir);
|
|
9657
|
+
const auth = await developerAuth(flags, dir, io);
|
|
9658
|
+
const [app, versions] = await Promise.all([devApi(auth, io, "GET", `/apps/${state.appId}`), devApi(auth, io, "GET", `/apps/${state.appId}/versions`)]);
|
|
9659
|
+
io.out(cli_app_status({
|
|
9660
|
+
handle: app.handle,
|
|
9661
|
+
name: app.name,
|
|
9662
|
+
appId: app.appId,
|
|
9663
|
+
status: app.status,
|
|
9664
|
+
current: app.currentVersionNumber === null ? "-" : String(app.currentVersionNumber),
|
|
9665
|
+
active: String(app.installations.active),
|
|
9666
|
+
pending: String(app.installations.pendingReconsent)
|
|
9667
|
+
}));
|
|
9668
|
+
for (const version$1 of versions.slice(0, 5)) io.out(` v${version$1.number} ${version$1.status}${version$1.current ? " *" : ""} ${version$1.createdAt}${version$1.message ? ` — ${version$1.message}` : ""}${version$1.build?.errorCode ? ` (${version$1.build.errorCode})` : ""}`);
|
|
9669
|
+
return 0;
|
|
9670
|
+
}
|
|
9671
|
+
const SINCE_RE = /^(\d+)([smhd])$/;
|
|
9672
|
+
function sinceMs(value) {
|
|
9673
|
+
if (!value) return null;
|
|
9674
|
+
const match = SINCE_RE.exec(value.trim());
|
|
9675
|
+
if (!match) throw new CliError(cli_app_since_invalid({ value }));
|
|
9676
|
+
const unit = {
|
|
9677
|
+
s: 1e3,
|
|
9678
|
+
m: 6e4,
|
|
9679
|
+
h: 36e5,
|
|
9680
|
+
d: 864e5
|
|
9681
|
+
}[match[2]];
|
|
9682
|
+
return Number(match[1]) * unit;
|
|
9683
|
+
}
|
|
9684
|
+
function invocationLine(row) {
|
|
9685
|
+
const head = `${row.createdAt} ${row.outcome.padEnd(14)} ${row.kind}${row.eventType ? ` ${row.eventType}` : ""} ${row.storeName ?? row.storeId} v${row.versionNumber ?? "-"} ${row.durationMs}ms`;
|
|
9686
|
+
const lines = row.logs.map((line) => ` [${line.level}] ${line.message}`);
|
|
9687
|
+
if (row.errorMessage) lines.push(` ! ${row.errorMessage.split("\n")[0]}`);
|
|
9688
|
+
return [head, ...lines].join("\n");
|
|
9689
|
+
}
|
|
9690
|
+
async function appLogs(flags, io) {
|
|
9691
|
+
const dir = appDir(flags, io);
|
|
9692
|
+
const state = requireState(dir);
|
|
9693
|
+
const auth = await developerAuth(flags, dir, io);
|
|
9694
|
+
const since = sinceMs(flags.since);
|
|
9695
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9696
|
+
const query = new URLSearchParams({ limit: "50" });
|
|
9697
|
+
if (flags.store) query.set("storeId", flags.store);
|
|
9698
|
+
const fetchOnce = async () => {
|
|
9699
|
+
const rows = await devApi(auth, io, "GET", `/apps/${state.appId}/invocations?${query}`);
|
|
9700
|
+
const now = io.now?.() ?? Date.now();
|
|
9701
|
+
const fresh = rows.filter((row) => !seen.has(row.invocationId)).filter((row) => since === null || now - Date.parse(row.createdAt) <= since).reverse();
|
|
9702
|
+
for (const row of fresh) {
|
|
9703
|
+
seen.add(row.invocationId);
|
|
9704
|
+
io.out(invocationLine(row));
|
|
9705
|
+
}
|
|
9706
|
+
return fresh.length;
|
|
9707
|
+
};
|
|
9708
|
+
const count = await fetchOnce();
|
|
9709
|
+
if (!flags.tail) {
|
|
9710
|
+
if (count === 0) io.out(cli_app_logs_empty());
|
|
9711
|
+
return 0;
|
|
9712
|
+
}
|
|
9713
|
+
while (!io.signal?.aborted) {
|
|
9714
|
+
await io.sleep(io.pollMs);
|
|
9715
|
+
await fetchOnce();
|
|
9716
|
+
}
|
|
9717
|
+
return 0;
|
|
9718
|
+
}
|
|
9719
|
+
async function appSecret(action, name, flags, io) {
|
|
9720
|
+
const dir = appDir(flags, io);
|
|
9721
|
+
const state = requireState(dir);
|
|
9722
|
+
const auth = await developerAuth(flags, dir, io);
|
|
9723
|
+
const base = `/apps/${state.appId}/secrets`;
|
|
9724
|
+
if (action === "list") {
|
|
9725
|
+
const list = await devApi(auth, io, "GET", base);
|
|
9726
|
+
if (list.length === 0) io.out(cli_app_secret_empty());
|
|
9727
|
+
for (const item of list) io.out(` ${item.name} ${item.updatedAt}`);
|
|
9728
|
+
return 0;
|
|
9729
|
+
}
|
|
9730
|
+
if (!name) throw new CliError(cli_app_secret_name_required());
|
|
9731
|
+
if (action === "put") {
|
|
9732
|
+
const value = (await (io.readStdin ?? readStdin)()).replace(/\r?\n$/, "");
|
|
9733
|
+
if (!value) throw new CliError(cli_app_secret_value_required({ name }));
|
|
9734
|
+
await devApi(auth, io, "PUT", `${base}/${encodeURIComponent(name)}`, { value });
|
|
9735
|
+
io.out(cli_app_secret_saved({ name }));
|
|
9736
|
+
return 0;
|
|
9737
|
+
}
|
|
9738
|
+
if (action === "delete") {
|
|
9739
|
+
await devApi(auth, io, "DELETE", `${base}/${encodeURIComponent(name)}`);
|
|
9740
|
+
io.out(cli_app_secret_deleted({ name }));
|
|
9741
|
+
return 0;
|
|
9742
|
+
}
|
|
9743
|
+
throw new CliError(cli_app_subcommand_unknown({ command: `secret ${action ?? ""}`.trim() }));
|
|
9744
|
+
}
|
|
9745
|
+
function readStdin() {
|
|
9746
|
+
return new Promise((resolve$1, reject) => {
|
|
9747
|
+
let data = "";
|
|
9748
|
+
process.stdin.setEncoding("utf8");
|
|
9749
|
+
process.stdin.on("data", (chunk) => {
|
|
9750
|
+
data += chunk;
|
|
9751
|
+
});
|
|
9752
|
+
process.stdin.once("end", () => resolve$1(data));
|
|
9753
|
+
process.stdin.once("error", reject);
|
|
9754
|
+
});
|
|
9755
|
+
}
|
|
9756
|
+
async function appDevStore(action, name, flags, io) {
|
|
9757
|
+
const auth = await developerAuth(flags, appDir(flags, io), io, { accountOnly: true });
|
|
9758
|
+
if (action === "list") {
|
|
9759
|
+
const list = await devApi(auth, io, "GET", "/dev-stores");
|
|
9760
|
+
if (list.length === 0) io.out(cli_app_dev_store_empty());
|
|
9761
|
+
for (const store of list) io.out(` ${store.storeId} ${store.storeCode} ${store.name} ${store.status}`);
|
|
9762
|
+
return 0;
|
|
9763
|
+
}
|
|
9764
|
+
if (action === "create") {
|
|
9765
|
+
if (!name?.trim()) throw new CliError(cli_app_dev_store_name_required());
|
|
9766
|
+
const store = await devApi(auth, io, "POST", "/dev-stores", { name: name.trim() });
|
|
9767
|
+
io.out(cli_app_dev_store_created({
|
|
9768
|
+
storeId: store.storeId,
|
|
9769
|
+
name: store.name
|
|
9770
|
+
}));
|
|
9771
|
+
return 0;
|
|
9772
|
+
}
|
|
9773
|
+
throw new CliError(cli_app_subcommand_unknown({ command: `dev-store ${action ?? ""}`.trim() }));
|
|
9774
|
+
}
|
|
9775
|
+
/** `.env`의 `SAYREN_APP_SETTING_*`·`SAYREN_APP_SECRET_*` — 설정은 매니페스트 타입으로 바꾼다 */
|
|
9776
|
+
function localSettingsOf(manifest, env) {
|
|
9777
|
+
const settings = {};
|
|
9778
|
+
const secrets = {};
|
|
9779
|
+
for (const [key, field] of Object.entries(manifest.settings ?? {})) {
|
|
9780
|
+
const raw = env.get(`${SETTING_ENV_PREFIX}${key}`);
|
|
9781
|
+
if (raw === void 0) {
|
|
9782
|
+
if (field.default !== void 0) settings[key] = field.default;
|
|
9783
|
+
continue;
|
|
9784
|
+
}
|
|
9785
|
+
settings[key] = field.type === "number" ? Number(raw) : field.type === "boolean" ? raw === "true" || raw === "1" : raw;
|
|
9786
|
+
}
|
|
9787
|
+
for (const [key, value] of env) if (key.startsWith(SECRET_ENV_PREFIX)) secrets[key.slice(18)] = value;
|
|
9788
|
+
return {
|
|
9789
|
+
settings,
|
|
9790
|
+
secrets
|
|
9791
|
+
};
|
|
9792
|
+
}
|
|
9793
|
+
let devSeq = 0;
|
|
9794
|
+
function devEnvelope(input) {
|
|
9795
|
+
devSeq += 1;
|
|
9796
|
+
return {
|
|
9797
|
+
v: 1,
|
|
9798
|
+
invocationId: `inv_dev_${Date.now().toString(36)}_${devSeq}`,
|
|
9799
|
+
kind: input.kind,
|
|
9800
|
+
installation: {
|
|
9801
|
+
installationId: input.token?.installationId ?? "inst_dev",
|
|
9802
|
+
appId: input.appId,
|
|
9803
|
+
versionNumber: 0,
|
|
9804
|
+
storeId: input.token?.storeId ?? "store_dev",
|
|
9805
|
+
storeName: "(dev)",
|
|
9806
|
+
apiBaseUrl: input.apiBaseUrl
|
|
9807
|
+
},
|
|
9808
|
+
token: input.token?.token ?? null,
|
|
9809
|
+
settings: input.settings,
|
|
9810
|
+
secrets: input.secrets,
|
|
9811
|
+
...input.kind === "http" ? { user: {
|
|
9812
|
+
userId: "user_dev",
|
|
9813
|
+
role: "OWNER"
|
|
9814
|
+
} } : {},
|
|
9815
|
+
...input.event ? { event: {
|
|
9816
|
+
id: `evt_dev_${devSeq}`,
|
|
9817
|
+
type: input.event.type,
|
|
9818
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9819
|
+
storeId: input.token?.storeId ?? "store_dev",
|
|
9820
|
+
sequence: devSeq,
|
|
9821
|
+
data: input.event.data
|
|
9822
|
+
} } : {},
|
|
9823
|
+
...input.request ? { request: input.request } : {}
|
|
9824
|
+
};
|
|
9825
|
+
}
|
|
9826
|
+
async function invokeLocal(app, envelope) {
|
|
9827
|
+
const res = await app.fetch(new Request("https://app.local/_invoke", {
|
|
9828
|
+
method: "POST",
|
|
9829
|
+
headers: { "content-type": "application/json" },
|
|
9830
|
+
body: JSON.stringify(envelope)
|
|
9831
|
+
}));
|
|
9832
|
+
if (!res.ok) throw new CliError(cli_app_bundle_failed({ tail: await res.text() }));
|
|
9833
|
+
return await res.json();
|
|
9834
|
+
}
|
|
9835
|
+
function printResult(label, result, io) {
|
|
9836
|
+
io.out(`${label} ${result.outcome} ${result.durationMs}ms`);
|
|
9837
|
+
for (const line of result.logs) io.out(` [${line.level}] ${line.message}`);
|
|
9838
|
+
if (result.errorMessage) io.err(` ! ${result.errorMessage}`);
|
|
9839
|
+
}
|
|
9840
|
+
const MIME = {
|
|
9841
|
+
".html": "text/html; charset=utf-8",
|
|
9842
|
+
".js": "text/javascript; charset=utf-8",
|
|
9843
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
9844
|
+
".css": "text/css; charset=utf-8",
|
|
9845
|
+
".json": "application/json",
|
|
9846
|
+
".svg": "image/svg+xml",
|
|
9847
|
+
".png": "image/png",
|
|
9848
|
+
".jpg": "image/jpeg",
|
|
9849
|
+
".ico": "image/x-icon"
|
|
9850
|
+
};
|
|
9851
|
+
function readBody(req) {
|
|
9852
|
+
return new Promise((resolve$1, reject) => {
|
|
9853
|
+
const chunks = [];
|
|
9854
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
9855
|
+
req.once("end", () => resolve$1(Buffer.concat(chunks).toString("utf8")));
|
|
9856
|
+
req.once("error", reject);
|
|
9857
|
+
});
|
|
9858
|
+
}
|
|
9859
|
+
/**
|
|
9860
|
+
* 로컬 실행(매뉴얼 「2. 로컬에서 실행하기」) — 번들을 이 프로세스에서 불러 `/_trigger`(이벤트)·`/_http/*`(HTTP 핸들러)·`/`(앱 화면)를 받는다.
|
|
9861
|
+
* 설치 토큰은 `POST …/dev-token`(1시간)으로 받고 만료 전에 다시 받는다. `--store`가 없으면 토큰 없이 돈다(`ctx.api`는 401).
|
|
9862
|
+
*/
|
|
9863
|
+
async function appDev(flags, io) {
|
|
9864
|
+
const dir = appDir(flags, io);
|
|
9865
|
+
const state = requireState(dir);
|
|
9866
|
+
const { manifest } = loadManifest(dir);
|
|
9867
|
+
const { app } = await loadBundle(dir, await bundleServer(dir));
|
|
9868
|
+
const port = Number(flags.port ?? io.env.SAYREN_APP_PORT ?? APP_DEV_PORT);
|
|
9869
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new CliError(cli_app_port_invalid({ value: String(flags.port) }));
|
|
9870
|
+
const storeId = flags.store?.trim() || io.env.SAYREN_STORE_ID?.trim() || null;
|
|
9871
|
+
const auth = storeId ? await developerAuth(flags, dir, io) : null;
|
|
9872
|
+
let token = null;
|
|
9873
|
+
const ensureToken = async () => {
|
|
9874
|
+
if (!auth || !storeId) return null;
|
|
9875
|
+
const now = io.now?.() ?? Date.now();
|
|
9876
|
+
if (token && Date.parse(token.expiresAt) - now > DEV_TOKEN_REFRESH_MARGIN_MS) return token;
|
|
9877
|
+
token = await devApi(auth, io, "POST", `/apps/${state.appId}/dev-token`, { storeId });
|
|
9878
|
+
return token;
|
|
9879
|
+
};
|
|
9880
|
+
await ensureToken();
|
|
9881
|
+
const local = () => localSettingsOf(manifest, readEnvFile(dir));
|
|
9882
|
+
const apiBaseUrl = `${state.apiOrigin}/v1`;
|
|
9883
|
+
const uiRoot = join(dir, UI_DIR);
|
|
9884
|
+
const handle = async (req, res) => {
|
|
9885
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
9886
|
+
try {
|
|
9887
|
+
if (req.method === "POST" && url.pathname === "/_trigger") {
|
|
9888
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
9889
|
+
if (!body.type) {
|
|
9890
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
9891
|
+
res.end(JSON.stringify({ error: "TYPE_REQUIRED" }));
|
|
9892
|
+
return;
|
|
9893
|
+
}
|
|
9894
|
+
const { settings, secrets } = local();
|
|
9895
|
+
const result = await invokeLocal(app, devEnvelope({
|
|
9896
|
+
kind: "event",
|
|
9897
|
+
token: await ensureToken(),
|
|
9898
|
+
apiBaseUrl,
|
|
9899
|
+
appId: state.appId,
|
|
9900
|
+
settings,
|
|
9901
|
+
secrets,
|
|
9902
|
+
event: {
|
|
9903
|
+
type: body.type,
|
|
9904
|
+
data: body.data ?? {}
|
|
9905
|
+
}
|
|
9906
|
+
}));
|
|
9907
|
+
printResult(`[event] ${body.type}`, result, io);
|
|
9908
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
9909
|
+
res.end(JSON.stringify(result));
|
|
9910
|
+
return;
|
|
9911
|
+
}
|
|
9912
|
+
if (url.pathname.startsWith("/_http/")) {
|
|
9913
|
+
const headers = {};
|
|
9914
|
+
for (const [key, value] of Object.entries(req.headers)) if (typeof value === "string" && key !== "host") headers[key] = value;
|
|
9915
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
9916
|
+
const { settings, secrets } = local();
|
|
9917
|
+
const result = await invokeLocal(app, devEnvelope({
|
|
9918
|
+
kind: "http",
|
|
9919
|
+
token: await ensureToken(),
|
|
9920
|
+
apiBaseUrl,
|
|
9921
|
+
appId: state.appId,
|
|
9922
|
+
settings,
|
|
9923
|
+
secrets,
|
|
9924
|
+
request: {
|
|
9925
|
+
method,
|
|
9926
|
+
path: `${url.pathname.slice(6)}${url.search}`,
|
|
9927
|
+
headers,
|
|
9928
|
+
body: method === "GET" ? null : await readBody(req)
|
|
9929
|
+
}
|
|
9930
|
+
}));
|
|
9931
|
+
printResult(`[http] ${method} ${url.pathname}`, result, io);
|
|
9932
|
+
if (!result.response) {
|
|
9933
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
9934
|
+
res.end(JSON.stringify({
|
|
9935
|
+
error: result.outcome,
|
|
9936
|
+
message: result.errorMessage
|
|
9937
|
+
}));
|
|
9938
|
+
return;
|
|
9939
|
+
}
|
|
9940
|
+
res.writeHead(result.response.status, result.response.headers);
|
|
9941
|
+
res.end(result.response.body ?? "");
|
|
9942
|
+
return;
|
|
9943
|
+
}
|
|
9944
|
+
const file = resolve(uiRoot, url.pathname === "/" ? "index.html" : url.pathname.slice(1));
|
|
9945
|
+
if (file.startsWith(uiRoot) && existsSync(file) && statSync(file).isFile()) {
|
|
9946
|
+
res.writeHead(200, { "content-type": MIME[extname(file)] ?? "application/octet-stream" });
|
|
9947
|
+
res.end(readFileSync(file));
|
|
9948
|
+
return;
|
|
9949
|
+
}
|
|
9950
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
9951
|
+
res.end("not found");
|
|
9952
|
+
} catch (error) {
|
|
9953
|
+
io.err(cli_app_dev_error({ message: error instanceof Error ? error.message : String(error) }));
|
|
9954
|
+
if (!res.headersSent) res.writeHead(500, { "content-type": "application/json" });
|
|
9955
|
+
res.end(JSON.stringify({ error: "DEV_ERROR" }));
|
|
9956
|
+
}
|
|
9957
|
+
};
|
|
9958
|
+
const serverHandle = createServer((req, res) => {
|
|
9959
|
+
handle(req, res);
|
|
9960
|
+
});
|
|
9961
|
+
await new Promise((resolveListen, reject) => {
|
|
9962
|
+
serverHandle.once("error", reject);
|
|
9963
|
+
serverHandle.listen(port, "127.0.0.1", () => resolveListen());
|
|
9964
|
+
});
|
|
9965
|
+
io.out(cli_app_dev_listening({
|
|
9966
|
+
port: String(port),
|
|
9967
|
+
store: storeId ?? "-",
|
|
9968
|
+
events: manifest.events.join(", ") || "-"
|
|
9969
|
+
}));
|
|
9970
|
+
await new Promise((resolveClose) => {
|
|
9971
|
+
const stop = () => {
|
|
9972
|
+
serverHandle.close();
|
|
9973
|
+
resolveClose();
|
|
9974
|
+
};
|
|
9975
|
+
if (io.signal) if (io.signal.aborted) stop();
|
|
9976
|
+
else io.signal.addEventListener("abort", stop, { once: true });
|
|
9977
|
+
else {
|
|
9978
|
+
process.once("SIGINT", stop);
|
|
9979
|
+
process.once("SIGTERM", stop);
|
|
9980
|
+
}
|
|
9981
|
+
});
|
|
9982
|
+
return 0;
|
|
9983
|
+
}
|
|
9984
|
+
async function appTrigger(type, flags, io) {
|
|
9985
|
+
if (!type) throw new CliError(cli_app_trigger_type_required());
|
|
9986
|
+
let data = {};
|
|
9987
|
+
if (flags.data) try {
|
|
9988
|
+
data = JSON.parse(flags.data);
|
|
9989
|
+
} catch {
|
|
9990
|
+
throw new CliError(cli_app_trigger_data_invalid());
|
|
9991
|
+
}
|
|
9992
|
+
if (flags.order) data = {
|
|
9993
|
+
...data,
|
|
9994
|
+
orderId: flags.order
|
|
9995
|
+
};
|
|
9996
|
+
const port = Number(flags.port ?? io.env.SAYREN_APP_PORT ?? APP_DEV_PORT);
|
|
9997
|
+
const res = await io.fetch(`http://127.0.0.1:${port}/_trigger`, {
|
|
9998
|
+
method: "POST",
|
|
9999
|
+
headers: { "content-type": "application/json" },
|
|
10000
|
+
body: JSON.stringify({
|
|
10001
|
+
type,
|
|
10002
|
+
data
|
|
10003
|
+
})
|
|
10004
|
+
});
|
|
10005
|
+
if (!res.ok) throw new CliError(cli_app_trigger_failed({ status: String(res.status) }));
|
|
10006
|
+
const result = await res.json();
|
|
10007
|
+
printResult(`[event] ${type}`, result, io);
|
|
10008
|
+
return result.outcome === "OK" ? 0 : 1;
|
|
10009
|
+
}
|
|
10010
|
+
async function appMain(positional, flags, io) {
|
|
10011
|
+
const [sub, ...rest] = positional;
|
|
10012
|
+
switch (sub) {
|
|
10013
|
+
case "init": return appInit(rest[0], flags, io);
|
|
10014
|
+
case "verify": return appVerify(flags, io);
|
|
10015
|
+
case "deploy": return appDeploy(flags, io);
|
|
10016
|
+
case "release": return appRelease(rest[0], flags, io);
|
|
10017
|
+
case "status": return appStatus(flags, io);
|
|
10018
|
+
case "logs": return appLogs(flags, io);
|
|
10019
|
+
case "secret": return appSecret(rest[0], rest[1], flags, io);
|
|
10020
|
+
case "dev-store": return appDevStore(rest[0], rest.slice(1).join(" ") || void 0, flags, io);
|
|
10021
|
+
case "dev": return appDev(flags, io);
|
|
10022
|
+
case "trigger": return appTrigger(rest[0], flags, io);
|
|
10023
|
+
default: throw new CliError(cli_app_subcommand_unknown({ command: sub ?? "" }));
|
|
10024
|
+
}
|
|
10025
|
+
}
|
|
10026
|
+
|
|
8215
10027
|
//#endregion
|
|
8216
10028
|
//#region ../mcp/src/rules.ts
|
|
8217
10029
|
/** 규칙의 심각도 — 없으면 `error` */
|
|
@@ -8427,6 +10239,16 @@ const RULES = [
|
|
|
8427
10239
|
templateFile: "src/lib/ordering.ts",
|
|
8428
10240
|
severity: "warning"
|
|
8429
10241
|
}),
|
|
10242
|
+
defineRule({
|
|
10243
|
+
id: "product-link",
|
|
10244
|
+
title: mcp_rule_product_link_title,
|
|
10245
|
+
why: mcp_rule_product_link_why,
|
|
10246
|
+
fix: mcp_rule_product_link_fix,
|
|
10247
|
+
example: mcp_rule_product_link_example,
|
|
10248
|
+
doc: "/guides/order-numbers",
|
|
10249
|
+
templateFile: "src/lib/product-path.ts",
|
|
10250
|
+
severity: "warning"
|
|
10251
|
+
}),
|
|
8430
10252
|
defineRule({
|
|
8431
10253
|
id: "server-only-import",
|
|
8432
10254
|
title: mcp_rule_server_only_import_title,
|
|
@@ -8522,6 +10344,8 @@ function verifySources(files, options = {}) {
|
|
|
8522
10344
|
inFile("custom-input-private", file, mcp_verify_custom_input_private({ where: where() }), pattern);
|
|
8523
10345
|
break;
|
|
8524
10346
|
}
|
|
10347
|
+
const productIdLink = /params=\{\{\s*productId:\s*[\w.?]+\.productId\s*\}\}/;
|
|
10348
|
+
if (/\/products\/\$productId/.test(file.content) && productIdLink.test(file.content)) inFile("product-link", file, mcp_verify_product_link(), productIdLink);
|
|
8525
10349
|
const analyticsFactory = /createAnalytics\(/;
|
|
8526
10350
|
if (isServer$1 && analyticsFactory.test(file.content)) inFile("analytics-start", file, mcp_verify_analytics_start_server_file(), analyticsFactory);
|
|
8527
10351
|
if (isServer$1 && /createStorefrontClient\(/.test(file.content) && !/analyticsIdsFromCookie|visitorId/.test(file.content)) inFile("analytics-ids", file, mcp_verify_analytics_ids(), /createStorefrontClient\(/);
|
|
@@ -10075,7 +11899,13 @@ const VALUE_FLAGS = new Set([
|
|
|
10075
11899
|
"api-origin",
|
|
10076
11900
|
"store",
|
|
10077
11901
|
"accounts-origin",
|
|
10078
|
-
"template"
|
|
11902
|
+
"template",
|
|
11903
|
+
"key",
|
|
11904
|
+
"name",
|
|
11905
|
+
"port",
|
|
11906
|
+
"since",
|
|
11907
|
+
"data",
|
|
11908
|
+
"order"
|
|
10079
11909
|
]);
|
|
10080
11910
|
const BOOL_FLAGS = new Set([
|
|
10081
11911
|
"force",
|
|
@@ -10085,7 +11915,8 @@ const BOOL_FLAGS = new Set([
|
|
|
10085
11915
|
"help",
|
|
10086
11916
|
"version",
|
|
10087
11917
|
"device",
|
|
10088
|
-
"paste"
|
|
11918
|
+
"paste",
|
|
11919
|
+
"tail"
|
|
10089
11920
|
]);
|
|
10090
11921
|
function parseArgs(argv) {
|
|
10091
11922
|
const out = {
|
|
@@ -10097,7 +11928,11 @@ function parseArgs(argv) {
|
|
|
10097
11928
|
const arg = argv[i];
|
|
10098
11929
|
if (arg === "-h") out.flags.help = true;
|
|
10099
11930
|
else if (arg === "-v") out.flags.version = true;
|
|
10100
|
-
else if (arg
|
|
11931
|
+
else if (arg === "-m") {
|
|
11932
|
+
const value = argv[++i];
|
|
11933
|
+
if (value === void 0 || value.startsWith("--")) throw new CliError(cli_flag_value_required({ name: "message" }));
|
|
11934
|
+
out.flags.message = value;
|
|
11935
|
+
} else if (arg.startsWith("--")) {
|
|
10101
11936
|
const body = arg.slice(2);
|
|
10102
11937
|
const eq = body.indexOf("=");
|
|
10103
11938
|
const name = eq < 0 ? body : body.slice(0, eq);
|
|
@@ -10142,6 +11977,21 @@ async function main(argv, io) {
|
|
|
10142
11977
|
accountsOrigin: str(parsed.flags["accounts-origin"])
|
|
10143
11978
|
};
|
|
10144
11979
|
switch (parsed.command) {
|
|
11980
|
+
case "app": return appMain(parsed.positional, {
|
|
11981
|
+
token: str(parsed.flags.token),
|
|
11982
|
+
key: str(parsed.flags.key),
|
|
11983
|
+
apiOrigin: str(parsed.flags["api-origin"]),
|
|
11984
|
+
dir: str(parsed.flags.dir),
|
|
11985
|
+
store: str(parsed.flags.store),
|
|
11986
|
+
message: str(parsed.flags.message),
|
|
11987
|
+
name: str(parsed.flags.name),
|
|
11988
|
+
port: str(parsed.flags.port),
|
|
11989
|
+
since: str(parsed.flags.since),
|
|
11990
|
+
tail: parsed.flags.tail === true,
|
|
11991
|
+
data: str(parsed.flags.data),
|
|
11992
|
+
order: str(parsed.flags.order),
|
|
11993
|
+
yes: parsed.flags.yes === true
|
|
11994
|
+
}, io);
|
|
10145
11995
|
case "init": return init({
|
|
10146
11996
|
...common,
|
|
10147
11997
|
template: str(parsed.flags.template),
|