sayren 0.3.3 → 0.4.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 +858 -14
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -1278,6 +1278,150 @@ const cli_deploy_build_in_progress = ((inputs = {}, options = {}) => {
1278
1278
  return ko_cli_deploy_build_in_progress(inputs);
1279
1279
  });
1280
1280
 
1281
+ //#endregion
1282
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_failed.js
1283
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1284
+ /** @typedef {{ code: NonNullable<unknown> }} Cli_Deploy_Build_Local_FailedInputs */
1285
+ const ko_cli_deploy_build_local_failed = (i) => {
1286
+ return `로컬 빌드가 실패해 올리지 않았습니다(종료 코드 ${i?.code}). 위 출력을 확인해 고친 뒤 다시 배포하십시오. 빌드를 확인하지 않고 올리려면 --skip-build를 붙이십시오`;
1287
+ };
1288
+ /**
1289
+
1290
+ * @param {Cli_Deploy_Build_Local_FailedInputs} inputs
1291
+ * @param {{ locale?: "ko" }} options
1292
+ * @returns {LocalizedString}
1293
+ */
1294
+ const cli_deploy_build_local_failed = ((inputs, options = {}) => {
1295
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1296
+ return ko_cli_deploy_build_local_failed(inputs);
1297
+ });
1298
+
1299
+ //#endregion
1300
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_no_deps.js
1301
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1302
+ /** @typedef {{}} Cli_Deploy_Build_Local_No_DepsInputs */
1303
+ const ko_cli_deploy_build_local_no_deps = () => {
1304
+ return `의존성이 설치되지 않아(node_modules 없음) 로컬 빌드를 건너뜁니다. npm install --ignore-scripts 뒤 배포하면 올리기 전에 빌드를 확인합니다`;
1305
+ };
1306
+ /**
1307
+
1308
+ * @param {Cli_Deploy_Build_Local_No_DepsInputs} inputs
1309
+ * @param {{ locale?: "ko" }} options
1310
+ * @returns {LocalizedString}
1311
+ */
1312
+ const cli_deploy_build_local_no_deps = ((inputs = {}, options = {}) => {
1313
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1314
+ return ko_cli_deploy_build_local_no_deps(inputs);
1315
+ });
1316
+
1317
+ //#endregion
1318
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_no_script.js
1319
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1320
+ /** @typedef {{}} Cli_Deploy_Build_Local_No_ScriptInputs */
1321
+ const ko_cli_deploy_build_local_no_script = () => {
1322
+ return `package.json에 build 스크립트가 없어 로컬 빌드를 건너뜁니다`;
1323
+ };
1324
+ /**
1325
+
1326
+ * @param {Cli_Deploy_Build_Local_No_ScriptInputs} inputs
1327
+ * @param {{ locale?: "ko" }} options
1328
+ * @returns {LocalizedString}
1329
+ */
1330
+ const cli_deploy_build_local_no_script = ((inputs = {}, options = {}) => {
1331
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1332
+ return ko_cli_deploy_build_local_no_script(inputs);
1333
+ });
1334
+
1335
+ //#endregion
1336
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_output.js
1337
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1338
+ /** @typedef {{ count: NonNullable<unknown> }} Cli_Deploy_Build_Local_OutputInputs */
1339
+ const ko_cli_deploy_build_local_output = (i) => {
1340
+ return `빌드 출력(마지막 ${i?.count}줄):`;
1341
+ };
1342
+ /**
1343
+
1344
+ * @param {Cli_Deploy_Build_Local_OutputInputs} inputs
1345
+ * @param {{ locale?: "ko" }} options
1346
+ * @returns {LocalizedString}
1347
+ */
1348
+ const cli_deploy_build_local_output = ((inputs, options = {}) => {
1349
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1350
+ return ko_cli_deploy_build_local_output(inputs);
1351
+ });
1352
+
1353
+ //#endregion
1354
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_passed.js
1355
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1356
+ /** @typedef {{ seconds: NonNullable<unknown> }} Cli_Deploy_Build_Local_PassedInputs */
1357
+ const ko_cli_deploy_build_local_passed = (i) => {
1358
+ return `로컬 빌드 통과(${i?.seconds}초)`;
1359
+ };
1360
+ /**
1361
+
1362
+ * @param {Cli_Deploy_Build_Local_PassedInputs} inputs
1363
+ * @param {{ locale?: "ko" }} options
1364
+ * @returns {LocalizedString}
1365
+ */
1366
+ const cli_deploy_build_local_passed = ((inputs, options = {}) => {
1367
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1368
+ return ko_cli_deploy_build_local_passed(inputs);
1369
+ });
1370
+
1371
+ //#endregion
1372
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_running.js
1373
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1374
+ /** @typedef {{}} Cli_Deploy_Build_Local_RunningInputs */
1375
+ const ko_cli_deploy_build_local_running = () => {
1376
+ return `로컬 빌드(npm run build)를 실행합니다. 서버 빌드가 실패할 것을 올리기 전에 확인합니다`;
1377
+ };
1378
+ /**
1379
+
1380
+ * @param {Cli_Deploy_Build_Local_RunningInputs} inputs
1381
+ * @param {{ locale?: "ko" }} options
1382
+ * @returns {LocalizedString}
1383
+ */
1384
+ const cli_deploy_build_local_running = ((inputs = {}, options = {}) => {
1385
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1386
+ return ko_cli_deploy_build_local_running(inputs);
1387
+ });
1388
+
1389
+ //#endregion
1390
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_skipped_flag.js
1391
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1392
+ /** @typedef {{}} Cli_Deploy_Build_Local_Skipped_FlagInputs */
1393
+ const ko_cli_deploy_build_local_skipped_flag = () => {
1394
+ return `로컬 빌드를 건너뜁니다(--skip-build). 서버 빌드가 같은 빌드를 다시 합니다`;
1395
+ };
1396
+ /**
1397
+
1398
+ * @param {Cli_Deploy_Build_Local_Skipped_FlagInputs} inputs
1399
+ * @param {{ locale?: "ko" }} options
1400
+ * @returns {LocalizedString}
1401
+ */
1402
+ const cli_deploy_build_local_skipped_flag = ((inputs = {}, options = {}) => {
1403
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1404
+ return ko_cli_deploy_build_local_skipped_flag(inputs);
1405
+ });
1406
+
1407
+ //#endregion
1408
+ //#region ../i18n/src/paraglide/messages/cli_deploy_build_local_spawn_failed.js
1409
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1410
+ /** @typedef {{ message: NonNullable<unknown> }} Cli_Deploy_Build_Local_Spawn_FailedInputs */
1411
+ const ko_cli_deploy_build_local_spawn_failed = (i) => {
1412
+ return `로컬 빌드를 시작하지 못해 올리지 않았습니다(${i?.message}). npm이 PATH에 있는지 확인하거나 --skip-build를 붙이십시오`;
1413
+ };
1414
+ /**
1415
+
1416
+ * @param {Cli_Deploy_Build_Local_Spawn_FailedInputs} inputs
1417
+ * @param {{ locale?: "ko" }} options
1418
+ * @returns {LocalizedString}
1419
+ */
1420
+ const cli_deploy_build_local_spawn_failed = ((inputs, options = {}) => {
1421
+ experimentalStaticLocale ?? options.locale ?? getLocale();
1422
+ return ko_cli_deploy_build_local_spawn_failed(inputs);
1423
+ });
1424
+
1281
1425
  //#endregion
1282
1426
  //#region ../i18n/src/paraglide/messages/cli_deploy_build_started.js
1283
1427
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
@@ -1890,6 +2034,295 @@ const cli_flag_value_required = ((inputs, options = {}) => {
1890
2034
  return ko_cli_flag_value_required(inputs);
1891
2035
  });
1892
2036
 
2037
+ //#endregion
2038
+ //#region ../i18n/src/paraglide/messages/cli_init_confirm.js
2039
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2040
+ /** @typedef {{}} Cli_Init_ConfirmInputs */
2041
+ const ko_cli_init_confirm = () => {
2042
+ return `사이트를 만드시겠습니까?`;
2043
+ };
2044
+ /**
2045
+
2046
+ * @param {Cli_Init_ConfirmInputs} inputs
2047
+ * @param {{ locale?: "ko" }} options
2048
+ * @returns {LocalizedString}
2049
+ */
2050
+ const cli_init_confirm = ((inputs = {}, options = {}) => {
2051
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2052
+ return ko_cli_init_confirm(inputs);
2053
+ });
2054
+
2055
+ //#endregion
2056
+ //#region ../i18n/src/paraglide/messages/cli_init_created.js
2057
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2058
+ /** @typedef {{ url: NonNullable<unknown> }} Cli_Init_CreatedInputs */
2059
+ const ko_cli_init_created = (i) => {
2060
+ return `사이트를 만들었습니다 — ${i?.url} (오픈 준비 중, 반영까지 1분 안팎)`;
2061
+ };
2062
+ /**
2063
+
2064
+ * @param {Cli_Init_CreatedInputs} inputs
2065
+ * @param {{ locale?: "ko" }} options
2066
+ * @returns {LocalizedString}
2067
+ */
2068
+ const cli_init_created = ((inputs, options = {}) => {
2069
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2070
+ return ko_cli_init_created(inputs);
2071
+ });
2072
+
2073
+ //#endregion
2074
+ //#region ../i18n/src/paraglide/messages/cli_init_dir_not_empty.js
2075
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2076
+ /** @typedef {{ dir: NonNullable<unknown> }} Cli_Init_Dir_Not_EmptyInputs */
2077
+ const ko_cli_init_dir_not_empty = (i) => {
2078
+ return `폴더가 비어 있지 않습니다: ${i?.dir}. 빈 폴더나 새 폴더 이름을 주십시오`;
2079
+ };
2080
+ /**
2081
+
2082
+ * @param {Cli_Init_Dir_Not_EmptyInputs} inputs
2083
+ * @param {{ locale?: "ko" }} options
2084
+ * @returns {LocalizedString}
2085
+ */
2086
+ const cli_init_dir_not_empty = ((inputs, options = {}) => {
2087
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2088
+ return ko_cli_init_dir_not_empty(inputs);
2089
+ });
2090
+
2091
+ //#endregion
2092
+ //#region ../i18n/src/paraglide/messages/cli_init_hosting_disabled.js
2093
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2094
+ /** @typedef {{}} Cli_Init_Hosting_DisabledInputs */
2095
+ const ko_cli_init_hosting_disabled = () => {
2096
+ return `지금은 새 호스팅 사이트를 만들 수 없습니다(호스팅 기능이 꺼져 있습니다)`;
2097
+ };
2098
+ /**
2099
+
2100
+ * @param {Cli_Init_Hosting_DisabledInputs} inputs
2101
+ * @param {{ locale?: "ko" }} options
2102
+ * @returns {LocalizedString}
2103
+ */
2104
+ const cli_init_hosting_disabled = ((inputs = {}, options = {}) => {
2105
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2106
+ return ko_cli_init_hosting_disabled(inputs);
2107
+ });
2108
+
2109
+ //#endregion
2110
+ //#region ../i18n/src/paraglide/messages/cli_init_inside_pulled.js
2111
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2112
+ /** @typedef {{}} Cli_Init_Inside_PulledInputs */
2113
+ const ko_cli_init_inside_pulled = () => {
2114
+ return `지금 폴더는 이미 받은 사이트 폴더입니다. 새 사이트는 다른 폴더에서 npx sayren init <폴더>로 만들고, 이 사이트를 다시 받으려면 npx sayren pull --force를 쓰십시오`;
2115
+ };
2116
+ /**
2117
+
2118
+ * @param {Cli_Init_Inside_PulledInputs} inputs
2119
+ * @param {{ locale?: "ko" }} options
2120
+ * @returns {LocalizedString}
2121
+ */
2122
+ const cli_init_inside_pulled = ((inputs = {}, options = {}) => {
2123
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2124
+ return ko_cli_init_inside_pulled(inputs);
2125
+ });
2126
+
2127
+ //#endregion
2128
+ //#region ../i18n/src/paraglide/messages/cli_init_no_templates.js
2129
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2130
+ /** @typedef {{}} Cli_Init_No_TemplatesInputs */
2131
+ const ko_cli_init_no_templates = () => {
2132
+ return `쓸 수 있는 템플릿이 없습니다`;
2133
+ };
2134
+ /**
2135
+
2136
+ * @param {Cli_Init_No_TemplatesInputs} inputs
2137
+ * @param {{ locale?: "ko" }} options
2138
+ * @returns {LocalizedString}
2139
+ */
2140
+ const cli_init_no_templates = ((inputs = {}, options = {}) => {
2141
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2142
+ return ko_cli_init_no_templates(inputs);
2143
+ });
2144
+
2145
+ //#endregion
2146
+ //#region ../i18n/src/paraglide/messages/cli_init_pull_failed.js
2147
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2148
+ /** @typedef {{ storeId: NonNullable<unknown> }} Cli_Init_Pull_FailedInputs */
2149
+ const ko_cli_init_pull_failed = (i) => {
2150
+ return `사이트는 만들었지만 소스를 받지 못했습니다. 다시 만들지 말고 npx sayren pull --project ${i?.storeId}로 받으십시오`;
2151
+ };
2152
+ /**
2153
+
2154
+ * @param {Cli_Init_Pull_FailedInputs} inputs
2155
+ * @param {{ locale?: "ko" }} options
2156
+ * @returns {LocalizedString}
2157
+ */
2158
+ const cli_init_pull_failed = ((inputs, options = {}) => {
2159
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2160
+ return ko_cli_init_pull_failed(inputs);
2161
+ });
2162
+
2163
+ //#endregion
2164
+ //#region ../i18n/src/paraglide/messages/cli_init_site_exists.js
2165
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2166
+ /** @typedef {{ storeId: NonNullable<unknown> }} Cli_Init_Site_ExistsInputs */
2167
+ const ko_cli_init_site_exists = (i) => {
2168
+ return `이 상점에는 이미 호스팅 사이트가 있어 새로 만들지 않았습니다. npx sayren pull --project ${i?.storeId}로 소스를 받으십시오`;
2169
+ };
2170
+ /**
2171
+
2172
+ * @param {Cli_Init_Site_ExistsInputs} inputs
2173
+ * @param {{ locale?: "ko" }} options
2174
+ * @returns {LocalizedString}
2175
+ */
2176
+ const cli_init_site_exists = ((inputs, options = {}) => {
2177
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2178
+ return ko_cli_init_site_exists(inputs);
2179
+ });
2180
+
2181
+ //#endregion
2182
+ //#region ../i18n/src/paraglide/messages/cli_init_site_url.js
2183
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2184
+ /** @typedef {{ url: NonNullable<unknown> }} Cli_Init_Site_UrlInputs */
2185
+ const ko_cli_init_site_url = (i) => {
2186
+ return `지금 사이트: ${i?.url}`;
2187
+ };
2188
+ /**
2189
+
2190
+ * @param {Cli_Init_Site_UrlInputs} inputs
2191
+ * @param {{ locale?: "ko" }} options
2192
+ * @returns {LocalizedString}
2193
+ */
2194
+ const cli_init_site_url = ((inputs, options = {}) => {
2195
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2196
+ return ko_cli_init_site_url(inputs);
2197
+ });
2198
+
2199
+ //#endregion
2200
+ //#region ../i18n/src/paraglide/messages/cli_init_slug_taken.js
2201
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2202
+ /** @typedef {{}} Cli_Init_Slug_TakenInputs */
2203
+ const ko_cli_init_slug_taken = () => {
2204
+ return `상점 코드와 같은 주소를 다른 사이트가 쓰고 있어 만들지 못했습니다. 상점 플랫폼 › 스토어프론트에서 다른 주소로 만든 뒤 npx sayren pull로 받으십시오`;
2205
+ };
2206
+ /**
2207
+
2208
+ * @param {Cli_Init_Slug_TakenInputs} inputs
2209
+ * @param {{ locale?: "ko" }} options
2210
+ * @returns {LocalizedString}
2211
+ */
2212
+ const cli_init_slug_taken = ((inputs = {}, options = {}) => {
2213
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2214
+ return ko_cli_init_slug_taken(inputs);
2215
+ });
2216
+
2217
+ //#endregion
2218
+ //#region ../i18n/src/paraglide/messages/cli_init_summary.js
2219
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2220
+ /** @typedef {{ storeId: NonNullable<unknown>, name: NonNullable<unknown>, templateId: NonNullable<unknown>, version: NonNullable<unknown> }} Cli_Init_SummaryInputs */
2221
+ const ko_cli_init_summary = (i) => {
2222
+ return `상점 ${i?.storeId}에 템플릿 ${i?.name}(${i?.templateId} v${i?.version})로 호스팅 사이트를 만듭니다. 주소는 상점 코드이고 오픈 준비 중으로 시작합니다. 만들기는 상점당 30일에 3번까지입니다`;
2223
+ };
2224
+ /**
2225
+
2226
+ * @param {Cli_Init_SummaryInputs} inputs
2227
+ * @param {{ locale?: "ko" }} options
2228
+ * @returns {LocalizedString}
2229
+ */
2230
+ const cli_init_summary = ((inputs, options = {}) => {
2231
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2232
+ return ko_cli_init_summary(inputs);
2233
+ });
2234
+
2235
+ //#endregion
2236
+ //#region ../i18n/src/paraglide/messages/cli_init_template_option.js
2237
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2238
+ /** @typedef {{ templateId: NonNullable<unknown>, name: NonNullable<unknown>, version: NonNullable<unknown>, description: NonNullable<unknown> }} Cli_Init_Template_OptionInputs */
2239
+ const ko_cli_init_template_option = (i) => {
2240
+ return `${i?.templateId} — ${i?.name} v${i?.version}: ${i?.description}`;
2241
+ };
2242
+ /**
2243
+
2244
+ * @param {Cli_Init_Template_OptionInputs} inputs
2245
+ * @param {{ locale?: "ko" }} options
2246
+ * @returns {LocalizedString}
2247
+ */
2248
+ const cli_init_template_option = ((inputs, options = {}) => {
2249
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2250
+ return ko_cli_init_template_option(inputs);
2251
+ });
2252
+
2253
+ //#endregion
2254
+ //#region ../i18n/src/paraglide/messages/cli_init_template_prompt.js
2255
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2256
+ /** @typedef {{}} Cli_Init_Template_PromptInputs */
2257
+ const ko_cli_init_template_prompt = () => {
2258
+ return `템플릿을 고르십시오:`;
2259
+ };
2260
+ /**
2261
+
2262
+ * @param {Cli_Init_Template_PromptInputs} inputs
2263
+ * @param {{ locale?: "ko" }} options
2264
+ * @returns {LocalizedString}
2265
+ */
2266
+ const cli_init_template_prompt = ((inputs = {}, options = {}) => {
2267
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2268
+ return ko_cli_init_template_prompt(inputs);
2269
+ });
2270
+
2271
+ //#endregion
2272
+ //#region ../i18n/src/paraglide/messages/cli_init_template_required.js
2273
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2274
+ /** @typedef {{ list: NonNullable<unknown> }} Cli_Init_Template_RequiredInputs */
2275
+ const ko_cli_init_template_required = (i) => {
2276
+ return `템플릿을 고르지 않았습니다. --template <id>로 주십시오(비대화형은 필수입니다):
2277
+ ${i?.list}`;
2278
+ };
2279
+ /**
2280
+
2281
+ * @param {Cli_Init_Template_RequiredInputs} inputs
2282
+ * @param {{ locale?: "ko" }} options
2283
+ * @returns {LocalizedString}
2284
+ */
2285
+ const cli_init_template_required = ((inputs, options = {}) => {
2286
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2287
+ return ko_cli_init_template_required(inputs);
2288
+ });
2289
+
2290
+ //#endregion
2291
+ //#region ../i18n/src/paraglide/messages/cli_init_template_unknown.js
2292
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2293
+ /** @typedef {{ templateId: NonNullable<unknown>, ids: NonNullable<unknown> }} Cli_Init_Template_UnknownInputs */
2294
+ const ko_cli_init_template_unknown = (i) => {
2295
+ return `템플릿을 찾지 못했습니다: ${i?.templateId}. 쓸 수 있는 템플릿: ${i?.ids}`;
2296
+ };
2297
+ /**
2298
+
2299
+ * @param {Cli_Init_Template_UnknownInputs} inputs
2300
+ * @param {{ locale?: "ko" }} options
2301
+ * @returns {LocalizedString}
2302
+ */
2303
+ const cli_init_template_unknown = ((inputs, options = {}) => {
2304
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2305
+ return ko_cli_init_template_unknown(inputs);
2306
+ });
2307
+
2308
+ //#endregion
2309
+ //#region ../i18n/src/paraglide/messages/cli_init_yes_required.js
2310
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2311
+ /** @typedef {{}} Cli_Init_Yes_RequiredInputs */
2312
+ const ko_cli_init_yes_required = () => {
2313
+ return `사이트 만들기를 확인할 수 없는 환경입니다. --yes를 함께 주십시오`;
2314
+ };
2315
+ /**
2316
+
2317
+ * @param {Cli_Init_Yes_RequiredInputs} inputs
2318
+ * @param {{ locale?: "ko" }} options
2319
+ * @returns {LocalizedString}
2320
+ */
2321
+ const cli_init_yes_required = ((inputs = {}, options = {}) => {
2322
+ experimentalStaticLocale ?? options.locale ?? getLocale();
2323
+ return ko_cli_init_yes_required(inputs);
2324
+ });
2325
+
1893
2326
  //#endregion
1894
2327
  //#region ../i18n/src/paraglide/messages/cli_invalid_file_path.js
1895
2328
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
@@ -2908,7 +3341,7 @@ const cli_pull_next_steps = ((inputs, options = {}) => {
2908
3341
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
2909
3342
  /** @typedef {{}} Cli_Pull_No_SiteInputs */
2910
3343
  const ko_cli_pull_no_site = () => {
2911
- return `이 상점에는 호스팅 사이트가 없습니다. 상점 플랫폼 › 스토어프론트에서 사이트를 만든 뒤 다시 받으십시오`;
3344
+ return `이 상점에는 호스팅 사이트가 없습니다. npx sayren init으로 만들거나 상점 플랫폼 › 스토어프론트에서 만든 뒤 다시 받으십시오`;
2912
3345
  };
2913
3346
  /**
2914
3347
 
@@ -3894,8 +4327,9 @@ const ko_cli_usage = (i) => {
3894
4327
  npx sayren login --paste # 액세스 토큰을 입력받아 저장(셸 기록에 남지 않습니다)
3895
4328
  npx sayren logout
3896
4329
  npx sayren whoami
4330
+ npx sayren init [폴더] [--template <id>] [--store <storeId>] [--yes]
3897
4331
  npx sayren pull --project <storeId> [폴더] [--force [--yes]]
3898
- npx sayren deploy [--no-publish] [--message <메모>] [--force [--yes]]
4332
+ npx sayren deploy [--no-publish] [--skip-build] [--message <메모>] [--force [--yes]]
3899
4333
  npx sayren status
3900
4334
  npx sayren verify
3901
4335
 
@@ -3903,8 +4337,9 @@ const ko_cli_usage = (i) => {
3903
4337
  login Sayren 계정으로 로그인합니다. 로그인 토큰은 사용자 설정 폴더(~/.config/sayren/credentials.json)에 둡니다
3904
4338
  logout 로그인을 폐기하고 이 컴퓨터의 토큰을 지웁니다
3905
4339
  whoami 쓰는 계정·토큰 종류·상점 범위
4340
+ init 템플릿을 골라 호스팅 사이트를 만들고 pull과 같은 폴더(기본: 상점 코드)로 받습니다. 사이트가 이미 있으면 만들지 않습니다
3906
4341
  pull 호스팅 중인 사이트의 작업 트리를 폴더(기본: 상점 코드)로 받고 .mcp.json·.env·.sayren/state.json을 씁니다
3907
- deploy 로컬 검사 → 작업 트리 올리기 → 빌드 → 발행. 원격이 pull 이후 바뀌었으면 덮어쓰지 않습니다
4342
+ deploy 로컬 검사 → 로컬 빌드 → 작업 트리 올리기 → 빌드 → 발행. 원격이 pull 이후 바뀌었으면 덮어쓰지 않습니다
3908
4343
  status 사이트·발행 버전·최근 빌드·편집 환경
3909
4344
  verify 로컬 규칙·경로·크기 검사(서버 빌드와 같은 규칙)
3910
4345
 
@@ -3915,11 +4350,13 @@ const ko_cli_usage = (i) => {
3915
4350
 
3916
4351
  옵션:
3917
4352
  --project <storeId> pull: 받을 상점(--store와 같습니다)
3918
- --dir <폴더> 작업 폴더(기본: 지금 폴더, pull은 상점 코드)
4353
+ --template <id> init: 템플릿 id(비대화형은 필수)
4354
+ --dir <폴더> 작업 폴더(기본: 지금 폴더, pull·init은 상점 코드)
3919
4355
  --force pull: 같은 사이트를 받은 폴더에 원격을 다시 받습니다(지울·덮을 파일을 보여 주고 묻습니다)
3920
4356
  deploy: 원격 변경·열린 편집 환경을 무시하고 덮어씁니다(확인을 묻습니다)
3921
- --yes --force의 확인을 묻지 않습니다(비대화형)
4357
+ --yes --force·init의 확인을 묻지 않습니다(비대화형)
3922
4358
  --no-publish deploy: 버전까지만 만들고 발행하지 않습니다
4359
+ --skip-build deploy: 올리기 전 로컬 빌드(npm run build)를 건너뜁니다
3923
4360
  --message <메모> deploy: 버전 메모
3924
4361
  --store <storeId> 다룰 상점(SAYREN_STORE_ID)
3925
4362
  --device login: 브라우저 대신 기기 코드로 로그인합니다(SSH 등)
@@ -4433,7 +4870,7 @@ const mcp_report_template_hint_local = ((inputs, options = {}) => {
4433
4870
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
4434
4871
  /** @typedef {{ files: NonNullable<unknown> }} Mcp_Report_Template_Hint_RemoteInputs */
4435
4872
  const ko_mcp_report_template_hint_remote = (i) => {
4436
- return `손대지 않아야 하는 파일을 고친 결과일 수 있다 — ${i?.files}을(를) \`read_file\`로 읽어 규칙의 \`example\`과 대조하고 되돌린다.`;
4873
+ return `손대지 않아야 하는 파일을 고친 결과일 수 있다 — ${i?.files}을(를) \`read_file\`로 읽어 \`get_template_file\`의 템플릿 원본(지금 카탈로그 버전)이나 규칙의 \`example\`과 대조하고 되돌린다.`;
4437
4874
  };
4438
4875
  /**
4439
4876
 
@@ -5318,7 +5755,7 @@ const mcp_rule_local_import_exists_example = ((inputs = {}, options = {}) => {
5318
5755
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5319
5756
  /** @typedef {{}} Mcp_Rule_Local_Import_Exists_FixInputs */
5320
5757
  const ko_mcp_rule_local_import_exists_fix = () => {
5321
- return `빠진 파일을 템플릿 원본(로컬 \`get_template_file\`, 또는 \`npx @sayren/mcp create\`로 받은 사본)에서 가져와 같은 경로에 둔다. 호스팅 사이트는 \`write_files\`(\`expectedHash: null\`)나 \`sayren deploy\`로 올린다. 쓰지 않는 import면 지운다. 템플릿의 화면 문구는 \`messages/ko.json\`이다`;
5758
+ return `빠진 파일을 템플릿 원본(\`get_template_file\`, 또는 \`npx @sayren/mcp create\`로 받은 사본)에서 가져와 같은 경로에 둔다. 호스팅 사이트는 \`write_files\`(\`expectedHash: null\`)나 \`sayren deploy\`로 올린다. 쓰지 않는 import면 지운다. 템플릿의 화면 문구는 \`messages/ko.json\`이다`;
5322
5759
  };
5323
5760
  /**
5324
5761
 
@@ -5953,6 +6390,85 @@ const mcp_rule_server_only_env_why = ((inputs = {}, options = {}) => {
5953
6390
  return ko_mcp_rule_server_only_env_why(inputs);
5954
6391
  });
5955
6392
 
6393
+ //#endregion
6394
+ //#region ../i18n/src/paraglide/messages/mcp_rule_server_only_import_example.js
6395
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
6396
+ /** @typedef {{}} Mcp_Rule_Server_Only_Import_ExampleInputs */
6397
+ const ko_mcp_rule_server_only_import_example = () => {
6398
+ return `// src/lib/my-orders.ts — 브라우저도 가져가는 파일
6399
+ import { apiFor } from "./api.server";
6400
+ import { readToken } from "./session.server";
6401
+
6402
+ // handler 안에서만 부른다. 일반 함수(export function)로 두면 브라우저 번들에 남는다
6403
+ export const listMyOrders = createServerFn({ method: "GET" }).handler(() =>
6404
+ apiFor({ accessToken: readToken() }).orders.list(),
6405
+ );`;
6406
+ };
6407
+ /**
6408
+
6409
+ * @param {Mcp_Rule_Server_Only_Import_ExampleInputs} inputs
6410
+ * @param {{ locale?: "ko" }} options
6411
+ * @returns {LocalizedString}
6412
+ */
6413
+ const mcp_rule_server_only_import_example = ((inputs = {}, options = {}) => {
6414
+ experimentalStaticLocale ?? options.locale ?? getLocale();
6415
+ return ko_mcp_rule_server_only_import_example(inputs);
6416
+ });
6417
+
6418
+ //#endregion
6419
+ //#region ../i18n/src/paraglide/messages/mcp_rule_server_only_import_fix.js
6420
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
6421
+ /** @typedef {{}} Mcp_Rule_Server_Only_Import_FixInputs */
6422
+ const ko_mcp_rule_server_only_import_fix = () => {
6423
+ return `서버 API를 부르는 함수는 \`*.server.ts\` 파일로 옮기거나 \`createServerFn().handler(…)\`로 감싼다. 브라우저가 함께 가져가는 파일(컴포넌트·\`src/lib/*.ts\`)은 서버 함수만 내보내고, \`*.server.ts\`의 값은 handler 안에서만 부른다. 서버 전용 모듈을 다시 내보내지 않는다`;
6424
+ };
6425
+ /**
6426
+
6427
+ * @param {Mcp_Rule_Server_Only_Import_FixInputs} inputs
6428
+ * @param {{ locale?: "ko" }} options
6429
+ * @returns {LocalizedString}
6430
+ */
6431
+ const mcp_rule_server_only_import_fix = ((inputs = {}, options = {}) => {
6432
+ experimentalStaticLocale ?? options.locale ?? getLocale();
6433
+ return ko_mcp_rule_server_only_import_fix(inputs);
6434
+ });
6435
+
6436
+ //#endregion
6437
+ //#region ../i18n/src/paraglide/messages/mcp_rule_server_only_import_title.js
6438
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
6439
+ /** @typedef {{}} Mcp_Rule_Server_Only_Import_TitleInputs */
6440
+ const ko_mcp_rule_server_only_import_title = () => {
6441
+ return `서버 전용 모듈(\`*.server.ts\`)에서 가져온 것은 서버 함수 안에서만 쓴다`;
6442
+ };
6443
+ /**
6444
+
6445
+ * @param {Mcp_Rule_Server_Only_Import_TitleInputs} inputs
6446
+ * @param {{ locale?: "ko" }} options
6447
+ * @returns {LocalizedString}
6448
+ */
6449
+ const mcp_rule_server_only_import_title = ((inputs = {}, options = {}) => {
6450
+ experimentalStaticLocale ?? options.locale ?? getLocale();
6451
+ return ko_mcp_rule_server_only_import_title(inputs);
6452
+ });
6453
+
6454
+ //#endregion
6455
+ //#region ../i18n/src/paraglide/messages/mcp_rule_server_only_import_why.js
6456
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
6457
+ /** @typedef {{}} Mcp_Rule_Server_Only_Import_WhyInputs */
6458
+ const ko_mcp_rule_server_only_import_why = () => {
6459
+ return `TanStack Start는 서버 함수 handler만 브라우저 번들에서 걷어낸다. 그 밖에서 \`*.server.ts\`(\`api.server\` 등)의 값을 쓰면 import가 브라우저 번들에 남고, import 보호가 버전 빌드(\`vite build\`)를 실패시킨다. 개발 서버와 타입 검사는 이것을 잡지 않는다`;
6460
+ };
6461
+ /**
6462
+
6463
+ * @param {Mcp_Rule_Server_Only_Import_WhyInputs} inputs
6464
+ * @param {{ locale?: "ko" }} options
6465
+ * @returns {LocalizedString}
6466
+ */
6467
+ const mcp_rule_server_only_import_why = ((inputs = {}, options = {}) => {
6468
+ experimentalStaticLocale ?? options.locale ?? getLocale();
6469
+ return ko_mcp_rule_server_only_import_why(inputs);
6470
+ });
6471
+
5956
6472
  //#endregion
5957
6473
  //#region ../i18n/src/paraglide/messages/mcp_rule_tenant_header_fix.js
5958
6474
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
@@ -6565,6 +7081,42 @@ const mcp_verify_server_only_env = ((inputs = {}, options = {}) => {
6565
7081
  return ko_mcp_verify_server_only_env(inputs);
6566
7082
  });
6567
7083
 
7084
+ //#endregion
7085
+ //#region ../i18n/src/paraglide/messages/mcp_verify_server_only_import.js
7086
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
7087
+ /** @typedef {{ specifier: NonNullable<unknown>, name: NonNullable<unknown> }} Mcp_Verify_Server_Only_ImportInputs */
7088
+ const ko_mcp_verify_server_only_import = (i) => {
7089
+ return `서버 전용 모듈 \`${i?.specifier}\`의 \`${i?.name}\`을 서버 함수(handler) 밖에서 쓴다. 브라우저 번들에 남아 버전 빌드가 import 보호로 실패한다`;
7090
+ };
7091
+ /**
7092
+
7093
+ * @param {Mcp_Verify_Server_Only_ImportInputs} inputs
7094
+ * @param {{ locale?: "ko" }} options
7095
+ * @returns {LocalizedString}
7096
+ */
7097
+ const mcp_verify_server_only_import = ((inputs, options = {}) => {
7098
+ experimentalStaticLocale ?? options.locale ?? getLocale();
7099
+ return ko_mcp_verify_server_only_import(inputs);
7100
+ });
7101
+
7102
+ //#endregion
7103
+ //#region ../i18n/src/paraglide/messages/mcp_verify_server_only_import_reexport.js
7104
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
7105
+ /** @typedef {{ specifier: NonNullable<unknown> }} Mcp_Verify_Server_Only_Import_ReexportInputs */
7106
+ const ko_mcp_verify_server_only_import_reexport = (i) => {
7107
+ return `서버 전용 모듈 \`${i?.specifier}\`를 부수 효과 import나 다시 내보내기로 가져온다. 브라우저 번들에 남아 버전 빌드가 import 보호로 실패한다`;
7108
+ };
7109
+ /**
7110
+
7111
+ * @param {Mcp_Verify_Server_Only_Import_ReexportInputs} inputs
7112
+ * @param {{ locale?: "ko" }} options
7113
+ * @returns {LocalizedString}
7114
+ */
7115
+ const mcp_verify_server_only_import_reexport = ((inputs, options = {}) => {
7116
+ experimentalStaticLocale ?? options.locale ?? getLocale();
7117
+ return ko_mcp_verify_server_only_import_reexport(inputs);
7118
+ });
7119
+
6568
7120
  //#endregion
6569
7121
  //#region ../i18n/src/paraglide/messages/mcp_verify_tenant_header.js
6570
7122
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
@@ -6652,7 +7204,7 @@ function takeLangFlag(argv) {
6652
7204
 
6653
7205
  //#endregion
6654
7206
  //#region package.json
6655
- var version = "0.3.3";
7207
+ var version = "0.4.0";
6656
7208
 
6657
7209
  //#endregion
6658
7210
  //#region src/client.ts
@@ -6673,11 +7225,11 @@ function clientOf(auth, fetchImpl = fetch, refresh) {
6673
7225
  accessToken: () => auth.token,
6674
7226
  storeId: () => auth.storeId ?? null,
6675
7227
  ...refresh ? { refresh } : {},
6676
- fetch: (input, init) => {
6677
- const headers = new Headers(init?.headers);
7228
+ fetch: (input, init$1) => {
7229
+ const headers = new Headers(init$1?.headers);
6678
7230
  headers.set("user-agent", CLI_USER_AGENT);
6679
7231
  return fetchImpl(input, {
6680
- ...init,
7232
+ ...init$1,
6681
7233
  headers
6682
7234
  });
6683
7235
  }
@@ -7875,6 +8427,15 @@ const RULES = [
7875
8427
  templateFile: "src/lib/ordering.ts",
7876
8428
  severity: "warning"
7877
8429
  }),
8430
+ defineRule({
8431
+ id: "server-only-import",
8432
+ title: mcp_rule_server_only_import_title,
8433
+ why: mcp_rule_server_only_import_why,
8434
+ fix: mcp_rule_server_only_import_fix,
8435
+ example: mcp_rule_server_only_import_example,
8436
+ doc: "/guides/mcp-customize",
8437
+ templateFile: "src/lib/api.server.ts"
8438
+ }),
7878
8439
  defineRule({
7879
8440
  id: "local-import-exists",
7880
8441
  title: mcp_rule_local_import_exists_title,
@@ -7988,6 +8549,7 @@ function verifySources(files, options = {}) {
7988
8549
  const clientQuery = matches(/\buse(Suspense)?Query\(/);
7989
8550
  if (clientQuery && !has("ensureQueryData(") && !has("prefetchQuery(")) inFile("loader-first", clientQuery, mcp_verify_loader_first_query(), /\buse(Suspense)?Query\(/);
7990
8551
  if (has("createStorefrontClient(") && !has("loader")) inProject("loader-first", mcp_verify_loader_first_missing());
8552
+ findings.push(...serverOnlyImports(cleaned));
7991
8553
  if (options.paths) findings.push(...missingLocalImports(cleaned, options.paths));
7992
8554
  return findings;
7993
8555
  }
@@ -8049,6 +8611,113 @@ function missingLocalImports(files, paths) {
8049
8611
  }
8050
8612
  return out;
8051
8613
  }
8614
+ /**
8615
+ * 서버 전용 모듈 — TanStack Start import 보호의 기본 판정과 같다(클라이언트 환경에서 `*.server.*` 파일과
8616
+ * `@tanstack/{react,solid,vue}-start/server`를 막는다). 표시 모듈 `…/server-only`는 막는 대상이 아니다.
8617
+ */
8618
+ const SERVER_ONLY_SPECIFIER = /^(?:@tanstack\/(?:react|solid|vue)-start\/server|\.{1,2}\/(?:[^"'\n]*\/)?[^/"'\n]+\.server(?:\.[cm]?[jt]sx?)?)$/;
8619
+ /** 그 자체가 브라우저로 가지 않아 import 보호가 보지 않는 파일 — `*.server.*`와 테스트 */
8620
+ const SERVER_ONLY_IMPORTER = /\.server\.[cm]?[jt]sx?$|\.(test|spec)\.[cm]?[jt]sx?$/;
8621
+ /** 스스로 서버 전용임을 밝힌 모듈 — 브라우저가 가져가면 import 보호가 그 모듈을 막으므로 여기서는 보지 않는다 */
8622
+ const SERVER_ONLY_MARKER = /\bimport\s*["']@tanstack\/(?:react|solid|vue)-start\/server-only["']/;
8623
+ /** 정적 import 문(여러 줄 포함). 1: `type` 2: 가져오는 이름들 3: 모듈 */
8624
+ const STATIC_IMPORT = /\bimport\s+(type\s+)?([^;"'`]*?)\s*\bfrom\s*["']([^"'\n]+)["']\s*;?/g;
8625
+ /** 부수 효과 import·다시 내보내기 — 서버 전용 모듈이면 그대로 브라우저 번들에 남는다 */
8626
+ const SIDE_EFFECT_OR_REEXPORT = /\bimport\s*["']([^"'\n]+)["']|\bexport\s+(?:type\s+)?(?:\*(?:\s+as\s+[\w$]+)?|\{[^}]*\})\s*from\s*["']([^"'\n]+)["']/g;
8627
+ /**
8628
+ * 서버에서만 도는 자리 — Start 컴파일러가 클라이언트 번들에서 걷어내는 인자다. `createServerFn().handler(…)`,
8629
+ * `createMiddleware().server(…)`·`createIsomorphicFn().server(…)`, `createServerOnlyFn(…)`, 서버 라우트
8630
+ * `createFileRoute(…)({ server: { handlers } })`의 `server` 객체. 일치의 마지막 글자가 여는 괄호다
8631
+ */
8632
+ const SERVER_CONTEXT = /\.(?:handler|server)\s*\(|\bcreateServerOnlyFn\s*\(|\bserver\s*:\s*\{/g;
8633
+ /** import 문이 가져오는 값 이름 — `type` 표시가 붙은 이름은 빼고, `a as b`는 `b`다 */
8634
+ function importedBindings(clause) {
8635
+ const names = [];
8636
+ const named = /\{([^}]*)\}/.exec(clause);
8637
+ for (const part of named?.[1]?.split(",") ?? []) {
8638
+ const item = part.trim();
8639
+ if (!item || /^type\s/.test(item)) continue;
8640
+ const alias = /\bas\s+([\w$]+)$/.exec(item);
8641
+ names.push(alias ? alias[1] : item.split(/\s+/)[0]);
8642
+ }
8643
+ const rest = clause.replace(/\{[^}]*\}/, "");
8644
+ const namespace = /\*\s*as\s+([\w$]+)/.exec(rest);
8645
+ if (namespace) names.push(namespace[1]);
8646
+ const defaultName = /^\s*([\w$]+)\s*(?:,|$)/.exec(rest.replace(/\*\s*as\s+[\w$]+/, ""));
8647
+ if (defaultName) names.push(defaultName[1]);
8648
+ return names.filter((name) => /^[A-Za-z_$][\w$]*$/.test(name));
8649
+ }
8650
+ /** `open` 위치의 여는 괄호와 짝이 맞는 닫는 괄호 다음 위치. 문자열·템플릿 리터럴 안의 괄호는 세지 않는다 */
8651
+ function closingBracket(source, open) {
8652
+ let depth = 0;
8653
+ let quote = null;
8654
+ for (let i = open; i < source.length; i++) {
8655
+ const ch = source[i];
8656
+ if (quote) {
8657
+ if (ch === "\\") i++;
8658
+ else if (ch === quote) quote = null;
8659
+ continue;
8660
+ }
8661
+ if (ch === "\"" || ch === "'" || ch === "`") quote = ch;
8662
+ else if (ch === "(" || ch === "{" || ch === "[") depth++;
8663
+ else if ((ch === ")" || ch === "}" || ch === "]") && --depth === 0) return i + 1;
8664
+ }
8665
+ return source.length;
8666
+ }
8667
+ /** 줄 수를 지키며 구간을 공백으로 지운다 — 남은 코드의 위치가 원본 줄 번호와 맞아야 한다 */
8668
+ const blank = (text) => text.replace(/[^\n]/g, " ");
8669
+ /**
8670
+ * 브라우저로 갈 수 있는 모듈이 서버 전용 모듈을 쓰는가(이슈 #88). TanStack Start는 서버 함수 handler처럼 서버에서만
8671
+ * 도는 인자를 클라이언트 번들에서 걷어내고, 그 뒤에도 `*.server.*` import가 남으면 import 보호로 빌드를 실패시킨다.
8672
+ * 그래서 import 자체가 아니라 **서버 자리 밖에서 가져온 이름을 쓰는 것**을 위반으로 본다. 템플릿은 라우트·`lib`에서
8673
+ * `apiFor`를 import하고 `createServerFn().handler(…)`·서버 라우트 `server` 안에서만 부른다. 서버 자리 밖의 일반 함수(예: 서버 API를 부르는
8674
+ * `export function wishedOf()`)는 브라우저 번들에 남으므로 위반이다 — 그 함수를 `*.server.ts`로 옮기거나 서버 함수로 감싼다.
8675
+ * 정적 검사라 서버 자리에서만 부르는 지역 함수까지 따라가지는 않는다(보수적으로 위반으로 본다).
8676
+ */
8677
+ function serverOnlyImports(files) {
8678
+ const out = [];
8679
+ for (const file of files) {
8680
+ if (!CLIENT_FILE.test(file.path) || SERVER_ONLY_IMPORTER.test(file.path)) continue;
8681
+ if (SERVER_ONLY_MARKER.test(file.content)) continue;
8682
+ const lineAt = (index) => file.content.slice(0, index).split("\n").length;
8683
+ for (const match of file.content.matchAll(SIDE_EFFECT_OR_REEXPORT)) {
8684
+ const specifier = match[1] ?? match[2];
8685
+ if (!SERVER_ONLY_SPECIFIER.test(specifier)) continue;
8686
+ out.push({
8687
+ ruleId: "server-only-import",
8688
+ file: file.path,
8689
+ line: lineAt(match.index),
8690
+ detail: mcp_verify_server_only_import_reexport({ specifier })
8691
+ });
8692
+ }
8693
+ const bindings = /* @__PURE__ */ new Map();
8694
+ let body = file.content.replace(STATIC_IMPORT, (statement, typeOnly, clause, specifier) => {
8695
+ if (!typeOnly && SERVER_ONLY_SPECIFIER.test(specifier)) for (const name of importedBindings(clause)) bindings.set(name, specifier);
8696
+ return blank(statement);
8697
+ });
8698
+ if (bindings.size === 0) continue;
8699
+ for (const context of [...body.matchAll(SERVER_CONTEXT)]) {
8700
+ const open = context.index + context[0].length - 1;
8701
+ if (body[open] !== "(" && body[open] !== "{") continue;
8702
+ const end = closingBracket(body, open);
8703
+ body = body.slice(0, open) + blank(body.slice(open, end)) + body.slice(end);
8704
+ }
8705
+ for (const [name, specifier] of bindings) {
8706
+ const use = (/* @__PURE__ */ new RegExp(`(?<![\\w$.])${name.replace(/\$/g, "\\$")}(?![\\w$])`)).exec(body);
8707
+ if (!use) continue;
8708
+ out.push({
8709
+ ruleId: "server-only-import",
8710
+ file: file.path,
8711
+ line: lineAt(use.index),
8712
+ detail: mcp_verify_server_only_import({
8713
+ name,
8714
+ specifier
8715
+ })
8716
+ });
8717
+ }
8718
+ }
8719
+ return out;
8720
+ }
8052
8721
 
8053
8722
  //#endregion
8054
8723
  //#region ../mcp/src/verify-report.ts
@@ -8601,6 +9270,55 @@ function lockImporterSpecifiers(lock) {
8601
9270
  return out;
8602
9271
  }
8603
9272
 
9273
+ //#endregion
9274
+ //#region src/local-build.ts
9275
+ /** 실패할 때 보여 주는 빌드 출력 줄 수 */
9276
+ const OUTPUT_TAIL_LINES = 40;
9277
+ /** 모아 두는 출력 상한(문자) — 긴 빌드 로그를 다 쥐고 있지 않는다 */
9278
+ const OUTPUT_KEEP_CHARS = 64 * 1024;
9279
+ /** 빌드를 돌릴 수 있는 폴더인가 — `build` 스크립트와 설치된 의존성(`node_modules`)이 있어야 한다 */
9280
+ function planLocalBuild(dir) {
9281
+ let scripts;
9282
+ try {
9283
+ scripts = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).scripts;
9284
+ } catch {
9285
+ return { kind: "no-script" };
9286
+ }
9287
+ const build = scripts && typeof scripts === "object" ? scripts.build : void 0;
9288
+ if (typeof build !== "string" || !build.trim()) return { kind: "no-script" };
9289
+ if (!existsSync(join(dir, "node_modules"))) return { kind: "no-deps" };
9290
+ return { kind: "run" };
9291
+ }
9292
+ /** `npm run build`를 돌린다. 출력은 모아 두고(실패할 때 꼬리를 보여 준다) 화면에 흘리지 않는다 */
9293
+ function runNpmBuild(dir, env) {
9294
+ return new Promise((resolve$1, reject) => {
9295
+ let output = "";
9296
+ const keep = (chunk) => {
9297
+ output = (output + chunk.toString("utf8")).slice(-OUTPUT_KEEP_CHARS);
9298
+ };
9299
+ const child = spawn("npm", ["run", "build"], {
9300
+ cwd: dir,
9301
+ env,
9302
+ stdio: [
9303
+ "ignore",
9304
+ "pipe",
9305
+ "pipe"
9306
+ ],
9307
+ shell: process.platform === "win32"
9308
+ });
9309
+ child.stdout?.on("data", keep);
9310
+ child.stderr?.on("data", keep);
9311
+ child.once("error", reject);
9312
+ child.once("close", (code) => resolve$1({
9313
+ code,
9314
+ output: tailOf(output)
9315
+ }));
9316
+ });
9317
+ }
9318
+ function tailOf(output, lines = OUTPUT_TAIL_LINES) {
9319
+ return output.trimEnd().split(/\r?\n/).slice(-lines).join("\n");
9320
+ }
9321
+
8604
9322
  //#endregion
8605
9323
  //#region src/commands.ts
8606
9324
  /** 종료 코드 — 0 성공 · 1 실패 · 3 발행이 승인 대기(에이전트 토큰) */
@@ -8657,13 +9375,20 @@ async function pull(flags, io) {
8657
9375
  storeId: source.storeId,
8658
9376
  project: flags.project
8659
9377
  }));
9378
+ return receiveSource(client, auth, source, flags, io, authDir);
9379
+ }
9380
+ /**
9381
+ * 받은 작업 트리를 폴더에 쓰고 받은 폴더 파일(`.sayren/state.json`·`.env`·`.mcp.json`·`.gitignore`)을 맞춘다. `pull`과 `init`(#101)이 같이 쓴다.
9382
+ * 대상은 `--dir` → 받은 폴더 안이면 그 폴더 → 상점 코드다. 비어 있지 않은 폴더는 `--force`일 때 같은 사이트를 받은 폴더만 다시 받는다.
9383
+ */
9384
+ async function receiveSource(client, auth, source, flags, io, migratedDir) {
8660
9385
  const store = {
8661
9386
  storeId: source.storeId,
8662
9387
  storeCode: source.storeCode
8663
9388
  };
8664
9389
  const defaultDir = store.storeCode || source.siteId;
8665
9390
  const target = !flags.dir && isPulledDir(io.cwd) ? io.cwd : resolve(io.cwd, flags.dir ?? defaultDir);
8666
- if (target !== authDir) migrateLegacy(target, io.out);
9391
+ if (target !== migratedDir) migrateLegacy(target, io.out);
8667
9392
  const existing = existsSync(target) ? readdirSync(target) : [];
8668
9393
  if (existing.length > 0) {
8669
9394
  if (!flags.force) throw new CliError(cli_pull_dir_not_empty({ dir: relativeOf(io, target) }));
@@ -8785,6 +9510,80 @@ async function pull(flags, io) {
8785
9510
  io.out(cli_pull_next_steps({ dir: pulledDir }));
8786
9511
  return 0;
8787
9512
  }
9513
+ /**
9514
+ * `sayren init [폴더]`(#101) — 템플릿을 골라 호스팅 사이트를 만들고(`POST /v1/storefront/site`) `pull`과 같은 폴더를 만든다.
9515
+ * 사이트가 이미 있으면 만들지 않고 `pull`을 안내한다(상점당 열린 사이트는 하나다). 만들기는 상점 한도(30일 3회)를 쓰므로 확인을 묻는다.
9516
+ * 토큰은 `storefront:rw`가 있어야 한다(로그인 토큰 `CLI_LOGIN_SCOPE`에 있다). 에이전트 토큰이어도 만들기는 승인 대상이 아니다
9517
+ * (쓰기 정책 표에 없다 — 오픈 준비 중으로 시작해 구매자 화면을 바꾸지 않는다).
9518
+ */
9519
+ async function init(flags, io) {
9520
+ const target = flags.dir ? resolve(io.cwd, flags.dir) : null;
9521
+ if (target && existsSync(target) && readdirSync(target).length > 0) throw new CliError(cli_init_dir_not_empty({ dir: relativeOf(io, target) }));
9522
+ if (!target && isPulledDir(io.cwd)) throw new CliError(cli_init_inside_pulled());
9523
+ const authDir = target && existsSync(target) ? target : io.cwd;
9524
+ const { auth, client } = await apiFor(flags, authDir, null, io);
9525
+ const storeId = auth.storeId ?? "<storeId>";
9526
+ const hosting = await client.storefront.get();
9527
+ if (hosting.site) {
9528
+ io.out(cli_init_site_url({ url: hosting.site.url }));
9529
+ throw new CliError(cli_init_site_exists({ storeId }));
9530
+ }
9531
+ if (!hosting.enabled) throw new CliError(cli_init_hosting_disabled());
9532
+ if (!hosting.editingEnabled) throw new CliError(cli_pull_editing_disabled());
9533
+ const template = await pickTemplate(await client.storefront.listTemplates(), flags.template, io);
9534
+ io.out(cli_init_summary({
9535
+ name: template.name,
9536
+ templateId: template.templateId,
9537
+ version: template.version,
9538
+ storeId
9539
+ }));
9540
+ if (!flags.yes) {
9541
+ if (!io.confirm) throw new CliError(cli_init_yes_required());
9542
+ if (!await io.confirm(cli_init_confirm())) {
9543
+ io.out(cli_cancelled());
9544
+ return 1;
9545
+ }
9546
+ }
9547
+ let site;
9548
+ try {
9549
+ site = await client.storefront.create({ templateId: template.templateId });
9550
+ } catch (error) {
9551
+ const code = codeOf(error);
9552
+ if (code === "STOREFRONT_SITE_EXISTS") throw new CliError(cli_init_site_exists({ storeId }));
9553
+ if (code === "STOREFRONT_SLUG_TAKEN") throw new CliError(cli_init_slug_taken());
9554
+ throw error;
9555
+ }
9556
+ io.out(cli_init_created({ url: site.url }));
9557
+ try {
9558
+ return await receiveSource(client, auth, await client.storefront.getSource(), { dir: flags.dir }, io, authDir);
9559
+ } catch (error) {
9560
+ io.err(cli_init_pull_failed({ storeId }));
9561
+ throw error;
9562
+ }
9563
+ }
9564
+ /** `--template`이 있으면 그 템플릿, 없으면 대화형 선택. 비대화형이면 `--template`이 있어야 한다 */
9565
+ async function pickTemplate(templates, wanted, io) {
9566
+ if (templates.length === 0) throw new CliError(cli_init_no_templates());
9567
+ const ids = templates.map((t) => t.templateId).join(", ");
9568
+ if (wanted) {
9569
+ const found = templates.find((t) => t.templateId === wanted);
9570
+ if (!found) throw new CliError(cli_init_template_unknown({
9571
+ templateId: wanted,
9572
+ ids
9573
+ }));
9574
+ return found;
9575
+ }
9576
+ const labels = templates.map((t) => cli_init_template_option({
9577
+ templateId: t.templateId,
9578
+ name: t.name,
9579
+ version: t.version,
9580
+ description: t.description
9581
+ }));
9582
+ const picked = io.select ? await io.select(cli_init_template_prompt(), labels) : null;
9583
+ const index = picked ? labels.indexOf(picked) : -1;
9584
+ if (index < 0) throw new CliError(cli_init_template_required({ list: labels.map((label) => ` ${label}`).join("\n") }));
9585
+ return templates[index];
9586
+ }
8788
9587
  async function verify(flags, io) {
8789
9588
  const dir = resolve(io.cwd, flags.dir ?? ".");
8790
9589
  const tree = collectTree(dir);
@@ -8841,6 +9640,7 @@ async function deploy(flags, io) {
8841
9640
  const checks = runLocalChecks(tree);
8842
9641
  printChecks(tree, checks, io);
8843
9642
  if (checks.blockingRules.length > 0 || checks.problems.length > 0) throw new CliError(cli_deploy_checks_failed());
9643
+ await localBuild(dir, flags.skipBuild === true, io);
8844
9644
  const site = (await client.storefront.get()).site;
8845
9645
  if (!site || site.siteId !== rc.siteId) throw new CliError(cli_deploy_site_mismatch({ siteId: rc.siteId }));
8846
9646
  if (flags.force && !flags.yes) {
@@ -8912,6 +9712,42 @@ async function deploy(flags, io) {
8912
9712
  throw error;
8913
9713
  }
8914
9714
  }
9715
+ /**
9716
+ * 배포 전 로컬 빌드(#103). 의존성이 설치돼 있고 `build` 스크립트가 있으면 돌리고, 실패하면(시작하지 못한 것 포함) 올리지 않는다.
9717
+ * 의존성이 없으면 알리고 건너뛴다 — 셀러가 install하지 않은 폴더에서도 배포는 된다(서버가 다시 빌드한다).
9718
+ */
9719
+ async function localBuild(dir, skip, io) {
9720
+ if (skip) {
9721
+ io.out(cli_deploy_build_local_skipped_flag());
9722
+ return;
9723
+ }
9724
+ const plan = planLocalBuild(dir);
9725
+ if (plan.kind === "no-script") {
9726
+ io.out(cli_deploy_build_local_no_script());
9727
+ return;
9728
+ }
9729
+ if (plan.kind === "no-deps") {
9730
+ io.out(cli_deploy_build_local_no_deps());
9731
+ return;
9732
+ }
9733
+ io.out(cli_deploy_build_local_running());
9734
+ const started = io.now?.() ?? Date.now();
9735
+ let result;
9736
+ try {
9737
+ result = await (io.runBuild ?? ((at) => runNpmBuild(at, io.env)))(dir);
9738
+ } catch (error) {
9739
+ throw new CliError(cli_deploy_build_local_spawn_failed({ message: error instanceof Error ? error.message : String(error) }));
9740
+ }
9741
+ if (result.code !== 0) {
9742
+ if (result.output) {
9743
+ io.err(cli_deploy_build_local_output({ count: OUTPUT_TAIL_LINES }));
9744
+ for (const line of result.output.split("\n")) io.err(` ${line}`);
9745
+ }
9746
+ throw new CliError(cli_deploy_build_local_failed({ code: String(result.code ?? "-") }));
9747
+ }
9748
+ const seconds = Math.round(((io.now?.() ?? Date.now()) - started) / 1e3);
9749
+ io.out(cli_deploy_build_local_passed({ seconds }));
9750
+ }
8915
9751
  async function replaceSource(client, tree, rc, force, io) {
8916
9752
  const files = tree.files.map((file) => ({
8917
9753
  path: file.path,
@@ -9238,12 +10074,14 @@ const VALUE_FLAGS = new Set([
9238
10074
  "message",
9239
10075
  "api-origin",
9240
10076
  "store",
9241
- "accounts-origin"
10077
+ "accounts-origin",
10078
+ "template"
9242
10079
  ]);
9243
10080
  const BOOL_FLAGS = new Set([
9244
10081
  "force",
9245
10082
  "yes",
9246
10083
  "no-publish",
10084
+ "skip-build",
9247
10085
  "help",
9248
10086
  "version",
9249
10087
  "device",
@@ -9304,6 +10142,11 @@ async function main(argv, io) {
9304
10142
  accountsOrigin: str(parsed.flags["accounts-origin"])
9305
10143
  };
9306
10144
  switch (parsed.command) {
10145
+ case "init": return init({
10146
+ ...common,
10147
+ template: str(parsed.flags.template),
10148
+ yes: parsed.flags.yes === true
10149
+ }, io);
9307
10150
  case "pull": return pull({
9308
10151
  ...common,
9309
10152
  project: str(parsed.flags.project),
@@ -9315,6 +10158,7 @@ async function main(argv, io) {
9315
10158
  force: parsed.flags.force === true,
9316
10159
  yes: parsed.flags.yes === true,
9317
10160
  publish: parsed.flags["no-publish"] !== true,
10161
+ skipBuild: parsed.flags["skip-build"] === true,
9318
10162
  message: str(parsed.flags.message)
9319
10163
  }, io);
9320
10164
  case "status": return status(common, io);
@@ -9344,7 +10188,7 @@ function defaultIo() {
9344
10188
  return {
9345
10189
  cwd: process.cwd(),
9346
10190
  env: process.env,
9347
- fetch: (input, init) => fetch(input, init),
10191
+ fetch: (input, init$1) => fetch(input, init$1),
9348
10192
  out: (line) => console.log(line),
9349
10193
  err: (line) => console.error(line),
9350
10194
  confirm: stdin.isTTY ? async (question) => /^(y|yes|예|네)$/i.test((await ask(`${question} (y/N) `) ?? "").trim()) : null,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "sayren",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
- "description": "sayren 스토어프론트 CLI — 호스팅 중인 사이트의 소스를 내려받고(pull) 로컬에서 고친 뒤 배포(deploy)합니다",
5
+ "description": "sayren 스토어프론트 CLI — 호스팅 사이트를 만들고(init) 소스를 내려받아(pull) 로컬에서 고친 뒤 배포(deploy)합니다",
6
6
  "bin": {
7
7
  "sayren": "./dist/index.mjs"
8
8
  },