sns-auto-builder 1.0.4 → 1.0.5
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/README.md +27 -8
- package/index.html +20 -0
- package/package.json +1 -1
- package/scripts/build-dist-zip.mjs +90 -0
- package/server.mjs +16 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ ChatGPT 웹을 몰아 만듭니다 — 둘 다 **본인 구독 계정**으로
|
|
|
16
16
|
|
|
17
17
|
`dist/` 의 파일 **4개를 같은 폴더에** 두고 씁니다:
|
|
18
18
|
|
|
19
|
-
1. **설치.bat** — 처음 한 번. Node · Claude Code 설치
|
|
19
|
+
1. **설치.bat** — 처음 한 번. 저장 폴더 선택 → Node · Claude Code 설치 → 로그인 2번(Claude, ChatGPT)
|
|
20
20
|
2. **실행.bat** — 매번. 더블클릭하면 브라우저가 열립니다
|
|
21
21
|
3. **config.bat** — 설정. **두 bat 이 이것을 읽습니다**
|
|
22
22
|
4. **사용법.txt** — 안내
|
|
@@ -48,15 +48,24 @@ Claude Code 안에서는 슬래시 커맨드도 됩니다:
|
|
|
48
48
|
브랜드 설정 · ChatGPT 로그인 · 결과물이 그대로 남습니다.
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
%LOCALAPPDATA%\sns-auto-builder
|
|
51
|
+
<설치.bat 에서 고른 폴더>\ ← 기본값은 %LOCALAPPDATA%\sns-auto-builder
|
|
52
52
|
├── workspace 파일들 (output, knowledge, .chrome-profile, logs …)
|
|
53
53
|
├── browser\ puppeteer 크로미움 (약 2.6GB)
|
|
54
54
|
└── npm-cache\ npx 캐시 (수 GB)
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
`설치.bat` 1단계가 폴더 선택 창을 띄우고, 고른 경로를 `config.bat` 의
|
|
58
|
+
`set "DATA_PATH="` 줄에 써넣습니다. 바꾸려면 `설치.bat` 을 다시 실행하면 됩니다.
|
|
59
|
+
기존 워크스페이스는 최초 1회 자동으로 옮겨집니다.
|
|
60
|
+
|
|
61
|
+
`config.bat` 을 손으로 고치지 마세요. 이 파일은 **UTF-8(BOM 없음) + CRLF** 여야 하고,
|
|
62
|
+
둘 중 하나만 어긋나면 cmd 가 한글 섞인 줄을 잘못 읽어 **설정이 조용히 무시**됩니다
|
|
63
|
+
(LF 로 저장된 적이 있었고, 그때 한글 경로가 통째로 날아갔습니다).
|
|
64
|
+
`설치.bat` 은 PowerShell `WriteAllLines` 로 써서 이 조합을 보장합니다.
|
|
65
|
+
잘못돼도 빌더 첫 화면에 **실제 사용 중인 폴더가 항상 표시**되므로 눈에 띕니다.
|
|
66
|
+
|
|
67
|
+
**USB·외장 드라이브, 다운로드 폴더, OneDrive 동기화 폴더는 피하세요** —
|
|
68
|
+
각각 분리 시 로그인 소실, 정리 중 삭제, 2.6GB 크로미움 무한 동기화 문제가 있습니다.
|
|
60
69
|
|
|
61
70
|
> **경로는 한 곳에서만 계산합니다.** 예전에는 `실행.bat` · `설치.bat` · 스크립트가
|
|
62
71
|
> 각자 경로를 계산했고, 한쪽만 바뀌자 **ChatGPT 로그인이 빌더가 안 보는 폴더에 저장**됐습니다.
|
|
@@ -213,9 +222,19 @@ npm publish
|
|
|
213
222
|
|
|
214
223
|
`.bat` 은 패키지에 없어서 **자동 업데이트가 안 됩니다.** 고치면 팀원에게 4개를 다시 보내야 합니다.
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
225
|
+
```bash
|
|
226
|
+
node scripts/build-dist-zip.mjs # dist/snsauto-builder.zip 재생성
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
이 스크립트가 묶기 전에 **CRLF · BOM 없음 · 명령부 ASCII** 를 검사하고, 어긋나면
|
|
230
|
+
멈춥니다. zip 은 한글 파일명에 UTF-8 플래그(0x800)를 직접 세웁니다
|
|
231
|
+
(`Compress-Archive` 는 이걸 안 세워서 푸는 쪽에 따라 이름이 깨집니다).
|
|
232
|
+
|
|
233
|
+
> `.bat` 의 **명령과 주석은 ASCII 로만** 쓰세요. cmd 는 콘솔 코드페이지로 파일을 읽는데
|
|
234
|
+
> 그 값이 PC 마다 달라서, 한글 바이트가 명령어를 갉아먹습니다
|
|
235
|
+
> (`echo.` 가 `ech` + `o.` 로 쪼개진 적 있음). 한글 안내는 `사용법.txt` 와 Node 출력이 담당합니다.
|
|
236
|
+
> 예외는 `config.bat` 의 `set "DATA_PATH=..."` 값 한 줄뿐이고,
|
|
237
|
+
> 그건 파일이 UTF-8 + CRLF 일 때만 안전합니다.
|
|
219
238
|
|
|
220
239
|
---
|
|
221
240
|
|
package/index.html
CHANGED
|
@@ -382,10 +382,13 @@ button.go:focus-visible, button.quiet:focus-visible, .copy:focus-visible { outli
|
|
|
382
382
|
</div>
|
|
383
383
|
<div class="bar">
|
|
384
384
|
<button class="quiet" id="brandOpen">⚙ 브랜드 컨셉 설정</button>
|
|
385
|
+
<button class="quiet" id="openHome" title="결과물이 저장되는 폴더를 엽니다">📂 작업 폴더 열기</button>
|
|
385
386
|
<a class="quiet dl" id="logDl" href="/logs" download
|
|
386
387
|
title="문제가 생기면 이 파일을 담당자에게 보내세요">🛟 문제 신고용 로그</a>
|
|
387
388
|
<span class="fine" id="brandNow"></span>
|
|
388
389
|
</div>
|
|
390
|
+
<!-- 설정이 조용히 무시돼도 여기서 바로 드러나게 실제 경로를 보여준다 -->
|
|
391
|
+
<p class="fine" id="homePath"></p>
|
|
389
392
|
</section>
|
|
390
393
|
|
|
391
394
|
<!-- 1.2 브랜드 설정 — 여기 값이 모든 카피·디자인의 기준이 된다 -->
|
|
@@ -1554,6 +1557,23 @@ async function openOutput(it) {
|
|
|
1554
1557
|
$('outsOpen').onclick = () => { loadOutputs(); show('outs'); };
|
|
1555
1558
|
$('outsBack').onclick = () => show('engine');
|
|
1556
1559
|
|
|
1560
|
+
// 데이터는 bat 옆이 아니라 안정된 위치에 쌓인다 (새 zip 을 다른 곳에 풀어도 살아남게).
|
|
1561
|
+
// 대신 어디인지 보이고, 눌러서 열 수 있어야 한다.
|
|
1562
|
+
$('openHome').onclick = async () => {
|
|
1563
|
+
try {
|
|
1564
|
+
const r = await fetch('/home/open', { method: 'POST' });
|
|
1565
|
+
if (!r.ok) throw new Error('열지 못했습니다');
|
|
1566
|
+
} catch (e) { alert(e.message); }
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
(async () => {
|
|
1570
|
+
try {
|
|
1571
|
+
const { output } = await (await fetch('/home')).json();
|
|
1572
|
+
$('openHome').title = `결과물 폴더: ${output}`;
|
|
1573
|
+
$('homePath').textContent = `저장 위치: ${output}`;
|
|
1574
|
+
} catch { /* 경로 표시는 없어도 동작에 지장 없다 */ }
|
|
1575
|
+
})();
|
|
1576
|
+
|
|
1557
1577
|
loadBrand();
|
|
1558
1578
|
|
|
1559
1579
|
$('go').onclick = generate;
|
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// 팀원에게 보내는 4파일 묶음을 만든다.
|
|
2
|
+
// node scripts/build-dist-zip.mjs
|
|
3
|
+
//
|
|
4
|
+
// store 모드(무압축) zip 을 직접 쓴다. 의존성을 늘리지 않으려는 것도 있지만,
|
|
5
|
+
// Compress-Archive 가 한글 파일명에 UTF-8 플래그를 안 세워서 압축을 푸는
|
|
6
|
+
// 쪽에 따라 이름이 깨지기 때문이다. 여기서는 0x800 을 직접 세운다.
|
|
7
|
+
//
|
|
8
|
+
// bat 은 UTF-8(BOM 없음) + CRLF 여야 한다. 둘 중 하나만 어긋나도 cmd 가
|
|
9
|
+
// 한글이 섞인 줄을 조용히 잘못 읽어서 저장 폴더 설정이 무시된다.
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
12
|
+
import { join, dirname } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const DIST = join(dirname(fileURLToPath(import.meta.url)), '..', 'dist');
|
|
16
|
+
const OUT = 'snsauto-builder.zip';
|
|
17
|
+
|
|
18
|
+
const CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
|
|
19
|
+
let c = n;
|
|
20
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
21
|
+
return c >>> 0;
|
|
22
|
+
});
|
|
23
|
+
const crc32 = (buf) => {
|
|
24
|
+
let c = 0xffffffff;
|
|
25
|
+
for (const b of buf) c = CRC_TABLE[(c ^ b) & 0xff] ^ (c >>> 8);
|
|
26
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function checkBat(name, buf) {
|
|
30
|
+
if (buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) throw new Error(`${name}: BOM 이 있다`);
|
|
31
|
+
const text = buf.toString('utf8');
|
|
32
|
+
if (/(?<!\r)\n/.test(text)) throw new Error(`${name}: LF 줄바꿈이 남아 있다 (CRLF 여야 함)`);
|
|
33
|
+
for (const [i, line] of text.split('\r\n').entries()) {
|
|
34
|
+
// 경로 값 줄만 한글 허용. 명령·주석은 ASCII 여야 콘솔 코드페이지와 무관하게 읽힌다.
|
|
35
|
+
if (/[^\x00-\x7F]/.test(line) && !/^set "DATA_PATH=/.test(line)) {
|
|
36
|
+
throw new Error(`${name}:${i + 1}: bat 명령부에 비ASCII 문자 — ${line.trim()}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files = readdirSync(DIST).filter((f) => f !== OUT).sort();
|
|
42
|
+
const locals = [];
|
|
43
|
+
const central = [];
|
|
44
|
+
let offset = 0;
|
|
45
|
+
|
|
46
|
+
for (const name of files) {
|
|
47
|
+
const body = readFileSync(join(DIST, name));
|
|
48
|
+
if (name.endsWith('.bat')) checkBat(name, body);
|
|
49
|
+
|
|
50
|
+
const nameBuf = Buffer.from(name, 'utf8');
|
|
51
|
+
const crc = crc32(body);
|
|
52
|
+
const flags = 0x0800; // 파일명이 UTF-8 임을 표시
|
|
53
|
+
|
|
54
|
+
const local = Buffer.alloc(30);
|
|
55
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
56
|
+
local.writeUInt16LE(20, 4);
|
|
57
|
+
local.writeUInt16LE(flags, 6);
|
|
58
|
+
local.writeUInt32LE(crc, 14);
|
|
59
|
+
local.writeUInt32LE(body.length, 18);
|
|
60
|
+
local.writeUInt32LE(body.length, 22);
|
|
61
|
+
local.writeUInt16LE(nameBuf.length, 26);
|
|
62
|
+
locals.push(local, nameBuf, body);
|
|
63
|
+
|
|
64
|
+
const dir = Buffer.alloc(46);
|
|
65
|
+
dir.writeUInt32LE(0x02014b50, 0);
|
|
66
|
+
dir.writeUInt16LE(20, 4);
|
|
67
|
+
dir.writeUInt16LE(20, 6);
|
|
68
|
+
dir.writeUInt16LE(flags, 8);
|
|
69
|
+
dir.writeUInt32LE(crc, 16);
|
|
70
|
+
dir.writeUInt32LE(body.length, 20);
|
|
71
|
+
dir.writeUInt32LE(body.length, 24);
|
|
72
|
+
dir.writeUInt16LE(nameBuf.length, 28);
|
|
73
|
+
dir.writeUInt32LE(offset, 42);
|
|
74
|
+
central.push(dir, nameBuf);
|
|
75
|
+
|
|
76
|
+
offset += 30 + nameBuf.length + body.length;
|
|
77
|
+
console.log(` ${name.padEnd(14)} ${String(body.length).padStart(6)} bytes`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const centralBuf = Buffer.concat(central);
|
|
81
|
+
const end = Buffer.alloc(22);
|
|
82
|
+
end.writeUInt32LE(0x06054b50, 0);
|
|
83
|
+
end.writeUInt16LE(files.length, 8);
|
|
84
|
+
end.writeUInt16LE(files.length, 10);
|
|
85
|
+
end.writeUInt32LE(centralBuf.length, 12);
|
|
86
|
+
end.writeUInt32LE(offset, 16);
|
|
87
|
+
|
|
88
|
+
const zip = Buffer.concat([...locals, centralBuf, end]);
|
|
89
|
+
writeFileSync(join(DIST, OUT), zip);
|
|
90
|
+
console.log(`\n${OUT}: ${files.length}개 파일, ${zip.length} bytes`);
|
package/server.mjs
CHANGED
|
@@ -902,6 +902,22 @@ ${context || '(없음)'}
|
|
|
902
902
|
.end(readFileSync(LOGO_SVG));
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
+
// 작업 폴더 위치를 알려주고, 탐색기로 열어준다.
|
|
906
|
+
// 데이터를 bat 옆이 아니라 안정된 위치에 두는 대신, 찾기는 쉬워야 한다.
|
|
907
|
+
if (req.method === 'GET' && req.url === '/home') {
|
|
908
|
+
return json(200, { root: ROOT, output: join(ROOT, 'output') });
|
|
909
|
+
}
|
|
910
|
+
if (req.method === 'POST' && req.url === '/home/open') {
|
|
911
|
+
const target = join(ROOT, 'output');
|
|
912
|
+
try { mkdirSync(target, { recursive: true }); } catch { /* 이미 있으면 그만 */ }
|
|
913
|
+
const cmd = process.platform === 'win32' ? ['explorer', [target]]
|
|
914
|
+
: process.platform === 'darwin' ? ['open', [target]] : ['xdg-open', [target]];
|
|
915
|
+
// explorer 는 성공해도 0 이 아닌 코드를 내는 일이 있어 종료코드를 보지 않는다.
|
|
916
|
+
spawn(cmd[0], cmd[1], { detached: true, stdio: 'ignore' }).on('error', () => {}).unref();
|
|
917
|
+
log('작업 폴더 열기', target);
|
|
918
|
+
return json(200, { ok: true, path: target });
|
|
919
|
+
}
|
|
920
|
+
|
|
905
921
|
// 브랜드 설정 — 스키마와 현재 값을 같이 준다 (화면이 스키마로 폼을 그린다)
|
|
906
922
|
if (req.method === 'GET' && req.url === '/brand') {
|
|
907
923
|
return json(200, { schema: JSON.parse(read('brand.schema.json')), values: readBrand(), logo: hasLogo() });
|