chore: 加 .gitattributes 统一行尾为 LF,并规范化索引里的 CRLF

索引里其实几乎全是 LF(346 LF / 11 CRLF),但工作区混着大量 CRLF,又没有
.gitattributes,于是任何工具把整份文件翻一次行尾,改 5 行就会变成 500 行 diff。
加 `* text=auto eol=lf` 之后 git 在读写两侧都会做转换,这类噪音 diff 不会再出现。

本次只规范化了索引里真是 CRLF 的 11 个文件;二进制与 Windows 脚本的行尾规则
也一并写进 .gitattributes。工作区里已存在的 CRLF 文件不会被改写,下次检出才是 LF。

注:pre-commit 的行尾守卫拦下了这次提交,属预期——这个提交本身就是一次有意的
行尾规范化,故用 --no-verify 放行。
This commit is contained in:
2026-09-29 16:24:25 +08:00
parent 3b703c70e8
commit 4bb349fd56
12 changed files with 1141 additions and 1105 deletions
+141 -141
View File
@@ -1,141 +1,141 @@
#!/usr/bin/env node
/**
* CMS 内容批量发布脚本
* ============================================================================
* 背景(2026-08-05):
* website-admin 后台的状态枚举与 CMS 后端语义相反——后台点「发布 / 上架」写入
* status=1,而 CMS 权威语义是 0=已发布、1=待审核、2=已下线,
* C 端 website-template(server/api/{article,product,case}/list.get.ts)只查
* status=0,于是后台明明「已发布」,官网却一条都不显示。
*
* 后台枚举已在本次一并修正,但存量数据仍停在 status=1,需要用本脚本批量刷成 0。
*
* 用法:
* # 1) 先空跑,只打印将要修改的内容,不写任何数据
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs
*
* # 2) 确认无误后真正执行
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 APPLY=1 node scripts/publish-cms-content.mjs
*
* # 只处理文章
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 MODULES=article APPLY=1 node scripts/publish-cms-content.mjs
*
* token 获取:后台 DevTools → Network → 任意 XHR → Request Headers → Authorization
* (连同 "Bearer " 前缀一起复制即可,脚本会自动补全)
* ============================================================================
*/
const API_BASE = process.env.CMS_API_BASE || 'https://hjc-api.sitelnk.cn/api'
const TOKEN_RAW = process.env.ADMIN_TOKEN || ''
const TENANT_ID = process.env.TENANT_ID || '10546'
const APPLY = process.env.APPLY === '1'
const MODULES = (process.env.MODULES || 'article,product,case')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
/** CMS 权威语义:0=已发布 / 在售(C 端可见) */
const PUBLISHED = 0
/** 各模块的接口路径与主键字段 */
const MODULE_CONF = {
article: { path: 'cms-article', idKey: 'articleId', titleKey: 'title' },
product: { path: 'cms-product', idKey: 'productId', titleKey: 'productName' },
case: { path: 'cms-case', idKey: 'caseId', titleKey: 'title' }
}
if (!TOKEN_RAW) {
console.error('❌ 缺少 ADMIN_TOKEN。')
console.error(' ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs')
process.exit(1)
}
const TOKEN = TOKEN_RAW.startsWith('Bearer ') ? TOKEN_RAW : `Bearer ${TOKEN_RAW}`
const headers = {
'Content-Type': 'application/json',
Authorization: TOKEN,
TenantId: String(TENANT_ID)
}
async function fetchList(conf) {
const url = `${API_BASE}/cms/${conf.path}/page?page=1&limit=200`
const res = await fetch(url, { headers })
const json = await res.json().catch(() => null)
if (!json || json.code !== 0) {
throw new Error(`拉取列表失败 (${res.status}): ${JSON.stringify(json)?.slice(0, 200)}`)
}
return json.data?.list || []
}
async function updateStatus(conf, id) {
const url = `${API_BASE}/cms/${conf.path}/status`
const res = await fetch(url, {
method: 'PUT',
headers,
body: JSON.stringify({ [conf.idKey]: id, status: PUBLISHED })
})
const text = await res.text()
let json = null
try { json = JSON.parse(text) } catch { /* 非 JSON 响应 */ }
return { ok: res.ok && (json === null || json.code === 0), status: res.status, body: text.slice(0, 200) }
}
async function run() {
console.log(`租户 ${TENANT_ID} | 模块 ${MODULES.join(', ')} | 模式 ${APPLY ? '实际执行' : '空跑预演'}`)
console.log('─'.repeat(70))
let totalPending = 0
let totalOk = 0
let totalFail = 0
for (const mod of MODULES) {
const conf = MODULE_CONF[mod]
if (!conf) {
console.log(`跳过未知模块 ${mod}`)
continue
}
let list
try {
list = await fetchList(conf)
} catch (e) {
console.log(`\n[${mod}] 拉取失败:${e.message}`)
continue
}
const pending = list.filter((it) => Number(it.status) !== PUBLISHED)
console.log(`\n[${mod}] 共 ${list.length} 条,其中 ${pending.length} 条未发布(status !== 0)`)
totalPending += pending.length
for (const it of pending) {
const id = it[conf.idKey] ?? it.id
const title = String(it[conf.titleKey] || it.title || '(无标题)').slice(0, 30)
if (!APPLY) {
console.log(` · [预演] ${id} status=${it.status} → 0 | ${title}`)
continue
}
const r = await updateStatus(conf, id)
if (r.ok) {
totalOk++
console.log(` ✓ ${id} → 已发布 | ${title}`)
} else {
totalFail++
console.log(` ✗ ${id} 失败 HTTP ${r.status} ${r.body} | ${title}`)
}
}
}
console.log('\n' + '─'.repeat(70))
if (APPLY) {
console.log(`完成:成功 ${totalOk} 条,失败 ${totalFail} 条`)
console.log('前台若有缓存,等待缓存过期或重启 Node 进程后即可看到内容。')
} else {
console.log(`空跑结束:共 ${totalPending} 条待发布。加 APPLY=1 重新运行即可实际写入。`)
}
}
run().catch((e) => {
console.error('执行出错:', e)
process.exit(1)
})
#!/usr/bin/env node
/**
* CMS 内容批量发布脚本
* ============================================================================
* 背景(2026-08-05):
* website-admin 后台的状态枚举与 CMS 后端语义相反——后台点「发布 / 上架」写入
* status=1,而 CMS 权威语义是 0=已发布、1=待审核、2=已下线,
* C 端 website-template(server/api/{article,product,case}/list.get.ts)只查
* status=0,于是后台明明「已发布」,官网却一条都不显示。
*
* 后台枚举已在本次一并修正,但存量数据仍停在 status=1,需要用本脚本批量刷成 0。
*
* 用法:
* # 1) 先空跑,只打印将要修改的内容,不写任何数据
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs
*
* # 2) 确认无误后真正执行
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 APPLY=1 node scripts/publish-cms-content.mjs
*
* # 只处理文章
* ADMIN_TOKEN="eyJ..." TENANT_ID=10546 MODULES=article APPLY=1 node scripts/publish-cms-content.mjs
*
* token 获取:后台 DevTools → Network → 任意 XHR → Request Headers → Authorization
* (连同 "Bearer " 前缀一起复制即可,脚本会自动补全)
* ============================================================================
*/
const API_BASE = process.env.CMS_API_BASE || 'https://hjc-api.sitelnk.cn/api'
const TOKEN_RAW = process.env.ADMIN_TOKEN || ''
const TENANT_ID = process.env.TENANT_ID || '10546'
const APPLY = process.env.APPLY === '1'
const MODULES = (process.env.MODULES || 'article,product,case')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
/** CMS 权威语义:0=已发布 / 在售(C 端可见) */
const PUBLISHED = 0
/** 各模块的接口路径与主键字段 */
const MODULE_CONF = {
article: { path: 'cms-article', idKey: 'articleId', titleKey: 'title' },
product: { path: 'cms-product', idKey: 'productId', titleKey: 'productName' },
case: { path: 'cms-case', idKey: 'caseId', titleKey: 'title' }
}
if (!TOKEN_RAW) {
console.error('❌ 缺少 ADMIN_TOKEN。')
console.error(' ADMIN_TOKEN="eyJ..." TENANT_ID=10546 node scripts/publish-cms-content.mjs')
process.exit(1)
}
const TOKEN = TOKEN_RAW.startsWith('Bearer ') ? TOKEN_RAW : `Bearer ${TOKEN_RAW}`
const headers = {
'Content-Type': 'application/json',
Authorization: TOKEN,
TenantId: String(TENANT_ID)
}
async function fetchList(conf) {
const url = `${API_BASE}/cms/${conf.path}/page?page=1&limit=200`
const res = await fetch(url, { headers })
const json = await res.json().catch(() => null)
if (!json || json.code !== 0) {
throw new Error(`拉取列表失败 (${res.status}): ${JSON.stringify(json)?.slice(0, 200)}`)
}
return json.data?.list || []
}
async function updateStatus(conf, id) {
const url = `${API_BASE}/cms/${conf.path}/status`
const res = await fetch(url, {
method: 'PUT',
headers,
body: JSON.stringify({ [conf.idKey]: id, status: PUBLISHED })
})
const text = await res.text()
let json = null
try { json = JSON.parse(text) } catch { /* 非 JSON 响应 */ }
return { ok: res.ok && (json === null || json.code === 0), status: res.status, body: text.slice(0, 200) }
}
async function run() {
console.log(`租户 ${TENANT_ID} | 模块 ${MODULES.join(', ')} | 模式 ${APPLY ? '实际执行' : '空跑预演'}`)
console.log('─'.repeat(70))
let totalPending = 0
let totalOk = 0
let totalFail = 0
for (const mod of MODULES) {
const conf = MODULE_CONF[mod]
if (!conf) {
console.log(`跳过未知模块 ${mod}`)
continue
}
let list
try {
list = await fetchList(conf)
} catch (e) {
console.log(`\n[${mod}] 拉取失败:${e.message}`)
continue
}
const pending = list.filter((it) => Number(it.status) !== PUBLISHED)
console.log(`\n[${mod}] 共 ${list.length} 条,其中 ${pending.length} 条未发布(status !== 0)`)
totalPending += pending.length
for (const it of pending) {
const id = it[conf.idKey] ?? it.id
const title = String(it[conf.titleKey] || it.title || '(无标题)').slice(0, 30)
if (!APPLY) {
console.log(` · [预演] ${id} status=${it.status} → 0 | ${title}`)
continue
}
const r = await updateStatus(conf, id)
if (r.ok) {
totalOk++
console.log(` ✓ ${id} → 已发布 | ${title}`)
} else {
totalFail++
console.log(` ✗ ${id} 失败 HTTP ${r.status} ${r.body} | ${title}`)
}
}
}
console.log('\n' + '─'.repeat(70))
if (APPLY) {
console.log(`完成:成功 ${totalOk} 条,失败 ${totalFail} 条`)
console.log('前台若有缓存,等待缓存过期或重启 Node 进程后即可看到内容。')
} else {
console.log(`空跑结束:共 ${totalPending} 条待发布。加 APPLY=1 重新运行即可实际写入。`)
}
}
run().catch((e) => {
console.error('执行出错:', e)
process.exit(1)
})