@zalkera/client 0.24.1 → 0.24.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.
@@ -175,18 +175,25 @@ function isInternalHost(host) {
175
175
  /**
176
176
  * IPv6 리터럴에 든 IPv4 를 점표기로 꺼낸다. 없으면 `null`.
177
177
  *
178
- * 형태를 본다 — IPv4-매핑(`::ffff:…`)NAT64(`64:ff9b::…`). 마지막 32비트가 IPv4 이고,
179
- * `WHATWG URL` 은 그것을 16진 두 묶음으로 정규화한다(`::ffff:7f00:1`). 점표기로 오는 경우도
180
- * 있으므로(정규화를 거친 입력) 그쪽도 받는다.
178
+ * 형태를 본다 — IPv4-매핑(`::ffff:…`)·NAT64(`64:ff9b::…`)·IPv4-호환(`::…`, RFC 4291
179
+ * `::/96`). 셋 다 마지막 32비트가 IPv4 이고, `WHATWG URL` 은 그것을 16진 두 묶음으로
180
+ * 정규화한다(`::127.0.0.1` `::7f00:1`). 점표기로 오는 경우도 있으므로 그쪽도 받는다.
181
+ *
182
+ * ⚠ **IPv4-호환은 오늘 이 리눅스에서 연결이 안 된다**(실측: `::127.0.0.1` 로의 연결은
183
+ * ENETUNREACH). 그래도 여는 이유는 이 열거가 **호스트 문자열 판정**이고, 라우팅 가능성은
184
+ * 커널·플랫폼이 정하는 다른 층이기 때문이다. 열거가 형태를 빠뜨리면 그 층이 바뀔 때 조용히 열린다.
181
185
  *
182
186
  * ⚠ **못 보는 것**: DNS 재바인딩(공개 이름이 내부 A 레코드를 주는 경우)은 이 층에서 못 막는다 —
183
187
  * 그건 이름 해석 시점의 문제라 주소 문자열로는 안 보인다. 아래 `assertPublicHost` 가 그 사실을
184
188
  * 이미 적어 두었다.
185
189
  */
186
190
  function embeddedIPv4(host) {
187
- const dotted = /^(?:::ffff:|64:ff9b::)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i.exec(host);
191
+ // `::` **접두가 없는** 형태라 마지막에 둔다. (JS 정규식 교대는 백트래킹하므로 앞에 둬도
192
+ // 결과는 같다 — 실측으로 7개 프로브 전부 동일. 읽는 사람이 「좁은 것부터」로 읽게 두는 배치다.)
193
+ const PREFIX = "(?:::ffff:|64:ff9b::|::)";
194
+ const dotted = new RegExp(`^${PREFIX}(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$`, "i").exec(host);
188
195
  if (dotted) return dotted[1];
189
- const hex = /^(?:::ffff:|64:ff9b::)([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(host);
196
+ const hex = new RegExp(`^${PREFIX}([0-9a-f]{1,4}):([0-9a-f]{1,4})$`, "i").exec(host);
190
197
  if (!hex) return null;
191
198
  const high = Number.parseInt(hex[1], 16);
192
199
  const low = Number.parseInt(hex[2], 16);
@@ -1109,13 +1109,24 @@ function fromStatements(src, where) {
1109
1109
  if (ts === null) return {statements: [], reliable: false};
1110
1110
 
1111
1111
  const tsx = where === undefined || /\.(tsx|jsx|js)$/.test(where);
1112
- const file = ts.createSourceFile(
1113
- where ?? "input.tsx",
1114
- src,
1115
- ts.ScriptTarget.Latest,
1116
- /* setParentNodes */ false,
1117
- tsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
1118
- );
1112
+ // **파서가 죽을 수 있다.** 극단으로 중첩된 식(괄호 수만 겹)은 TypeScript 파서를 스택
1113
+ // 오버플로로 보낸다. 그대로 두면 전역 `uncaughtException` 이 받아 rc=2 로 서는데(fail-closed —
1114
+ // 거짓 통과는 안 난다) **어느 파일인지 말하지 않는다.** 고객은 무엇을 고쳐야 할지 모른다.
1115
+ // 여기서 잡아 좌표를 실어 「못 읽음」으로 돌린다 — 판정은 그대로 «판독 실패» 다.
1116
+ // 재현: `export default () => ` + `(`×5만 + `1` + `)`×5만 을 `src/app/page.tsx` 에 두고 `--gate`.
1117
+ let file;
1118
+ try {
1119
+ file = ts.createSourceFile(
1120
+ where ?? "input.tsx",
1121
+ src,
1122
+ ts.ScriptTarget.Latest,
1123
+ /* setParentNodes */ false,
1124
+ tsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
1125
+ );
1126
+ } catch (e) {
1127
+ markUnmeasured(where ?? "(입력)", e?.name ?? "EPARSE", "구문 판독(파서가 감당하지 못함)");
1128
+ return {statements: [], reliable: false};
1129
+ }
1119
1130
  // 구문 오류가 있으면 트리가 어디까지 맞는지 알 수 없다 — 그 상태의 «못 찾음» 은 사실이 아니다.
1120
1131
  if ((file.parseDiagnostics ?? []).length > 0) return {statements: [], reliable: false};
1121
1132
 
@@ -1898,6 +1909,49 @@ function echoValue(value) {
1898
1909
  return s.length <= 200 ? s : `${s.slice(0, 200)}… (${s.length}자)`;
1899
1910
  }
1900
1911
 
1912
+ /**
1913
+ * JSON 파싱 실패를 **자리로만** 말한다. 원문 조각은 싣지 않는다.
1914
+ *
1915
+ * ⚠ **Node 의 메시지에는 소스 조각이 들어 있다.** v22 는 `Unexpected token` 계열에서 실패 지점
1916
+ * 앞뒤 ~15자를 붙인다 — 그 자리에 시크릿이 있으면 그 꼬리가 반려문에 실리고, 반려문은
1917
+ * `/_gate-status` logTail 로 돌아가며 빌드 로그에 남는다(심의 실측: 시크릿 값이
1918
+ * 든 배열에 쉼표를 하나 더 넣으면, Node 메시지가 그 값의 **꼬리 몇 글자**를 그대로 싣는다).
1919
+ *
1920
+ * ⚠ 이 주석에 그 형태를 **리터럴로 적지 않는다** — 이 파일 자신이 E3(시크릿 스캔) 대상이라
1921
+ * 검사기가 자기를 잡는다(실측으로 그렇게 됐다).
1922
+ *
1923
+ * 이 검사기의 다른 자리는 전부 소스 에코를 통제한다(E3 는 값을 아예 안 찍고, N5 는
1924
+ * `echoValue` 로 200자에서 끊는다). 여기만 통제 밖이었다.
1925
+ *
1926
+ * 위치(`at position N` · `line L column C`)는 남긴다 — 고치는 사람이 어디를 볼지 알아야 한다.
1927
+ * 그 숫자는 내용이 아니다.
1928
+ */
1929
+ /**
1930
+ * JSON 의 **구조 문자**. 이것만 반려문에 실어도 내용이 새지 않는다 — 값에는 못 나오는 글자들이다
1931
+ * (따옴표 안이면 파싱이 안 깨지고, 깨졌다는 것은 그 자리가 구조라는 뜻이다).
1932
+ */
1933
+ const STRUCTURAL_JSON = new Set([",", ":", "{", "}", "[", "]", '"', "'", " ", "\t", "\n", "\r"]);
1934
+
1935
+ function parseWhere(error) {
1936
+ const raw = String(error?.message ?? "");
1937
+ const where = /(?:at position \d+(?:\s*\(line \d+ column \d+\))?|line \d+ column \d+)/.exec(raw);
1938
+ // ⚠ **걸린 글자는 구조 문자일 때만 싣는다.** 「그 한 글자는 구조 정보이지 내용이 아니다」는
1939
+ // **거짓이다** — 값이 따옴표 없이 적혀 있으면 그 글자는 **값의 첫 글자**다(실측: 시크릿을
1940
+ // 비인용으로 두면 그 첫 글자가 반려문에 실린다). 회당 한 글자라도 여기서 내보내지 않는다.
1941
+ // 구조 문자면 그대로 두는 이유: 「쉼표가 하나 더 있습니다」는 고치는 사람에게 실제로 쓸모가 있다.
1942
+ const token = /^Unexpected token ('(.)'|"(.)")/.exec(raw);
1943
+ const glyph = token ? (token[2] ?? token[3]) : null;
1944
+ const head =
1945
+ glyph !== null && STRUCTURAL_JSON.has(glyph)
1946
+ ? `예상하지 못한 글자 '${glyph}'`
1947
+ : glyph !== null
1948
+ ? "값이 따옴표 없이 적혀 있거나 표기가 잘못됐습니다"
1949
+ : /Unexpected end of JSON input/.test(raw)
1950
+ ? "내용이 도중에 끝났습니다"
1951
+ : "형식이 올바르지 않습니다";
1952
+ return where ? `${head} (${where[0]})` : head;
1953
+ }
1954
+
1901
1955
  const DOC_PATH_TOKEN = /^[\p{L}\p{N}_.\-/[\]]+\.(?:tsx?|jsx?|mjs|cjs|json|css|md)$/u;
1902
1956
  /**
1903
1957
  * 좌표로 **안 보는** 접두들.
@@ -3078,7 +3132,7 @@ function checkContentContract() {
3078
3132
  try {
3079
3133
  page = JSON.parse(pageText);
3080
3134
  } catch (e) {
3081
- sink.push(`[N2] ${rel(file)}: JSON 파싱 실패 — ${e.message}`);
3135
+ sink.push(`[N2] ${rel(file)}: JSON 파싱 실패 — ${parseWhere(e)}`);
3082
3136
  continue;
3083
3137
  }
3084
3138
  if (page == null || typeof page !== "object" || Array.isArray(page)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalkera/client",
3
- "version": "0.24.1",
3
+ "version": "0.24.3",
4
4
  "description": "zalkera 헤드리스 CMS 공개 API 클라이언트 (테넌트 사이트용)",
5
5
  "license": "MIT",
6
6
  "author": "Credium Co., Ltd.",