2026-10-26 11:02:58 +00:00
/**
* scripts/import-sources.mjs — 참조 저장소(Webmaker·AriMail)의 소스를 modules/ 로 가져오고 IMPORTED_SOURCES.md manifest 를 만든다.
*
* 원칙(개발 지시 §0·§38)
* - 원본 저장소·커밋을 고정하고, 파일마다 원본 경로·대상 경로·수정 여부(해시 비교)·라이선스 검토 결과를 기록한다.
* - 문서·보고서·샘플·PDF 처럼 실행에 필요 없는 파일은 가져오지 않는다(EXCLUDE).
* - 이미 가져온 파일을 다시 실행해도 Ariweb Suite 쪽 수정본을 덮어쓰지 않는다(수정된 파일은 'modified' 로 표시하고 건너뜀). --force 로 원본으로 되돌릴 수 있다.
*
2026-10-28 15:07:35 +00:00
* 사용: node scripts/import-sources.mjs [--force] [--only webmaker|arimail] [--allow-dirty]
2026-10-26 11:02:58 +00:00
* 원본 위치는 환경 변수 WEBMAKER_SRC / ARIMAIL_SRC (기본: ~/work/WebHoting_AI_Agent, ~/work/mail-gateway 의 git 체크아웃).
2026-10-28 15:07:35 +00:00
* --allow-dirty (0.2.0): 원본 체크아웃이 고정 커밋 위에 아직 커밋되지 않은 변경(작업 트리)을 가진 상태도 받아들인다 — 참조 저장소와 Suite 를 같은 작업자가
* 나란히 개발할 때(예: Webmaker 1.8.0 직접 편집) 쓴다. 추적되지 않은 새 파일도 가져오고, upstream{version·branch·headCommit·workingTreeDirty} 를 함께 기록한다.
* commit 칸은 변하지 않는 기준(불변 baseline) 커밋이며, 작업 트리 변경이 원본 저장소에 커밋되면 pin 을 그 커밋으로 올리고 다시 실행한다.
* --only 로 한 소스만 다시 만들 때 다른 소스의 기존 기록은 그대로 유지한다.
2026-10-26 11:02:58 +00:00
*/
import { execSync } from 'node:child_process' ;
import { readFileSync , writeFileSync , mkdirSync , existsSync , statSync , copyFileSync } from 'node:fs' ;
import { createHash } from 'node:crypto' ;
import { join , dirname } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
import { homedir } from 'node:os' ;
const ROOT = join ( dirname ( fileURLToPath ( import . meta . url )), '..' );
2026-10-28 15:07:35 +00:00
const args = process . argv . slice ( 2 ); const force = args . includes ( '--force' ); const only = args . includes ( '--only' ) ? args [ args . indexOf ( '--only' ) + 1 ] : null ; const allowDirty = args . includes ( '--allow-dirty' );
2026-10-26 11:02:58 +00:00
const SOURCES = [
HAL 0.4.0: 통합 설정 관제 — Webmaker·AriMail·Agent 서버 단위 설정을 HAL 이 관리(설정 목록·범위 global/server·비밀값 Vault·유효값 hash, /ops/api/settings·재인증, settings.apply → settings.apply.target → Agent module.configure(HMAC 설정 받기·hash 확인·hal-managed.json 0600·재시작·모듈 적용 확인), /ops/settings 화면·운영 현황 "설정 적용 필요", AI 도구 hal_settings_get/set/apply(비밀값 거부)), 도입 모듈 Webmaker 1.9.0·AriMail 1.1.0(HAL 0.4.0 대응 halCompat, hal-managed 적용·관리 항목 409 managed_by_hal 잠금·"HAL 관리" 표시) 재도입(pin 8f8716c·f959b65, import-sources 미수정 파일 갱신), Agent heartbeat 관리, module-as-user write --mode 600, 개발 지시 v0.4.0(Oz)·docs/HAL-SETTINGS.md·VERSION_HISTORY 0.4.0 — test/hal-040.test.mjs RED → GREEN 5/5, agent 11/11, 전체 31파일 통과
2026-10-29 06:46:45 +00:00
{ name : 'webmaker' , repo : 'https://source.ibit.kr/OZ-Group/WebHosting_AI_agent' , src : process . env . WEBMAKER_SRC || join ( homedir (), 'work' , 'WebHoting_AI_Agent' ), dest : 'modules/webmaker' , pin : '8f8716cc2d59fedd94c9916f7f65c3cfdcd7a6af' ,
2026-10-26 11:02:58 +00:00
exclude : [ /^reports\// , /^sample\// , /\.pdf$/ , /^Dev_Order/ , /^WebHosting_AI_agent_/ , /^CHANGELOG\.md$/ , /^ERRORS\.md$/ , /^\.gitignore$/ ], license : 'OZ-Group 내부 전용(private: true, 별도 LICENSE 없음) — 같은 조직의 Ariweb Suite 에서 재사용 허용. 외부 배포 불가.' },
HAL 0.4.0: 통합 설정 관제 — Webmaker·AriMail·Agent 서버 단위 설정을 HAL 이 관리(설정 목록·범위 global/server·비밀값 Vault·유효값 hash, /ops/api/settings·재인증, settings.apply → settings.apply.target → Agent module.configure(HMAC 설정 받기·hash 확인·hal-managed.json 0600·재시작·모듈 적용 확인), /ops/settings 화면·운영 현황 "설정 적용 필요", AI 도구 hal_settings_get/set/apply(비밀값 거부)), 도입 모듈 Webmaker 1.9.0·AriMail 1.1.0(HAL 0.4.0 대응 halCompat, hal-managed 적용·관리 항목 409 managed_by_hal 잠금·"HAL 관리" 표시) 재도입(pin 8f8716c·f959b65, import-sources 미수정 파일 갱신), Agent heartbeat 관리, module-as-user write --mode 600, 개발 지시 v0.4.0(Oz)·docs/HAL-SETTINGS.md·VERSION_HISTORY 0.4.0 — test/hal-040.test.mjs RED → GREEN 5/5, agent 11/11, 전체 31파일 통과
2026-10-29 06:46:45 +00:00
{ name : 'arimail' , repo : 'https://source.ibit.kr/OZ-Group/AriMail' , src : process . env . ARIMAIL_SRC || join ( homedir (), 'work' , 'mail-gateway' ), dest : 'modules/arimail' , pin : 'f959b656f410289e024001f1f4712f2737c3b42b' ,
2026-10-26 11:02:58 +00:00
exclude : [ /^CHANGELOG\.md$/ , /^\.gitignore$/ , /^deploy\/sites\// ], license : 'OZ-Group 내부 전용(private: true, 별도 LICENSE 없음) — 같은 조직의 Ariweb Suite 에서 재사용 허용. 외부 배포 불가. haraka/ 는 npm Haraka(MIT) 를 설치 시점에 내려받는 래퍼만 포함.' },
];
const sha = ( f ) => createHash ( 'sha256' ). update ( readFileSync ( f )). digest ( 'hex' ). slice ( 0 , 16 );
2026-10-28 15:07:35 +00:00
const prevManifest = existsSync ( join ( ROOT , 'IMPORTED_SOURCES.json' )) ? JSON . parse ( readFileSync ( join ( ROOT , 'IMPORTED_SOURCES.json' ), 'utf8' )) : { sources : [] };
2026-10-26 11:02:58 +00:00
const manifest = { generatedAt : new Date (). toISOString (), sources : [] };
for ( const s of SOURCES ) {
2026-10-28 15:07:35 +00:00
if ( only && only !== s . name ) { const keep = prevManifest . sources . find (( x ) => x . name === s . name ); if ( keep ) manifest . sources . push ( keep ); continue ; } // --only: 다른 소스의 기존 기록 유지
2026-10-26 11:02:58 +00:00
const head = execSync ( 'git rev-parse HEAD' , { cwd : s . src }). toString (). trim ();
if ( head !== s . pin ) throw new Error ( ` ${ s . name } : 원본 체크아웃 커밋 ${ head } 이 고정 커밋 ${ s . pin } 과 다릅니다. git -C ${ s . src } checkout ${ s . pin } ` );
2026-10-28 15:07:35 +00:00
const dirty = execSync ( 'git status --porcelain' , { cwd : s . src }). toString (). trim (). length > 0 ;
if ( dirty && ! allowDirty ) throw new Error ( ` ${ s . name } : 원본 체크아웃에 커밋되지 않은 변경이 있습니다. 커밋한 뒤 pin 을 올리거나, 나란히 개발 중이면 --allow-dirty 로 실행하세요` );
// 추적 파일 + (--allow-dirty 일 때) 아직 추적되지 않은 새 파일. .gitignore 대상(data/·node_modules/ 등)은 제외
const lsArgs = allowDirty ? 'git ls-files -z --cached --others --exclude-standard' : 'git ls-files -z' ;
const files = [... new Set ( execSync ( lsArgs , { cwd : s . src }). toString (). split ( '\0' ). filter ( Boolean ))]. sort (). filter (( f ) => ! s . exclude . some (( re ) => re . test ( f )));
const branch = execSync ( 'git rev-parse --abbrev-ref HEAD' , { cwd : s . src }). toString (). trim ();
let version = null ; try { version = JSON . parse ( readFileSync ( join ( s . src , 'package.json' ), 'utf8' )). version || null ; } catch ( _ ) {}
const entry = { name : s . name , repo : s . repo , commit : head , dest : s . dest , license : s . license ,
upstream : { version , branch , headCommit : head , workingTreeDirty : dirty , note : dirty ? '고정 커밋(commit) 위에 커밋되지 않은 작업 트리 변경을 함께 가져왔다. 원본 저장소에 커밋되면 pin 을 그 커밋으로 올려 다시 실행한다.' : '고정 커밋의 깨끗한 체크아웃' },
files : [] };
HAL 0.4.0: 통합 설정 관제 — Webmaker·AriMail·Agent 서버 단위 설정을 HAL 이 관리(설정 목록·범위 global/server·비밀값 Vault·유효값 hash, /ops/api/settings·재인증, settings.apply → settings.apply.target → Agent module.configure(HMAC 설정 받기·hash 확인·hal-managed.json 0600·재시작·모듈 적용 확인), /ops/settings 화면·운영 현황 "설정 적용 필요", AI 도구 hal_settings_get/set/apply(비밀값 거부)), 도입 모듈 Webmaker 1.9.0·AriMail 1.1.0(HAL 0.4.0 대응 halCompat, hal-managed 적용·관리 항목 409 managed_by_hal 잠금·"HAL 관리" 표시) 재도입(pin 8f8716c·f959b65, import-sources 미수정 파일 갱신), Agent heartbeat 관리, module-as-user write --mode 600, 개발 지시 v0.4.0(Oz)·docs/HAL-SETTINGS.md·VERSION_HISTORY 0.4.0 — test/hal-040.test.mjs RED → GREEN 5/5, agent 11/11, 전체 31파일 통과
2026-10-29 06:46:45 +00:00
const prevFiles = new Map ((( prevManifest . sources . find (( x ) => x . name === s . name ) || {}). files || []). map (( x ) => [ x . from , x . origHash ]));
2026-10-26 11:02:58 +00:00
for ( const f of files ) {
const from = join ( s . src , f ); const to = join ( ROOT , s . dest , f );
if ( ! statSync ( from ). isFile ()) continue ;
const origHash = sha ( from );
let status ;
if ( ! existsSync ( to )) { mkdirSync ( dirname ( to ), { recursive : true }); copyFileSync ( from , to ); status = 'imported' ; }
else if ( sha ( to ) === origHash ) status = 'unchanged' ;
else if ( force ) { copyFileSync ( from , to ); status = 'restored' ; }
HAL 0.4.0: 통합 설정 관제 — Webmaker·AriMail·Agent 서버 단위 설정을 HAL 이 관리(설정 목록·범위 global/server·비밀값 Vault·유효값 hash, /ops/api/settings·재인증, settings.apply → settings.apply.target → Agent module.configure(HMAC 설정 받기·hash 확인·hal-managed.json 0600·재시작·모듈 적용 확인), /ops/settings 화면·운영 현황 "설정 적용 필요", AI 도구 hal_settings_get/set/apply(비밀값 거부)), 도입 모듈 Webmaker 1.9.0·AriMail 1.1.0(HAL 0.4.0 대응 halCompat, hal-managed 적용·관리 항목 409 managed_by_hal 잠금·"HAL 관리" 표시) 재도입(pin 8f8716c·f959b65, import-sources 미수정 파일 갱신), Agent heartbeat 관리, module-as-user write --mode 600, 개발 지시 v0.4.0(Oz)·docs/HAL-SETTINGS.md·VERSION_HISTORY 0.4.0 — test/hal-040.test.mjs RED → GREEN 5/5, agent 11/11, 전체 31파일 통과
2026-10-29 06:46:45 +00:00
// [0.4.0] pin 을 올릴 때: Suite 사본이 "이전에 가져온 원본"과 같으면(Suite 가 고치지 않은 파일) 새 원본으로 갱신한다. Suite 가 고친 파일만 'modified' 로 남겨 직접 옮긴다.
else if ( prevFiles . get ( f ) && sha ( to ) === prevFiles . get ( f )) { copyFileSync ( from , to ); status = 'updated' ; }
2026-10-26 11:02:58 +00:00
else status = 'modified' ;
entry . files . push ({ from : f , to : ` ${ s . dest } / ${ f } ` , status , origHash });
}
manifest . sources . push ( entry );
}
// manifest 파일(기계용)과 사람용 표를 함께 쓴다. 사람용 표의 '수정 여부' 는 git 의 현재 상태(해시 비교)로 다시 계산되므로 언제 실행해도 사실과 같다.
writeFileSync ( join ( ROOT , 'IMPORTED_SOURCES.json' ), JSON . stringify ( manifest , null , 1 ));
let md = `# 도입 소스 기록(IMPORTED_SOURCES)\n\n생성: ${ manifest . generatedAt } — \`node scripts/import-sources.mjs\` 가 만든다. 파일별 '수정' 은 원본 커밋과 현재 파일의 해시 비교 결과이며, 수정 이유는 VERSION_HISTORY.md 의 "도입 소스" 절과 각 파일 머리 주석의 \`[Ariweb Suite 변경]\` 표시로 설명한다.\n\n` ;
for ( const e of manifest . sources ) {
const mod = e . files . filter (( f ) => f . status === 'modified' ). length ;
2026-10-28 15:07:35 +00:00
const up = e . upstream ? `- 원본 버전(package.json): ${ e . upstream . version || '-' } · 브랜치: \` ${ e . upstream . branch || '-' } \` · 작업 트리: ${ e . upstream . workingTreeDirty ? '**커밋되지 않은 변경 포함(--allow-dirty)** — ' + e . upstream . note : '깨끗함' } \n` : '' ;
md += `## ${ e . name } \n\n- 원본 저장소: ${ e . repo } \n- 고정 커밋(불변 기준): \` ${ e . commit } \`\n ${ up } - 대상 경로: \` ${ e . dest } /\`\n- 파일 수: ${ e . files . length } (수정 ${ mod } )\n- 라이선스 검토: ${ e . license } \n\n| 원본 경로 | 대상 경로 | 수정 | 원본 해시 |\n|---|---|---|---|\n` ;
HAL 0.4.0: 통합 설정 관제 — Webmaker·AriMail·Agent 서버 단위 설정을 HAL 이 관리(설정 목록·범위 global/server·비밀값 Vault·유효값 hash, /ops/api/settings·재인증, settings.apply → settings.apply.target → Agent module.configure(HMAC 설정 받기·hash 확인·hal-managed.json 0600·재시작·모듈 적용 확인), /ops/settings 화면·운영 현황 "설정 적용 필요", AI 도구 hal_settings_get/set/apply(비밀값 거부)), 도입 모듈 Webmaker 1.9.0·AriMail 1.1.0(HAL 0.4.0 대응 halCompat, hal-managed 적용·관리 항목 409 managed_by_hal 잠금·"HAL 관리" 표시) 재도입(pin 8f8716c·f959b65, import-sources 미수정 파일 갱신), Agent heartbeat 관리, module-as-user write --mode 600, 개발 지시 v0.4.0(Oz)·docs/HAL-SETTINGS.md·VERSION_HISTORY 0.4.0 — test/hal-040.test.mjs RED → GREEN 5/5, agent 11/11, 전체 31파일 통과
2026-10-29 06:46:45 +00:00
for ( const f of e . files ) md += `| ${ f . from } | ${ f . to } | ${ f . status === 'modified' ? '**수정**' : f . status === 'restored' ? '원본 복원' : f . status === 'updated' ? '새 원본으로 갱신' : '그대로' } | ${ f . origHash } |\n` ;
2026-10-26 11:02:58 +00:00
md += '\n' ;
}
writeFileSync ( join ( ROOT , 'IMPORTED_SOURCES.md' ), md );
HAL 0.4.0: 통합 설정 관제 — Webmaker·AriMail·Agent 서버 단위 설정을 HAL 이 관리(설정 목록·범위 global/server·비밀값 Vault·유효값 hash, /ops/api/settings·재인증, settings.apply → settings.apply.target → Agent module.configure(HMAC 설정 받기·hash 확인·hal-managed.json 0600·재시작·모듈 적용 확인), /ops/settings 화면·운영 현황 "설정 적용 필요", AI 도구 hal_settings_get/set/apply(비밀값 거부)), 도입 모듈 Webmaker 1.9.0·AriMail 1.1.0(HAL 0.4.0 대응 halCompat, hal-managed 적용·관리 항목 409 managed_by_hal 잠금·"HAL 관리" 표시) 재도입(pin 8f8716c·f959b65, import-sources 미수정 파일 갱신), Agent heartbeat 관리, module-as-user write --mode 600, 개발 지시 v0.4.0(Oz)·docs/HAL-SETTINGS.md·VERSION_HISTORY 0.4.0 — test/hal-040.test.mjs RED → GREEN 5/5, agent 11/11, 전체 31파일 통과
2026-10-29 06:46:45 +00:00
for ( const e of manifest . sources ) console . log ( ` ${ e . name } : ${ e . files . length } 파일 ( ${ e . files . filter (( f ) => f . status === 'imported' ). length } 새로 가져옴, ${ e . files . filter (( f ) => f . status === 'updated' ). length } 갱신, ${ e . files . filter (( f ) => f . status === 'modified' ). length } 수정됨)` );