init
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
const API = 'http://localhost:3000/api/v1';
|
||||
|
||||
async function req(clientApp, path, options = {}) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Client-App': clientApp,
|
||||
...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),
|
||||
};
|
||||
const res = await fetch(`${API}${path}`, { ...options, headers, body: options.body });
|
||||
const json = await res.json();
|
||||
if (json.code !== 0) throw new Error(`${path}: ${json.message}`);
|
||||
return json.data;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('1. Health');
|
||||
await req('USER_H5', '/health');
|
||||
|
||||
console.log('2. User login');
|
||||
const userLogin = await req('USER_H5', '/auth/login/sms', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ phone: '13800000001', code: '123456' }),
|
||||
});
|
||||
const userToken = userLogin.accessToken;
|
||||
|
||||
console.log('3. Catalog');
|
||||
const products = await req('USER_H5', '/catalog/products');
|
||||
if (products.length < 4) throw new Error('Expected 4 products');
|
||||
|
||||
console.log('4. Create address + order + mock pay');
|
||||
const addr = await req('USER_H5', '/user/addresses', {
|
||||
method: 'POST',
|
||||
token: userToken,
|
||||
body: JSON.stringify({
|
||||
receiverName: '测试',
|
||||
phone: '13800000001',
|
||||
province: '河南省',
|
||||
city: '郑州市',
|
||||
district: '金水区',
|
||||
detail: '冒烟测试地址1号',
|
||||
isDefault: true,
|
||||
}),
|
||||
});
|
||||
const order = await req('USER_H5', '/trade/orders', {
|
||||
method: 'POST',
|
||||
token: userToken,
|
||||
body: JSON.stringify({ productId: products[0].id, quantity: 2, addressId: addr.id }),
|
||||
});
|
||||
await req('USER_H5', `/trade/orders/${order.id}/pay`, { method: 'POST', token: userToken });
|
||||
|
||||
console.log('5. Benefit granted');
|
||||
const coupons = await req('USER_H5', '/benefit/coupons', { token: userToken });
|
||||
if (!coupons.length) throw new Error('No coupon after pay');
|
||||
|
||||
console.log('6. Redeem token');
|
||||
const tokenData = await req('USER_H5', '/redeem/tokens', {
|
||||
method: 'POST',
|
||||
token: userToken,
|
||||
body: JSON.stringify({ couponId: coupons[0].id, amount: 50 }),
|
||||
});
|
||||
|
||||
console.log('7. Shop redeem');
|
||||
const shopLogin = await req('SHOP_H5', '/shop/auth/login/sms', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ phone: '13900000001', code: '123456' }),
|
||||
});
|
||||
await req('SHOP_H5', '/shop/redeem/confirm', {
|
||||
method: 'POST',
|
||||
token: shopLogin.accessToken,
|
||||
body: JSON.stringify({ token: tokenData.token }),
|
||||
});
|
||||
|
||||
console.log('8. Partner orders');
|
||||
const partnerLogin = await req('PARTNER_H5', '/partner/auth/login/sms', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ phone: '13700000001', code: '123456' }),
|
||||
});
|
||||
const orders = await req('PARTNER_H5', '/partner/orders', { token: partnerLogin.accessToken });
|
||||
if (!orders.list.length) throw new Error('Partner should see orders');
|
||||
|
||||
console.log('\n✅ preV1 smoke passed');
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error('❌ Smoke failed:', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Sync Stitch shared project "4端1.0版本" prototypes to pages/
|
||||
* Usage: STITCH_API_KEY=xxx node scripts/sync-stitch.mjs [--force]
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const PAGES = path.join(ROOT, 'pages');
|
||||
const PROJECT_ID = process.env.STITCH_PROJECT_ID || '9513024892894556686';
|
||||
const FORCE = process.argv.includes('--force');
|
||||
|
||||
const API_KEY =
|
||||
process.env.STITCH_API_KEY ||
|
||||
(() => {
|
||||
try {
|
||||
const mcp = JSON.parse(fs.readFileSync(path.join(process.env.USERPROFILE || '', '.cursor', 'mcp.json'), 'utf8'));
|
||||
return mcp?.mcpServers?.stitch?.headers?.['X-Goog-Api-Key'];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error('Missing STITCH_API_KEY');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function mcpCall(tool, args = {}) {
|
||||
const res = await fetch('https://stitch.googleapis.com/mcp', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Goog-Api-Key': API_KEY },
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: Date.now(),
|
||||
method: 'tools/call',
|
||||
params: { name: tool, arguments: args },
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.error) throw new Error(json.error.message || JSON.stringify(json.error));
|
||||
if (json.result?.isError) throw new Error(json.result.content?.[0]?.text || 'MCP error');
|
||||
return json.result.structuredContent ?? JSON.parse(json.result.content?.[0]?.text || '{}');
|
||||
}
|
||||
|
||||
async function download(url, dest) {
|
||||
if (!url) return false;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return false;
|
||||
fs.writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
|
||||
return true;
|
||||
}
|
||||
|
||||
function classify(title) {
|
||||
const t = title || '';
|
||||
if (/\.md$|logo\.png|全案交付|Stitch提示词/i.test(t)) return 'asset';
|
||||
if (/总部|开城管理|开城页面|城市参数|商品管理页|拦截配送|数据报表|预警详情|补发处理页面|跨城发货处理|财务结算中心|财务对账|合同管理|子账号|我的员工|推广码|数据周报|总部管理|在线客服对话页.*总部|客服中心.*跨城|申请打款|资产明细|账单明细详情|超时订单|订单售后详情.*补发与退款|补发订单详情|添加商品|编辑城市页面|门店审核管理|门店审核记录|添加子账号|新增开城|生成推广码|推广码详情|推广码管理|合伙人贡献榜|合伙人待确认账单.*(?!合伙人中心)/i.test(t)) {
|
||||
if (/合伙人待确认账单|合伙人中心|合伙人订单|录入新门店|合伙人门店|合伙人工作台|城市合伙人登录|城市合伙人快捷|城市合伙人订单/.test(t)) {
|
||||
// keep partner-specific
|
||||
} else if (/总部|开城|拦截|数据报表|预警|补发处理页|跨城发货|财务结算|财务对账|合同|子账号|我的员工|推广码|数据周报|申请打款|资产明细|账单明细|超时订单|添加商品|编辑城市|门店审核管理|门店审核记录|添加子账号|新增开城|商品管理页/.test(t)) {
|
||||
return 'skip';
|
||||
}
|
||||
}
|
||||
if (/总部|开城管理|拦截配送管理|数据报表中心|预警详情|补发处理页面|跨城发货处理|财务结算中心|财务对账|合同管理|子账号|我的员工|推广码管理|数据周报|总部管理|申请打款|资产明细|账单明细详情|超时订单详情|添加商品页面|编辑城市页面|门店审核管理|门店审核记录|添加子账号|新增开城|生成推广码|推广码详情|合伙人贡献榜|商品管理页|订单中心管理页|订单售后详情|补发订单详情|在线客服对话页|总部客服|总部端订单|总部管理中心|总部管理登录|总部管理一键|拦截配送|城市参数配置/i.test(t)) {
|
||||
return 'skip';
|
||||
}
|
||||
if (/门店端|门店管理|S-0|门店核销|门店营业|门店登录|门店管理登录|门店管理首页|门店管理一键|门店核销成功|门店核销确认|门店核销记录/i.test(t) && !/合伙人门店|录入新门店/.test(t)) {
|
||||
return 'shop';
|
||||
}
|
||||
if (/合伙人|录入新门店|城市合伙人/i.test(t)) return 'partner';
|
||||
if (/C端|小程序|用户登录|商品详情|确认订单|地址|我的订单|订单详情|好客权益|个人中心|餐券核销|核销码|可用门店|门店详情|微信支付|定位授权|微信|核销成功及评价|核销成功.*评价/i.test(t)) {
|
||||
return 'user';
|
||||
}
|
||||
if (/门店详情页$/.test(t) && !/编辑/.test(t)) return 'user';
|
||||
if (/门店详情编辑/.test(t)) return 'partner';
|
||||
return 'skip';
|
||||
}
|
||||
|
||||
function slug(title, screenId) {
|
||||
const base = (title || 'screen')
|
||||
.replace(/[^\w\u4e00-\u9fa5]+/g, '_')
|
||||
.replace(/^_|_$/g, '')
|
||||
.slice(0, 40);
|
||||
return `${base}_${screenId.slice(0, 8)}`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Fetching project...');
|
||||
const project = await mcpCall('get_project', { name: `projects/${PROJECT_ID}` });
|
||||
const designMd = project.designTheme?.designMd;
|
||||
if (designMd) {
|
||||
const designPath = path.join(PAGES, 'stitch_4_1.0', 'DESIGN.md');
|
||||
fs.mkdirSync(path.dirname(designPath), { recursive: true });
|
||||
fs.writeFileSync(designPath, designMd, 'utf8');
|
||||
console.log('Updated DESIGN.md');
|
||||
}
|
||||
|
||||
console.log('Listing screens...');
|
||||
const { screens = [] } = await mcpCall('list_screens', { projectId: PROJECT_ID });
|
||||
console.log(`Found ${screens.length} screens`);
|
||||
|
||||
const manifest = {
|
||||
projectId: PROJECT_ID,
|
||||
projectTitle: '4端1.0版本',
|
||||
syncedAt: new Date().toISOString(),
|
||||
screens: [],
|
||||
};
|
||||
|
||||
let idx = { user: 0, shop: 0, partner: 0, skip: 0, asset: 0 };
|
||||
|
||||
for (const screen of screens) {
|
||||
const screenId = screen.name?.replace(/.*\/screens\//, '') || screen.screenId;
|
||||
const title = screen.title || screen.displayName || '';
|
||||
const category = classify(title);
|
||||
idx[category] = (idx[category] || 0) + 1;
|
||||
|
||||
const entry = {
|
||||
screenId,
|
||||
title,
|
||||
category,
|
||||
route: null,
|
||||
priority: category === 'skip' || category === 'asset' ? 'skip' : 'P1',
|
||||
};
|
||||
|
||||
if (category === 'asset') {
|
||||
const assetDir = path.join(PAGES, '_assets');
|
||||
fs.mkdirSync(assetDir, { recursive: true });
|
||||
manifest.screens.push(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (category === 'skip') {
|
||||
const archiveDir = path.join(PAGES, '_archive', slug(title, screenId));
|
||||
fs.mkdirSync(archiveDir, { recursive: true });
|
||||
entry.dir = path.relative(ROOT, archiveDir);
|
||||
manifest.screens.push(entry);
|
||||
if (FORCE || !fs.existsSync(path.join(archiveDir, 'meta.json'))) {
|
||||
try {
|
||||
const detail = await mcpCall('get_screen', { name: `projects/${PROJECT_ID}/screens/${screenId}`, projectId: PROJECT_ID, screenId });
|
||||
fs.writeFileSync(path.join(archiveDir, 'meta.json'), JSON.stringify({ title, screenId, syncedAt: new Date().toISOString() }, null, 2));
|
||||
if (detail.htmlCode?.downloadUrl) await download(detail.htmlCode.downloadUrl, path.join(archiveDir, 'code.html'));
|
||||
if (detail.screenshot?.downloadUrl) await download(detail.screenshot.downloadUrl, path.join(archiveDir, 'screen.png'));
|
||||
} catch (e) {
|
||||
console.warn(` skip download failed: ${title}`, e.message);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const num = String(++idx[category]).padStart(2, '0');
|
||||
const dir = path.join(PAGES, category, `${num}_${slug(title, screenId)}`);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
entry.dir = path.relative(ROOT, dir);
|
||||
|
||||
const metaPath = path.join(dir, 'meta.json');
|
||||
if (!FORCE && fs.existsSync(metaPath) && fs.existsSync(path.join(dir, 'code.html'))) {
|
||||
manifest.screens.push(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const detail = await mcpCall('get_screen', {
|
||||
name: `projects/${PROJECT_ID}/screens/${screenId}`,
|
||||
projectId: PROJECT_ID,
|
||||
screenId,
|
||||
});
|
||||
const meta = { title, screenId, category, syncedAt: new Date().toISOString() };
|
||||
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
||||
|
||||
if (detail.htmlCode?.downloadUrl) {
|
||||
const ok = await download(detail.htmlCode.downloadUrl, path.join(dir, 'code.html'));
|
||||
if (!ok) console.warn(` HTML download failed: ${title}`);
|
||||
}
|
||||
if (detail.screenshot?.downloadUrl) {
|
||||
await download(detail.screenshot.downloadUrl, path.join(dir, 'screen.png'));
|
||||
}
|
||||
console.log(` [${category}] ${title}`);
|
||||
} catch (e) {
|
||||
console.warn(` Failed: ${title}`, e.message);
|
||||
}
|
||||
manifest.screens.push(entry);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(PAGES, 'SCREEN_MAP.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
||||
console.log('\nDone.');
|
||||
console.log(` user: ${manifest.screens.filter((s) => s.category === 'user').length}`);
|
||||
console.log(` shop: ${manifest.screens.filter((s) => s.category === 'shop').length}`);
|
||||
console.log(` partner: ${manifest.screens.filter((s) => s.category === 'partner').length}`);
|
||||
console.log(` skip/archive: ${manifest.screens.filter((s) => s.category === 'skip').length}`);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user