feat: Pro 买断门禁、SFTP 精简与批量多机改造
新增 entitlement/license IPC 与 Waffo 一次性结账服务骨架,挂载 AI/转发/代理/串口/广播/批量导入导出门禁;批量执行改为结果矩阵并复用连接池,SFTP 工具栏图标化并去掉已连接条,同时完善跨平台本地终端与窗口配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
101d2164b4
commit
fea6d8db04
72 files changed
+3307
-396
No files matched your search
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* First-time bootstrap: create Store "OpsXShell" and one-time Pro product,
|
||||
* then write IDs into .env (never logs the private key).
|
||||
*/
|
||||
import { createRequire } from 'node:module'
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const ROOT = join(__dirname, '..')
|
||||
const ENV_PATH = join(ROOT, '.env')
|
||||
|
||||
function loadEnv() {
|
||||
if (!existsSync(ENV_PATH)) {
|
||||
console.error('缺少 .env — 请先 cp .env.example .env 并填入 WAFFO_PRIVATE_KEY')
|
||||
process.exit(1)
|
||||
}
|
||||
const raw = readFileSync(ENV_PATH, 'utf8')
|
||||
for (const line of raw.split('\n')) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) continue
|
||||
const eq = trimmed.indexOf('=')
|
||||
if (eq <= 0) continue
|
||||
const key = trimmed.slice(0, eq).trim()
|
||||
let value = trimmed.slice(eq + 1).trim()
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1)
|
||||
}
|
||||
if (!(key in process.env)) process.env[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
function upsertEnv(key, value) {
|
||||
let raw = existsSync(ENV_PATH) ? readFileSync(ENV_PATH, 'utf8') : ''
|
||||
const line = `${key}=${value}`
|
||||
const re = new RegExp(`^${key}=.*$`, 'm')
|
||||
if (re.test(raw)) raw = raw.replace(re, line)
|
||||
else raw = `${raw.trimEnd()}\n${line}\n`
|
||||
writeFileSync(ENV_PATH, raw)
|
||||
}
|
||||
|
||||
loadEnv()
|
||||
|
||||
const merchantId = process.env.WAFFO_MERCHANT_ID
|
||||
const privateKey = process.env.WAFFO_PRIVATE_KEY
|
||||
if (!merchantId || !privateKey) {
|
||||
console.error('需要 WAFFO_MERCHANT_ID 与 WAFFO_PRIVATE_KEY')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const { WaffoPancake } = require('@waffo/pancake-ts')
|
||||
const client = new WaffoPancake({
|
||||
merchantId,
|
||||
privateKey,
|
||||
...(process.env.WAFFO_ENVIRONMENT ? { environment: process.env.WAFFO_ENVIRONMENT } : {}),
|
||||
})
|
||||
|
||||
async function main() {
|
||||
let storeId = process.env.WAFFO_STORE_ID
|
||||
if (!storeId) {
|
||||
console.log('Creating store OpsXShell…')
|
||||
const store = await client.stores.create({
|
||||
name: 'OpsXShell',
|
||||
slug: `opsxshell-${Date.now().toString(36)}`,
|
||||
})
|
||||
storeId = store.id || store.storeId
|
||||
if (!storeId) throw new Error(`stores.create 未返回 id: ${JSON.stringify(store)}`)
|
||||
upsertEnv('WAFFO_STORE_ID', storeId)
|
||||
console.log('Store:', storeId)
|
||||
} else {
|
||||
console.log('Using existing store:', storeId)
|
||||
}
|
||||
|
||||
if (process.env.WAFFO_PRODUCT_ID) {
|
||||
console.log(`Using WAFFO_PRODUCT_ID=${process.env.WAFFO_PRODUCT_ID}`)
|
||||
console.log('\nBootstrap complete.')
|
||||
return
|
||||
}
|
||||
|
||||
const price = process.env.WAFFO_PRODUCT_PRICE || '29.00'
|
||||
console.log(`Creating one-time product OpsXShell Pro (USD ${price})…`)
|
||||
const { product } = await client.onetimeProducts.create({
|
||||
storeId,
|
||||
name: 'OpsXShell Pro',
|
||||
description: 'One-time unlock for OpsXShell professional features',
|
||||
prices: {
|
||||
USD: { amount: price, taxCategory: 'software' },
|
||||
},
|
||||
successUrl: process.env.SUCCESS_URL || 'opsxshell://pro/activated',
|
||||
metadata: { app: 'opsxshell', license: 'lifetime' },
|
||||
})
|
||||
const id = product?.id
|
||||
if (!id) throw new Error(`create onetime product failed: ${JSON.stringify(product)}`)
|
||||
|
||||
if (client.onetimeProducts.publish) {
|
||||
await client.onetimeProducts.publish({ id }).catch(() => undefined)
|
||||
}
|
||||
upsertEnv('WAFFO_PRODUCT_ID', id)
|
||||
console.log('WAFFO_PRODUCT_ID', id)
|
||||
console.log('\nBootstrap complete. Product ID written to .env')
|
||||
console.log('Next: npm start')
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in new issue
Block a user