sayren 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +184 -9
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -5294,6 +5294,79 @@ const mcp_rule_loader_first_why = ((inputs = {}, options = {}) => {
|
|
|
5294
5294
|
return ko_mcp_rule_loader_first_why(inputs);
|
|
5295
5295
|
});
|
|
5296
5296
|
|
|
5297
|
+
//#endregion
|
|
5298
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_local_import_exists_example.js
|
|
5299
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5300
|
+
/** @typedef {{}} Mcp_Rule_Local_Import_Exists_ExampleInputs */
|
|
5301
|
+
const ko_mcp_rule_local_import_exists_example = () => {
|
|
5302
|
+
return `// src/i18n/index.ts — 작업 트리에 messages/ko.json이 있어야 한다
|
|
5303
|
+
import ko from "../../messages/ko.json";`;
|
|
5304
|
+
};
|
|
5305
|
+
/**
|
|
5306
|
+
|
|
5307
|
+
* @param {Mcp_Rule_Local_Import_Exists_ExampleInputs} inputs
|
|
5308
|
+
* @param {{ locale?: "ko" }} options
|
|
5309
|
+
* @returns {LocalizedString}
|
|
5310
|
+
*/
|
|
5311
|
+
const mcp_rule_local_import_exists_example = ((inputs = {}, options = {}) => {
|
|
5312
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5313
|
+
return ko_mcp_rule_local_import_exists_example(inputs);
|
|
5314
|
+
});
|
|
5315
|
+
|
|
5316
|
+
//#endregion
|
|
5317
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_local_import_exists_fix.js
|
|
5318
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5319
|
+
/** @typedef {{}} Mcp_Rule_Local_Import_Exists_FixInputs */
|
|
5320
|
+
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\`이다`;
|
|
5322
|
+
};
|
|
5323
|
+
/**
|
|
5324
|
+
|
|
5325
|
+
* @param {Mcp_Rule_Local_Import_Exists_FixInputs} inputs
|
|
5326
|
+
* @param {{ locale?: "ko" }} options
|
|
5327
|
+
* @returns {LocalizedString}
|
|
5328
|
+
*/
|
|
5329
|
+
const mcp_rule_local_import_exists_fix = ((inputs = {}, options = {}) => {
|
|
5330
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5331
|
+
return ko_mcp_rule_local_import_exists_fix(inputs);
|
|
5332
|
+
});
|
|
5333
|
+
|
|
5334
|
+
//#endregion
|
|
5335
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_local_import_exists_title.js
|
|
5336
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5337
|
+
/** @typedef {{}} Mcp_Rule_Local_Import_Exists_TitleInputs */
|
|
5338
|
+
const ko_mcp_rule_local_import_exists_title = () => {
|
|
5339
|
+
return `상대 경로로 import한 파일은 작업 트리에 있어야 한다`;
|
|
5340
|
+
};
|
|
5341
|
+
/**
|
|
5342
|
+
|
|
5343
|
+
* @param {Mcp_Rule_Local_Import_Exists_TitleInputs} inputs
|
|
5344
|
+
* @param {{ locale?: "ko" }} options
|
|
5345
|
+
* @returns {LocalizedString}
|
|
5346
|
+
*/
|
|
5347
|
+
const mcp_rule_local_import_exists_title = ((inputs = {}, options = {}) => {
|
|
5348
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5349
|
+
return ko_mcp_rule_local_import_exists_title(inputs);
|
|
5350
|
+
});
|
|
5351
|
+
|
|
5352
|
+
//#endregion
|
|
5353
|
+
//#region ../i18n/src/paraglide/messages/mcp_rule_local_import_exists_why.js
|
|
5354
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5355
|
+
/** @typedef {{}} Mcp_Rule_Local_Import_Exists_WhyInputs */
|
|
5356
|
+
const ko_mcp_rule_local_import_exists_why = () => {
|
|
5357
|
+
return `규칙 검사는 코드 문자열만 보지만 타입 검사와 빌드는 import한 파일을 실제로 읽는다. 대상 파일이 없으면 \`Cannot find module\`(TS2307)로 검사와 버전 빌드가 실패한다`;
|
|
5358
|
+
};
|
|
5359
|
+
/**
|
|
5360
|
+
|
|
5361
|
+
* @param {Mcp_Rule_Local_Import_Exists_WhyInputs} inputs
|
|
5362
|
+
* @param {{ locale?: "ko" }} options
|
|
5363
|
+
* @returns {LocalizedString}
|
|
5364
|
+
*/
|
|
5365
|
+
const mcp_rule_local_import_exists_why = ((inputs = {}, options = {}) => {
|
|
5366
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
5367
|
+
return ko_mcp_rule_local_import_exists_why(inputs);
|
|
5368
|
+
});
|
|
5369
|
+
|
|
5297
5370
|
//#endregion
|
|
5298
5371
|
//#region ../i18n/src/paraglide/messages/mcp_rule_no_module_token_example.js
|
|
5299
5372
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -6312,6 +6385,24 @@ const mcp_verify_loader_first_query = ((inputs = {}, options = {}) => {
|
|
|
6312
6385
|
return ko_mcp_verify_loader_first_query(inputs);
|
|
6313
6386
|
});
|
|
6314
6387
|
|
|
6388
|
+
//#endregion
|
|
6389
|
+
//#region ../i18n/src/paraglide/messages/mcp_verify_local_import_missing.js
|
|
6390
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6391
|
+
/** @typedef {{ specifier: NonNullable<unknown>, path: NonNullable<unknown> }} Mcp_Verify_Local_Import_MissingInputs */
|
|
6392
|
+
const ko_mcp_verify_local_import_missing = (i) => {
|
|
6393
|
+
return `\`${i?.specifier}\`를 import하지만 작업 트리에 \`${i?.path}\`가 없다`;
|
|
6394
|
+
};
|
|
6395
|
+
/**
|
|
6396
|
+
|
|
6397
|
+
* @param {Mcp_Verify_Local_Import_MissingInputs} inputs
|
|
6398
|
+
* @param {{ locale?: "ko" }} options
|
|
6399
|
+
* @returns {LocalizedString}
|
|
6400
|
+
*/
|
|
6401
|
+
const mcp_verify_local_import_missing = ((inputs, options = {}) => {
|
|
6402
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6403
|
+
return ko_mcp_verify_local_import_missing(inputs);
|
|
6404
|
+
});
|
|
6405
|
+
|
|
6315
6406
|
//#endregion
|
|
6316
6407
|
//#region ../i18n/src/paraglide/messages/mcp_verify_no_module_token.js
|
|
6317
6408
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -6561,7 +6652,7 @@ function takeLangFlag(argv) {
|
|
|
6561
6652
|
|
|
6562
6653
|
//#endregion
|
|
6563
6654
|
//#region package.json
|
|
6564
|
-
var version = "0.3.
|
|
6655
|
+
var version = "0.3.3";
|
|
6565
6656
|
|
|
6566
6657
|
//#endregion
|
|
6567
6658
|
//#region src/client.ts
|
|
@@ -7783,6 +7874,14 @@ const RULES = [
|
|
|
7783
7874
|
doc: "/guides/storefront-sdk",
|
|
7784
7875
|
templateFile: "src/lib/ordering.ts",
|
|
7785
7876
|
severity: "warning"
|
|
7877
|
+
}),
|
|
7878
|
+
defineRule({
|
|
7879
|
+
id: "local-import-exists",
|
|
7880
|
+
title: mcp_rule_local_import_exists_title,
|
|
7881
|
+
why: mcp_rule_local_import_exists_why,
|
|
7882
|
+
fix: mcp_rule_local_import_exists_fix,
|
|
7883
|
+
example: mcp_rule_local_import_exists_example,
|
|
7884
|
+
doc: "/guides/mcp-customize"
|
|
7786
7885
|
})
|
|
7787
7886
|
];
|
|
7788
7887
|
/**
|
|
@@ -7816,7 +7915,7 @@ const CUSTOM_INPUT_LEAKS = [
|
|
|
7816
7915
|
* 만들어진 프로젝트의 소스를 규칙과 대조한다. 정적 검사라 모든 문제를 잡지는 못하지만,
|
|
7817
7916
|
* 여기 있는 것들은 실제로 반복해서 깨지는 것들이다.
|
|
7818
7917
|
*/
|
|
7819
|
-
function verifySources(files) {
|
|
7918
|
+
function verifySources(files, options = {}) {
|
|
7820
7919
|
const findings = [];
|
|
7821
7920
|
const cleaned = files.map((file) => ({
|
|
7822
7921
|
path: file.path,
|
|
@@ -7889,8 +7988,67 @@ function verifySources(files) {
|
|
|
7889
7988
|
const clientQuery = matches(/\buse(Suspense)?Query\(/);
|
|
7890
7989
|
if (clientQuery && !has("ensureQueryData(") && !has("prefetchQuery(")) inFile("loader-first", clientQuery, mcp_verify_loader_first_query(), /\buse(Suspense)?Query\(/);
|
|
7891
7990
|
if (has("createStorefrontClient(") && !has("loader")) inProject("loader-first", mcp_verify_loader_first_missing());
|
|
7991
|
+
if (options.paths) findings.push(...missingLocalImports(cleaned, options.paths));
|
|
7892
7992
|
return findings;
|
|
7893
7993
|
}
|
|
7994
|
+
/** 상대 경로 import·export·동적 import의 대상 — `from "./x"`, `import "./x"`, `import("./x")` */
|
|
7995
|
+
const RELATIVE_IMPORT = /(?:\bfrom\s*|\bimport\s*\(?\s*)(["'])(\.{1,2}\/[^"'\n]+)\1/g;
|
|
7996
|
+
/** 확장자 없이 쓴 import가 가리킬 수 있는 파일 */
|
|
7997
|
+
const RESOLVE_SUFFIXES = [
|
|
7998
|
+
".ts",
|
|
7999
|
+
".tsx",
|
|
8000
|
+
".js",
|
|
8001
|
+
".jsx",
|
|
8002
|
+
".mjs",
|
|
8003
|
+
".json",
|
|
8004
|
+
"/index.ts",
|
|
8005
|
+
"/index.tsx",
|
|
8006
|
+
"/index.js",
|
|
8007
|
+
"/index.jsx"
|
|
8008
|
+
];
|
|
8009
|
+
/** `from` 파일 기준으로 상대 경로를 푼다. 작업 트리 루트 밖으로 나가면 null(판정하지 않는다) */
|
|
8010
|
+
function resolveRelative(from, specifier) {
|
|
8011
|
+
const segments = from.split("/").slice(0, -1);
|
|
8012
|
+
for (const segment of specifier.split("/")) {
|
|
8013
|
+
if (segment === "" || segment === ".") continue;
|
|
8014
|
+
if (segment === "..") {
|
|
8015
|
+
if (!segments.length) return null;
|
|
8016
|
+
segments.pop();
|
|
8017
|
+
} else segments.push(segment);
|
|
8018
|
+
}
|
|
8019
|
+
return segments.join("/");
|
|
8020
|
+
}
|
|
8021
|
+
/**
|
|
8022
|
+
* 상대 경로로 import한 파일이 작업 트리에 있는가(이슈 #78) — 규칙 검사는 소스 문자열만 보므로 빠진 파일(예: 템플릿의
|
|
8023
|
+
* `messages/ko.json`)을 타입 검사·빌드 전에는 알 수 없었다. `paths`는 검사 대상 트리의 모든 파일 경로다(소스 아닌 파일 포함).
|
|
8024
|
+
* vite 쿼리(`?url`·`?raw`)는 떼고, `.js`로 쓴 TypeScript import(`./a.js` → `a.ts`)도 받는다.
|
|
8025
|
+
*/
|
|
8026
|
+
function missingLocalImports(files, paths) {
|
|
8027
|
+
const tree = new Set(paths);
|
|
8028
|
+
const out = [];
|
|
8029
|
+
for (const file of files) for (const match of file.content.matchAll(RELATIVE_IMPORT)) {
|
|
8030
|
+
const specifier = match[2];
|
|
8031
|
+
const target = resolveRelative(file.path, specifier.replace(/[?#].*$/, ""));
|
|
8032
|
+
if (target === null || target === "") continue;
|
|
8033
|
+
const candidates = [target, ...RESOLVE_SUFFIXES.map((suffix) => `${target}${suffix}`)];
|
|
8034
|
+
const js = /\.(m?js|jsx)$/.exec(target);
|
|
8035
|
+
if (js) {
|
|
8036
|
+
const stem = target.slice(0, -js[0].length);
|
|
8037
|
+
candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.mts`);
|
|
8038
|
+
}
|
|
8039
|
+
if (candidates.some((candidate) => tree.has(candidate))) continue;
|
|
8040
|
+
out.push({
|
|
8041
|
+
ruleId: "local-import-exists",
|
|
8042
|
+
file: file.path,
|
|
8043
|
+
line: file.content.slice(0, match.index).split("\n").length,
|
|
8044
|
+
detail: mcp_verify_local_import_missing({
|
|
8045
|
+
specifier,
|
|
8046
|
+
path: target
|
|
8047
|
+
})
|
|
8048
|
+
});
|
|
8049
|
+
}
|
|
8050
|
+
return out;
|
|
8051
|
+
}
|
|
7894
8052
|
|
|
7895
8053
|
//#endregion
|
|
7896
8054
|
//#region ../mcp/src/verify-report.ts
|
|
@@ -7932,9 +8090,12 @@ function nextSteps(findings, target) {
|
|
|
7932
8090
|
mcp_report_next_doc()
|
|
7933
8091
|
];
|
|
7934
8092
|
}
|
|
7935
|
-
/**
|
|
7936
|
-
|
|
7937
|
-
|
|
8093
|
+
/**
|
|
8094
|
+
* 소스를 규칙과 대조해 고칠 수 있는 보고서로 만든다. `paths`(트리의 모든 파일 경로)를 주면 상대 경로 import의 대상이
|
|
8095
|
+
* 트리에 있는지도 본다(`local-import-exists`, #78)
|
|
8096
|
+
*/
|
|
8097
|
+
function buildReport(files, target = "local", paths) {
|
|
8098
|
+
return reportFromFindings(verifySources(files, { paths }), files.length, target);
|
|
7938
8099
|
}
|
|
7939
8100
|
/**
|
|
7940
8101
|
* 이미 찾은 위반으로 보고서를 만든다. 원격은 api가 작업 트리에서 `verifySources`를 돌려 위반만 돌려주고, 원인·수정
|
|
@@ -8015,7 +8176,7 @@ function verifyTree(files) {
|
|
|
8015
8176
|
path,
|
|
8016
8177
|
content
|
|
8017
8178
|
}];
|
|
8018
|
-
}), "remote");
|
|
8179
|
+
}), "remote", files.keys());
|
|
8019
8180
|
}
|
|
8020
8181
|
|
|
8021
8182
|
//#endregion
|
|
@@ -8048,7 +8209,21 @@ const WRITABLE_ROOT_FILES = new Set([
|
|
|
8048
8209
|
]);
|
|
8049
8210
|
/** 읽기만 되는 루트 파일 — 의존성은 전용 경로로만 바꾼다(레지스트리 프록시와 잠금 파일을 함께 맞춘다) */
|
|
8050
8211
|
const READONLY_ROOT_FILES = new Set(["package.json", "pnpm-lock.yaml"]);
|
|
8051
|
-
|
|
8212
|
+
/**
|
|
8213
|
+
* 쓸 수 있는 폴더 — 코드(`src`), 정적 파일(`public`), 화면 문구 카탈로그(`messages`, 템플릿 `src/i18n`이 `messages/ko.json`을 import한다, #73).
|
|
8214
|
+
* 템플릿이 import하는 파일이 이 밖에 있으면 첫 작업 트리(source.tgz)를 풀 때 빠져 빌드·검사가 깨진다(#78)
|
|
8215
|
+
*/
|
|
8216
|
+
const WRITABLE_DIRS = new Set([
|
|
8217
|
+
"src",
|
|
8218
|
+
"public",
|
|
8219
|
+
"messages"
|
|
8220
|
+
]);
|
|
8221
|
+
/** 작업 트리 루트에 둘 수 있는 이름(폴더·파일) — 컨테이너 스냅샷이 이 이름만 묶는다(`container-tree.ts`) */
|
|
8222
|
+
const TRACKED_ROOT_NAMES = [
|
|
8223
|
+
...WRITABLE_DIRS,
|
|
8224
|
+
...WRITABLE_ROOT_FILES,
|
|
8225
|
+
...READONLY_ROOT_FILES
|
|
8226
|
+
];
|
|
8052
8227
|
/** 형식·금지 세그먼트만 본다(위치 규칙은 보지 않는다). 통과하면 null, 아니면 이유 */
|
|
8053
8228
|
function formatProblem(path) {
|
|
8054
8229
|
if (typeof path !== "string" || path.length === 0) return "empty";
|
|
@@ -8184,7 +8359,7 @@ async function gunzip(bytes, maxBytes) {
|
|
|
8184
8359
|
//#endregion
|
|
8185
8360
|
//#region src/tree.ts
|
|
8186
8361
|
/**
|
|
8187
|
-
* 로컬 작업 트리 — 서버 작업 트리와 **같은 경로 규칙**(편집 Sandbox `paths.ts`의 `accessOf`)으로 고른다. `src/**`·`public/**`·루트 설정
|
|
8362
|
+
* 로컬 작업 트리 — 서버 작업 트리와 **같은 경로 규칙**(편집 Sandbox `paths.ts`의 `accessOf`)으로 고른다. `src/**`·`public/**`·`messages/**`·루트 설정
|
|
8188
8363
|
* 파일·`package.json`·`pnpm-lock.yaml`만 올리고, `.env*`·`.mcp.json`·`.sayren/`·`node_modules`·`dist`·`package-lock.json` 등은 올리지 않는다.
|
|
8189
8364
|
* 비밀·로컬 상태 파일(`isNeverUpload`)은 서버 경로 규칙과 별개로 CLI가 먼저 막는다 — 서버 규칙이 넓어져도 토큰을 올리지 않게.
|
|
8190
8365
|
*/
|
|
@@ -8403,7 +8578,7 @@ function lockDrift(packageJson, lock) {
|
|
|
8403
8578
|
/** 잠금 파일 `importers: .:`의 이름 → specifier. 읽지 못하면 null */
|
|
8404
8579
|
function lockImporterSpecifiers(lock) {
|
|
8405
8580
|
const lines = lock.split(/\r?\n/);
|
|
8406
|
-
const start = lines.
|
|
8581
|
+
const start = lines.indexOf("importers:");
|
|
8407
8582
|
if (start < 0) return null;
|
|
8408
8583
|
const out = /* @__PURE__ */ new Map();
|
|
8409
8584
|
let inRoot = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sayren",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "sayren 스토어프론트 CLI — 호스팅 중인 사이트의 소스를 내려받고(pull) 로컬에서 고친 뒤 배포(deploy)합니다",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"node": ">=20.10"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@sayren/store-sdk": "^0.28.
|
|
16
|
+
"@sayren/store-sdk": "^0.28.2"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@biomejs/biome": "^2.5.14",
|