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.
Files changed (2) hide show
  1. package/dist/index.mjs +2016 -166
  2. 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
- * Type-only metadata attached to compiled message functions.
601
- *
602
- * @template Inputs
603
- * @template Options
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
- * A compiled, framework-neutral message part.
615
- *
616
- * @typedef {{
617
- * type: "text";
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
- * A message function is a message for a specific locale.
638
- *
639
- * @example
640
- * m.hello({ name: 'world' })
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
- * A message bundle function that selects the message to be returned.
646
- *
647
- * Uses `getLocale()` under the hood to determine the locale with an option.
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/locale.ts
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
- * 로케일 판정(이슈 #73) — 1순위 쿼리 `?lang=`, 2순위 `Accept-Language`(브라우저는 `navigator.languages`), 마지막 `ko`.
661
- * 순서는 컴파일 옵션(`scripts/compile.mjs`의 strategy)이 정하고, 여기서는 Paraglide에 없는 쿼리 판정만 등록한다.
662
- * api는 같은 순서를 `@nestjs/i18n` 리졸버(Query → AcceptLanguage)로 맞춘다.
1556
+
1557
+ * @param {Cli_App_Ui_Index_MissingInputs} inputs
1558
+ * @param {{ locale?: "ko" }} options
1559
+ * @returns {LocalizedString}
663
1560
  */
664
- const LOCALE_QUERY_PARAM = "lang";
665
- /** `?lang=ko-KR` → `ko`. 지원하지 않는 값·없으면 undefined(다음 판정으로 넘어간다) */
666
- function localeFromSearch(search) {
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/lazy-messages.ts
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
- * 라벨 표 — 값을 읽을 때마다 메시지 함수를 불러 그 순간의 로케일 문구를 돌려준다(이슈 #73).
688
- * 모듈을 읽을 때 문구를 만들면 서버 렌더링에서 요청 로케일이 아니라 기동 시점 로케일로 굳는다.
689
- * 타입은 `Record<K, string>`이라 호출부(`LABEL[key]`, `Object.entries(LABEL)`)를 바꾸지 않는다.
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
- function lazyMessages(table) {
696
- const labels = {};
697
- for (const key of Object.keys(table)) Object.defineProperty(labels, key, {
698
- enumerable: true,
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/cli_access_token_expired.js
1585
+ //#region ../i18n/src/paraglide/messages/cli_app_uploading.js
706
1586
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
707
- /** @typedef {{}} Cli_Access_Token_ExpiredInputs */
708
- const ko_cli_access_token_expired = () => {
709
- return `액세스 토큰이 만료됐습니다. 새 토큰을 만들거나 npx sayren login으로 다시 로그인하십시오`;
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 {Cli_Access_Token_ExpiredInputs} inputs
1593
+ * @param {Cli_App_UploadingInputs} inputs
714
1594
  * @param {{ locale?: "ko" }} options
715
1595
  * @returns {LocalizedString}
716
1596
  */
717
- const cli_access_token_expired = ((inputs = {}, options = {}) => {
1597
+ const cli_app_uploading = ((inputs, options = {}) => {
718
1598
  experimentalStaticLocale ?? options.locale ?? getLocale();
719
- return ko_cli_access_token_expired(inputs);
1599
+ return ko_cli_app_uploading(inputs);
720
1600
  });
721
1601
 
722
1602
  //#endregion
723
- //#region ../i18n/src/paraglide/messages/cli_account_mcp_written.js
1603
+ //#region ../i18n/src/paraglide/messages/cli_app_verify_ok.js
724
1604
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
725
- /** @typedef {{ file: NonNullable<unknown>, mcpFile: NonNullable<unknown> }} Cli_Account_Mcp_WrittenInputs */
726
- const ko_cli_account_mcp_written = (i) => {
727
- return `${i?.file} 작성 — 원격 MCP 주소와 상점. ${i?.mcpFile}에는 토큰을 쓰지 않았습니다 — MCP 클라이언트가 연결할 때 Sayren 계정으로 로그인합니다`;
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 {Cli_Account_Mcp_WrittenInputs} inputs
1611
+ * @param {Cli_App_Verify_OkInputs} inputs
732
1612
  * @param {{ locale?: "ko" }} options
733
1613
  * @returns {LocalizedString}
734
1614
  */
735
- const cli_account_mcp_written = ((inputs, options = {}) => {
1615
+ const cli_app_verify_ok = ((inputs, options = {}) => {
736
1616
  experimentalStaticLocale ?? options.locale ?? getLocale();
737
- return ko_cli_account_mcp_written(inputs);
1617
+ return ko_cli_app_verify_ok(inputs);
738
1618
  });
739
1619
 
740
1620
  //#endregion
741
- //#region ../i18n/src/paraglide/messages/cli_accounts_origin_unknown.js
1621
+ //#region ../i18n/src/paraglide/messages/cli_app_version_number_required.js
742
1622
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
743
- /** @typedef {{ origin: NonNullable<unknown> }} Cli_Accounts_Origin_UnknownInputs */
744
- const ko_cli_accounts_origin_unknown = (i) => {
745
- return `이 api 주소(${i?.origin})의 계정 서버를 모릅니다. --accounts-origin 또는 SAYREN_ACCOUNTS_ORIGIN으로 주십시오`;
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 {Cli_Accounts_Origin_UnknownInputs} inputs
1629
+ * @param {Cli_App_Version_Number_RequiredInputs} inputs
750
1630
  * @param {{ locale?: "ko" }} options
751
1631
  * @returns {LocalizedString}
752
1632
  */
753
- const cli_accounts_origin_unknown = ((inputs, options = {}) => {
1633
+ const cli_app_version_number_required = ((inputs = {}, options = {}) => {
754
1634
  experimentalStaticLocale ?? options.locale ?? getLocale();
755
- return ko_cli_accounts_origin_unknown(inputs);
1635
+ return ko_cli_app_version_number_required(inputs);
756
1636
  });
757
1637
 
758
1638
  //#endregion
759
- //#region ../i18n/src/paraglide/messages/cli_accounts_unreachable.js
1639
+ //#region ../i18n/src/paraglide/messages/cli_app_warning.js
760
1640
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
761
- /** @typedef {{}} Cli_Accounts_UnreachableInputs */
762
- const ko_cli_accounts_unreachable = () => {
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 {Cli_Accounts_UnreachableInputs} inputs
1647
+ * @param {Cli_App_WarningInputs} inputs
768
1648
  * @param {{ locale?: "ko" }} options
769
1649
  * @returns {LocalizedString}
770
1650
  */
771
- const cli_accounts_unreachable = ((inputs = {}, options = {}) => {
1651
+ const cli_app_warning = ((inputs, options = {}) => {
772
1652
  experimentalStaticLocale ?? options.locale ?? getLocale();
773
- return ko_cli_accounts_unreachable(inputs);
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 `결제 수단이 없거나 공개한 쇼핑몰의 실결제가 아직 열리지 않은 상점은 결제 요청이 409 \`PAYMENT_NOT_CONFIGURED\`다. 버튼을 그대로 두면 구매자가 담고 주문서까지 가서야 결제가 안 된다는 것을 안다`;
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.startsWith("--")) {
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),