fix(vendor-bundle): 코어 빌드 무한 행 해결 + 이커머스 알림 라벨 raw 노출 수정

이커머스 알림 정의 화면에서 order_pending_deposit·order_delivered 두 알림이
라벨 대신 raw 다국어 키로 표시되던 문제를 수정. settings.json 3로케일에 라벨
추가 + 렌더 회귀 테스트 + 브라우저 실측.

vendor-bundle 시스템 결함 5종 해결:
- (A/B) manifest 해시 기준을 활성이 아닌 출력(_bundled) 디렉토리로 통일해
 확장 업데이트가 composer_json_sha_mismatch 순환에 빠지던 문제 해소
- (C) 코어 빌드 시 composer autoload dump 의 async 손자 프로세스가 부모
 파이프 핸들을 상속·점유해 무한 대기하던 교착을, stdout/stderr 를 파이프
 아닌 파일 descriptor 로 받아 원천 차단 (파이프=행/파일=완주 A/B 실측으로 확정)
- (D) 빌드 실패 시 원본 번들 유실 방지 — 임시파일 성공 후 원자적 교체
- (E) Windows .bat 실행 시 bypass_shell 통일

코어 7.0.4 / 이커머스·언어팩 1.0.3 bump.
This commit is contained in:
HeuJung committed 2026-07-11 07:00:34 +09:00
1 parent a34a03eb40
commit 13e65131f4
30 files changed
+620 -79

No files matched your search

@@ -4,6 +4,12 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [1.0.3] - 2026-07-10
### Fixed
- 쇼핑몰 환경설정 > 알림 설정에서 "무통장 입금 안내"와 "배송 완료" 두 알림의 이름이 사람이 읽을 수 없는 내부 문자열로 표시되던 문제를 수정했습니다. 이제 다른 알림과 동일하게 이름이 표시되며, 알림을 기본값으로 되돌릴 때 뜨는 확인창과 알림 편집 화면에서도 함께 바로잡혔습니다.
## [1.0.2] - 2026-07-10
### Changed
@@ -2,7 +2,7 @@
"name": "modules/sirsoft-ecommerce",
"description": "Ecommerce module for Gnuboard7",
"type": "library",
"version": "1.0.2",
"version": "1.0.3",
"license": "MIT",
"autoload": {
"psr-4": {
@@ -5,7 +5,7 @@
"ko": "이커머스",
"en": "Ecommerce"
},
"version": "1.0.2",
"version": "1.0.3",
"license": "MIT",
"description": {
"ko": "그누보드7 이커머스 모듈 - 상품, 주문, 결제 관리",
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@g7/sirsoft-ecommerce",
"version": "1.0.2",
"version": "1.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@g7/sirsoft-ecommerce",
"version": "1.0.2",
"version": "1.0.3",
"devDependencies": {
"jsdom": "^27.4.0",
"typescript": "^5.3.3",
@@ -1,6 +1,6 @@
{
"name": "@g7/sirsoft-ecommerce",
"version": "1.0.2",
"version": "1.0.3",
"description": "그누보드7 이커머스 모듈 프론트엔드 에셋",
"private": true,
"type": "module",
@@ -0,0 +1,182 @@
// e2e:allow 라벨 데이터(settings.json) + Vitest 렌더 회귀 테스트만 변경 — 레이아웃 동작/구조 무변경. 알림 정의 화면의 실제 렌더 경로($t: 동적 키 해석)는 본 render 테스트가 커버하고, 화면 수준 검증은 별도 브라우저 실측(handoff §10)으로 확인.
/**
* @file notificationDefinitionTypeLabels.test.tsx
* @description 이커머스 알림 정의 화면 — 타입 라벨 다국어 키 raw 노출 회귀 가드.
*
* 배경: `order_pending_deposit`, `order_delivered` 두 알림 타입이 module.php 에는
* 정의됐으나 라벨을 읽는 settings.json 의 `notification_definitions.types` 에 이름이
* 등록되지 않아, 알림 정의 화면에서
* `sirsoft-ecommerce.admin.settings.notification_definitions.types.order_pending_deposit`
* 같은 raw 키가 그대로 노출됐다.
*
* 본 테스트는 _tab_notification_definitions.json 이 사용하는 실제 바인딩
* `$t:sirsoft-ecommerce.admin.settings.notification_definitions.types.{{def.type}}` 을
* 재현하고, **실제 settings.json** 라벨을 주입해 두 타입이 raw 키가 아닌 사람이 읽는
* 라벨로 렌더되는지 검증한다. settings.json 에서 어느 한 키가 제거되면 이 테스트가
* 다시 red 로 떨어져 회귀를 차단한다.
*/
import React from 'react';
import { describe, it, expect, afterEach, vi } from 'vitest';
import {
createLayoutTest,
createMockComponentRegistryWithBasics,
screen,
type MockComponentRegistry,
} from '@core/template-engine/__tests__/utils/layoutTestUtils';
import settingsKo from '../../../lang/partial/ko/admin/settings.json';
import settingsEn from '../../../lang/partial/en/admin/settings.json';
// 실제 화면(_tab_notification_definitions.json line ~371)의 타입 라벨 바인딩을 그대로 축약 재현.
// 알림 목록 항목마다 H4 에 `$t:...types.{{def.type}}` 로 이름을 표시한다.
const LABEL_BINDING =
'$t:sirsoft-ecommerce.admin.settings.notification_definitions.types.{{def.type}}';
function makeLayout() {
return {
name: 'notification_definition_type_labels_probe',
version: '1.0.0',
components: [
{
type: 'basic',
name: 'Div',
children: [
{
type: 'basic',
name: 'Div',
iteration: {
source: '{{ecommerceNotificationDefinitions?.data?.data ?? []}}',
item_var: 'def',
index_var: 'defIdx',
},
children: [
{
type: 'basic',
name: 'H4',
text: LABEL_BINDING,
},
],
},
],
},
],
};
}
// 실제 서빙 데이터를 모사한 알림 정의 목록 — 문제의 두 타입 포함.
const DEFINITIONS = [
{ id: 1, type: 'order_pending_deposit', templates: [], variables: [] },
{ id: 2, type: 'order_delivered', templates: [], variables: [] },
];
// settings.json 은 `notification_definitions` 를 admin.settings 하위가 아닌 파일 루트에 둔다.
// 화면 바인딩은 `sirsoft-ecommerce.admin.settings.notification_definitions...` 네임스페이스를
// 쓰므로, 주입 시 admin.settings 아래로 매핑한다(활성 언어팩 로더의 병합 결과와 동일 구조).
function buildTranslations(settings: Record<string, any>) {
return {
'sirsoft-ecommerce': {
admin: {
settings: settings,
},
},
};
}
let registry: MockComponentRegistry;
afterEach(() => {
vi.clearAllMocks();
});
describe('알림 정의 타입 라벨 — 다국어 키 raw 노출 회귀 가드', () => {
it('ko: order_pending_deposit / order_delivered 가 라벨로 렌더된다 (raw 키 미노출)', async () => {
registry = createMockComponentRegistryWithBasics();
const { render, cleanup } = createLayoutTest(makeLayout(), {
componentRegistry: registry as any,
locale: 'ko',
translations: buildTranslations(settingsKo),
initialData: {
ecommerceNotificationDefinitions: { data: { data: DEFINITIONS } },
},
});
await render();
expect(screen.getByText('무통장 입금 안내')).toBeInTheDocument();
expect(screen.getByText('배송 완료')).toBeInTheDocument();
// raw 키가 화면에 남아있으면 회귀
expect(
screen.queryByText(/notification_definitions\.types\.order_pending_deposit/),
).not.toBeInTheDocument();
expect(
screen.queryByText(/notification_definitions\.types\.order_delivered/),
).not.toBeInTheDocument();
cleanup();
});
it('en: order_pending_deposit / order_delivered 가 라벨로 렌더된다 (raw 키 미노출)', async () => {
registry = createMockComponentRegistryWithBasics();
const { render, cleanup } = createLayoutTest(makeLayout(), {
componentRegistry: registry as any,
locale: 'en',
translations: buildTranslations(settingsEn),
initialData: {
ecommerceNotificationDefinitions: { data: { data: DEFINITIONS } },
},
});
await render();
expect(screen.getByText('Bank Transfer Payment Guide')).toBeInTheDocument();
expect(screen.getByText('Order Delivered')).toBeInTheDocument();
expect(
screen.queryByText(/notification_definitions\.types\.order_pending_deposit/),
).not.toBeInTheDocument();
expect(
screen.queryByText(/notification_definitions\.types\.order_delivered/),
).not.toBeInTheDocument();
cleanup();
});
it('라벨 제거 시 raw 키가 노출됨 — 회귀 가드가 실제로 결함을 감지한다', async () => {
// 이 테스트는 위 두 가드가 "우연히 통과"하는 것이 아님을 증명한다:
// settings.json 에서 두 타입 라벨을 제거하면 화면에 raw 키가 그대로 노출된다.
registry = createMockComponentRegistryWithBasics();
const stripped = JSON.parse(JSON.stringify(settingsKo));
delete stripped.notification_definitions.types.order_pending_deposit;
delete stripped.notification_definitions.types.order_delivered;
const { render, cleanup } = createLayoutTest(makeLayout(), {
componentRegistry: registry as any,
locale: 'ko',
translations: buildTranslations(stripped),
initialData: {
ecommerceNotificationDefinitions: { data: { data: DEFINITIONS } },
},
});
await render();
expect(screen.queryByText('무통장 입금 안내')).not.toBeInTheDocument();
expect(
screen.getByText(/notification_definitions\.types\.order_pending_deposit/),
).toBeInTheDocument();
cleanup();
});
it('settings.json(ko/en) 의 types 에 두 신규 타입 라벨이 존재한다', () => {
const koTypes = (settingsKo as any).notification_definitions.types;
const enTypes = (settingsEn as any).notification_definitions.types;
expect(koTypes.order_pending_deposit).toBe('무통장 입금 안내');
expect(koTypes.order_delivered).toBe('배송 완료');
expect(enTypes.order_pending_deposit).toBe('Bank Transfer Payment Guide');
expect(enTypes.order_delivered).toBe('Order Delivered');
});
});
@@ -447,7 +447,9 @@
"save_error": "Failed to save notification settings.",
"types": {
"order_confirmed": "Order Confirmed",
"order_pending_deposit": "Bank Transfer Payment Guide",
"order_shipped": "Order Shipped",
"order_delivered": "Order Delivered",
"order_completed": "Purchase Completed",
"order_cancelled": "Order Cancelled",
"new_order_admin": "New Order (Admin)",
@@ -447,7 +447,9 @@
"save_error": "알림 설정 저장에 실패했습니다.",
"types": {
"order_confirmed": "주문 확인",
"order_pending_deposit": "무통장 입금 안내",
"order_shipped": "배송 시작",
"order_delivered": "배송 완료",
"order_completed": "구매 확정",
"order_cancelled": "주문 취소",
"new_order_admin": "신규 주문 (관리자)",
@@ -1,11 +1,11 @@
{
"schema_version": "1.0",
"generated_at": "2026-07-10T12:05:27+00:00",
"generated_at": "2026-07-10T20:22:38+00:00",
"generator": "g7 vendor-bundle:build",
"target": "module:sirsoft-ecommerce",
"composer_json_sha256": "aa8f74063b7f9cd969cb4effb120e3c50bb944a4d3b2e16fae7c31bd8232fce8",
"composer_json_sha256": "5195d789bc9edd0b5140c42aeedd7c87be67c7252b477a879ff90607b4787935",
"composer_lock_sha256": "876ca9c2273a33baff878d25050a567018a946412db053930a7f548c4add595d",
"zip_sha256": "ef77515e4d920a52a8adba1e6d7e310389c6f3f459586d3cd5c24749f24d7fb0",
"zip_sha256": "c1767e8eca61163667e9289a3963454bab92806ce4e8a3651777f1e1aea14081",
"zip_size": 435549,
"package_count": 1,
"php_requirement": "^8.2",
Binary file not shown.