magic-builder 1.0.0 → 1.1.1
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 +25 -0
- package/bin/magic-builder.js +3 -0
- package/package.json +18 -7
- package/src/commands/extract-cookie.js +85 -0
- package/src/commands/performance.js +264 -0
- package/src/index.js +3 -0
- package/src/lib/help.js +47 -0
- package/src/lib/multipart.js +6 -1
package/README.md
CHANGED
|
@@ -126,6 +126,31 @@ magic-builder link create --title "Weekly Report"
|
|
|
126
126
|
magic-builder link create --fid <faas-id>
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
Submit Markdown to a People performance draft (`perf` is an alias):
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
magic-builder performance --cookie ./cookie.txt --markdown ./performance.md
|
|
133
|
+
magic-builder perf --cookie "$PEOPLE_COOKIE" --markdown "# Performance summary"
|
|
134
|
+
magic-builder perf --cookie ./cookie.txt --markdown https://example.com/performance.md --dry-run
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Cookie input accepts a raw `Cookie` header, a local path, or a Netscape cookie file. Markdown input accepts literal content, a local path, an HTTP(S) URL, or stdin. The command extracts `x-f-csrf` from the Cookie and does not print the Cookie in its output.
|
|
138
|
+
|
|
139
|
+
When `--cookie` and `--cookie-file` are omitted, `performance` automatically reads `./cookie.txt`, matching the default output of `extract-cookie`:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
magic-builder extract-cookie --curl-file ./request.curl
|
|
143
|
+
magic-builder performance --markdown ./performance.md
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Extract Cookie from a copied curl command. This is a top-level general-purpose command. The default output is `./cookie.txt`; the file is created with mode `0600`:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
magic-builder extract-cookie --curl-file ./request.curl
|
|
150
|
+
magic-builder extract-cookie --curl "curl 'https://example.com/...' -b 'foo=bar; token=value'"
|
|
151
|
+
cat ./request.curl | magic-builder extract-cookie --out-dir ./secrets
|
|
152
|
+
```
|
|
153
|
+
|
|
129
154
|
Create or append a Feishu document HTML Box:
|
|
130
155
|
|
|
131
156
|
```bash
|
package/bin/magic-builder.js
CHANGED
|
@@ -12,6 +12,9 @@ const COMMANDS = {
|
|
|
12
12
|
link: require('../src/commands/link'),
|
|
13
13
|
doc: require('../src/commands/doc'),
|
|
14
14
|
feedback: require('../src/commands/feedback'),
|
|
15
|
+
performance: require('../src/commands/performance'),
|
|
16
|
+
perf: require('../src/commands/performance'),
|
|
17
|
+
'extract-cookie': require('../src/commands/extract-cookie'),
|
|
15
18
|
skill: require('../src/commands/skill'),
|
|
16
19
|
version: require('../src/commands/version'),
|
|
17
20
|
update: require('../src/commands/update'),
|
package/package.json
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magic-builder",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI for Magic Builder — publish pages, functions, and
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "CLI for Magic Builder — publish pages, functions, files, and performance drafts",
|
|
5
5
|
"bin": {
|
|
6
|
-
"magic-builder": "
|
|
7
|
-
"magic-cli": "
|
|
8
|
-
"miaobi": "
|
|
6
|
+
"magic-builder": "bin/magic-builder.js",
|
|
7
|
+
"magic-cli": "bin/magic-builder.js",
|
|
8
|
+
"miaobi": "bin/magic-builder.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "./src/index.js",
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=18.0.0"
|
|
13
13
|
},
|
|
14
14
|
"type": "commonjs",
|
|
15
|
-
"files": [
|
|
16
|
-
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/",
|
|
17
|
+
"src/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"magic-builder",
|
|
23
|
+
"miaobi",
|
|
24
|
+
"cli",
|
|
25
|
+
"faas",
|
|
26
|
+
"publish"
|
|
27
|
+
],
|
|
17
28
|
"license": "MIT",
|
|
18
29
|
"publishConfig": {
|
|
19
30
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs');
|
|
4
|
+
const { basename, dirname, resolve } = require('path');
|
|
5
|
+
const { success, fail } = require('../lib/output');
|
|
6
|
+
|
|
7
|
+
function run(args, opts) {
|
|
8
|
+
if (args.length) fail('Usage: magic-builder extract-cookie --curl <content|file> [--out <file>|--out-dir <dir>]', 'E_INVALID_ARGS');
|
|
9
|
+
const curl = readCurl(opts.curl, opts.curlFile);
|
|
10
|
+
if (!curl) fail('Missing curl content. Use --curl <content|file>, --curl-file <file>, or stdin.', 'E_INVALID_ARGS');
|
|
11
|
+
const cookie = extractCookieFromCurl(curl);
|
|
12
|
+
if (!cookie) fail('No Cookie found in curl. Expected -b/--cookie or a Cookie request header.', 'E_INVALID_ARGS');
|
|
13
|
+
|
|
14
|
+
const output = resolveOutput(opts.out, opts.outDir);
|
|
15
|
+
mkdirSync(dirname(output), { recursive: true });
|
|
16
|
+
writeFileSync(output, `${cookie}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
17
|
+
success({ path: output, filename: basename(output), cookieCount: cookie.split(';').filter(Boolean).length }, opts);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readCurl(value, file) {
|
|
21
|
+
const input = String(file || value || '').trim();
|
|
22
|
+
if (!input && !process.stdin.isTTY) return readFileSync(0, 'utf8');
|
|
23
|
+
if (!input) return '';
|
|
24
|
+
const path = resolve(input);
|
|
25
|
+
return file || existsSync(path) ? readFileSync(path, 'utf8') : input;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractCookieFromCurl(curl) {
|
|
29
|
+
const tokens = tokenizeShell(String(curl || '').replace(/\\\r?\n/g, ' '));
|
|
30
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
31
|
+
const token = tokens[i];
|
|
32
|
+
if (token === '-b' || token === '--cookie') return normalizeCookie(tokens[i + 1] || '');
|
|
33
|
+
if (token.startsWith('--cookie=')) return normalizeCookie(token.slice('--cookie='.length));
|
|
34
|
+
if (token === '-H' || token === '--header') {
|
|
35
|
+
const header = tokens[i + 1] || '';
|
|
36
|
+
if (/^cookie\s*:/i.test(header)) return normalizeCookie(header);
|
|
37
|
+
}
|
|
38
|
+
if (token.startsWith('--header=')) {
|
|
39
|
+
const header = token.slice('--header='.length);
|
|
40
|
+
if (/^cookie\s*:/i.test(header)) return normalizeCookie(header);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeCookie(raw) {
|
|
47
|
+
return String(raw || '').replace(/^cookie\s*:\s*/i, '').trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tokenizeShell(input) {
|
|
51
|
+
const tokens = [];
|
|
52
|
+
let token = '';
|
|
53
|
+
let quote = '';
|
|
54
|
+
let escaped = false;
|
|
55
|
+
for (const char of input) {
|
|
56
|
+
if (escaped) {
|
|
57
|
+
token += char;
|
|
58
|
+
escaped = false;
|
|
59
|
+
} else if (char === '\\' && quote !== "'") {
|
|
60
|
+
escaped = true;
|
|
61
|
+
} else if (quote) {
|
|
62
|
+
if (char === quote) quote = '';
|
|
63
|
+
else token += char;
|
|
64
|
+
} else if (char === "'" || char === '"') {
|
|
65
|
+
quote = char;
|
|
66
|
+
} else if (/\s/.test(char)) {
|
|
67
|
+
if (token) {
|
|
68
|
+
tokens.push(token);
|
|
69
|
+
token = '';
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
token += char;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (escaped) token += '\\';
|
|
76
|
+
if (token) tokens.push(token);
|
|
77
|
+
return tokens;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveOutput(out, outDir) {
|
|
81
|
+
if (out) return resolve(String(out));
|
|
82
|
+
return resolve(String(outDir || process.cwd()), 'cookie.txt');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { run, extractCookieFromCurl, resolveOutput };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { existsSync, readFileSync } = require('fs');
|
|
4
|
+
const { resolve } = require('path');
|
|
5
|
+
const { success, fail } = require('../lib/output');
|
|
6
|
+
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
endpoint: 'https://people.bytedance.net/perf/api/foundation/v2/draft',
|
|
9
|
+
reviewId: '7654473253011852917',
|
|
10
|
+
formId: '7654466592117358592',
|
|
11
|
+
templateId: '7654466592117358609',
|
|
12
|
+
unitId: '7654466592117358775',
|
|
13
|
+
fieldId: '7654466592117358799',
|
|
14
|
+
textFieldId: '7654466592117358824',
|
|
15
|
+
operatorId: '6687792427639817740',
|
|
16
|
+
sourceId: '7784085065561419062',
|
|
17
|
+
fieldSourceId: '7662587009851985528',
|
|
18
|
+
rootReviewId: '7657808756893306935',
|
|
19
|
+
version: 5,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
async function run(args, opts) {
|
|
23
|
+
if (args.length) fail('Usage: magic-builder performance --cookie <content|file> --markdown <content|file|url>', 'E_INVALID_ARGS');
|
|
24
|
+
|
|
25
|
+
const cookie = readCookie(opts.cookie, opts.cookieFile);
|
|
26
|
+
if (!cookie) fail('Missing Cookie. Use --cookie <content|file> or --cookie-file <file>.', 'E_INVALID_ARGS');
|
|
27
|
+
|
|
28
|
+
const markdown = await readMarkdown(opts.markdown, opts.markdownFile);
|
|
29
|
+
if (!markdown.trim()) fail('Missing Markdown. Use --markdown <content|file|url>, --markdown-file <file>, or stdin.', 'E_INVALID_ARGS');
|
|
30
|
+
|
|
31
|
+
const csrf = getCookie(cookie, 'x-f-csrf');
|
|
32
|
+
if (!csrf) fail('Cookie does not contain x-f-csrf.', 'E_INVALID_ARGS');
|
|
33
|
+
|
|
34
|
+
const config = resolveConfig(opts);
|
|
35
|
+
const payload = buildPayload(markdown, config);
|
|
36
|
+
if (opts.dryRun) {
|
|
37
|
+
success({ dryRun: true, endpoint: config.endpoint, payload }, opts);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await fetch(config.endpoint, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
accept: 'application/json, text/plain, */*',
|
|
45
|
+
'accept-language': 'zh-CN,zh;q=0.9',
|
|
46
|
+
'content-type': 'application/json;charset=UTF-8',
|
|
47
|
+
cookie,
|
|
48
|
+
origin: 'https://people.bytedance.net',
|
|
49
|
+
referer: config.referer,
|
|
50
|
+
'rpc-persist-lane-c-perfx-tenant-id': config.tenantId,
|
|
51
|
+
'x-f-csrf': csrf,
|
|
52
|
+
'x-f-lang': 'zh-CN',
|
|
53
|
+
'x-f-timezone': 'Asia/Shanghai',
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify(payload),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const text = await response.text();
|
|
59
|
+
const data = parseJson(text);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const error = new Error(data?.message || data?.msg || `Performance request failed: HTTP ${response.status}`);
|
|
62
|
+
error.code = response.status === 401 || response.status === 403 ? 'E_AUTH_FAILED' : 'E_REQUEST_FAILED';
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
success(data === null ? text : data, opts);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readCookie(value, file) {
|
|
69
|
+
const defaultFile = resolve(process.cwd(), 'cookie.txt');
|
|
70
|
+
const input = String(file || value || (existsSync(defaultFile) ? defaultFile : '')).trim();
|
|
71
|
+
if (!input) return '';
|
|
72
|
+
const path = resolve(input);
|
|
73
|
+
const raw = file || existsSync(path) ? readFileSync(path, 'utf8') : input;
|
|
74
|
+
return normalizeCookie(raw);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function readMarkdown(value, file) {
|
|
78
|
+
const input = String(file || value || '').trim();
|
|
79
|
+
if (!input && !process.stdin.isTTY) return readFileSync(0, 'utf8');
|
|
80
|
+
if (!input) return '';
|
|
81
|
+
if (!file && /^https?:\/\//i.test(input)) {
|
|
82
|
+
const response = await fetch(input);
|
|
83
|
+
if (!response.ok) throw new Error(`Unable to read Markdown URL: HTTP ${response.status}`);
|
|
84
|
+
return response.text();
|
|
85
|
+
}
|
|
86
|
+
const path = resolve(input);
|
|
87
|
+
return file || existsSync(path) ? readFileSync(path, 'utf8') : input;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeCookie(raw) {
|
|
91
|
+
const text = String(raw || '').trim();
|
|
92
|
+
const cookieHeader = text.match(/^cookie:\s*(.+)$/im);
|
|
93
|
+
if (cookieHeader) return cookieHeader[1].trim();
|
|
94
|
+
if (!text.includes('\n')) return text;
|
|
95
|
+
|
|
96
|
+
const netscape = text.split(/\r?\n/)
|
|
97
|
+
.filter(line => line && !line.startsWith('#'))
|
|
98
|
+
.map(line => line.split('\t'))
|
|
99
|
+
.filter(parts => parts.length >= 7)
|
|
100
|
+
.map(parts => `${parts[5]}=${parts[6]}`);
|
|
101
|
+
if (netscape.length) return netscape.join('; ');
|
|
102
|
+
|
|
103
|
+
return text.split(/\r?\n/).map(line => line.trim()).filter(Boolean).join(' ');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getCookie(cookie, name) {
|
|
107
|
+
for (const part of cookie.split(';')) {
|
|
108
|
+
const index = part.indexOf('=');
|
|
109
|
+
if (index < 0) continue;
|
|
110
|
+
if (part.slice(0, index).trim().toLowerCase() === name.toLowerCase()) return part.slice(index + 1).trim();
|
|
111
|
+
}
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveConfig(opts) {
|
|
116
|
+
const reviewId = String(opts.reviewId || DEFAULTS.reviewId);
|
|
117
|
+
const formId = String(opts.formId || DEFAULTS.formId);
|
|
118
|
+
const templateId = String(opts.templateId || DEFAULTS.templateId);
|
|
119
|
+
const tenantId = String(opts.tenantId || getCookie(String(opts.cookie || ''), 'tenant_id') || '6685321562717324807');
|
|
120
|
+
return {
|
|
121
|
+
endpoint: String(opts.endpoint || DEFAULTS.endpoint),
|
|
122
|
+
reviewId,
|
|
123
|
+
formId,
|
|
124
|
+
templateId,
|
|
125
|
+
unitId: String(opts.unitId || DEFAULTS.unitId),
|
|
126
|
+
fieldId: String(opts.fieldId || DEFAULTS.fieldId),
|
|
127
|
+
textFieldId: String(opts.textFieldId || DEFAULTS.textFieldId),
|
|
128
|
+
operatorId: String(opts.operatorId || DEFAULTS.operatorId),
|
|
129
|
+
sourceId: String(opts.sourceId || DEFAULTS.sourceId),
|
|
130
|
+
fieldSourceId: String(opts.fieldSourceId || DEFAULTS.fieldSourceId),
|
|
131
|
+
rootReviewId: String(opts.rootReviewId || DEFAULTS.rootReviewId),
|
|
132
|
+
version: Number(opts.version || DEFAULTS.version),
|
|
133
|
+
tenantId,
|
|
134
|
+
referer: String(opts.referer || `https://people.bytedance.net/performance/perf/review/${reviewId}/${formId}?mode=editable`),
|
|
135
|
+
uid: String(opts.uid || `magic-builder-${Date.now()}`),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildPayload(markdown, config) {
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const sections = splitMarkdownSections(markdown);
|
|
142
|
+
const subUnits = sections.map((section, index) => {
|
|
143
|
+
const value = { '0': { ops: markdownToDelta(section), zoneId: '0', zoneType: 'Z' } };
|
|
144
|
+
if (index > 0) {
|
|
145
|
+
return [{
|
|
146
|
+
key: `${index - 1}-${now + index}-${Math.random()}`,
|
|
147
|
+
id: config.textFieldId,
|
|
148
|
+
entityType: 'text',
|
|
149
|
+
value,
|
|
150
|
+
}];
|
|
151
|
+
}
|
|
152
|
+
return [{
|
|
153
|
+
id: config.textFieldId,
|
|
154
|
+
source_id: config.sourceId,
|
|
155
|
+
value,
|
|
156
|
+
json_value: {},
|
|
157
|
+
created_time: now,
|
|
158
|
+
updated_time: now,
|
|
159
|
+
entityType: 'text',
|
|
160
|
+
}];
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
key: `${config.reviewId}__confirm_invitation__${config.operatorId}__${config.formId}`,
|
|
164
|
+
data: {
|
|
165
|
+
keyWorks: {
|
|
166
|
+
valueSetting: [{ operatorId: config.operatorId, source_id: config.sourceId, isReviewee: true }],
|
|
167
|
+
currentUserId: config.operatorId,
|
|
168
|
+
units: [{
|
|
169
|
+
id: config.unitId,
|
|
170
|
+
fields: [{
|
|
171
|
+
id: config.fieldId,
|
|
172
|
+
entityType: 'multiple_texts',
|
|
173
|
+
source_id: config.fieldSourceId,
|
|
174
|
+
value: '',
|
|
175
|
+
created_time: now,
|
|
176
|
+
updated_time: now,
|
|
177
|
+
extra: null,
|
|
178
|
+
json_value: {},
|
|
179
|
+
sub_units: subUnits,
|
|
180
|
+
}],
|
|
181
|
+
type: 'object',
|
|
182
|
+
unknown: false,
|
|
183
|
+
}],
|
|
184
|
+
templateType: 'write',
|
|
185
|
+
template_id: config.templateId,
|
|
186
|
+
},
|
|
187
|
+
rootReviewId: config.rootReviewId,
|
|
188
|
+
uid: config.uid,
|
|
189
|
+
},
|
|
190
|
+
version: config.version,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function splitMarkdownSections(markdown) {
|
|
195
|
+
const text = String(markdown || '').replace(/\r\n?/g, '\n').trim();
|
|
196
|
+
if (!text) return [''];
|
|
197
|
+
const lines = text.split('\n');
|
|
198
|
+
const sections = [];
|
|
199
|
+
let current = [];
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
if (/^##\s+/.test(line)) {
|
|
202
|
+
if (current.some(item => item.trim()) && !current.every(item => /^#\s+/.test(item) || !item.trim())) {
|
|
203
|
+
sections.push(current.join('\n').trim());
|
|
204
|
+
}
|
|
205
|
+
current = [`# ${line.replace(/^##\s+/, '')}`];
|
|
206
|
+
} else if (current.length || !/^#\s+/.test(line)) {
|
|
207
|
+
current.push(line);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (current.some(item => item.trim())) sections.push(current.join('\n').trim());
|
|
211
|
+
return sections.length ? sections : [text];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function markdownToDelta(markdown) {
|
|
215
|
+
const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
|
|
216
|
+
const ops = [];
|
|
217
|
+
let inFence = false;
|
|
218
|
+
for (const line of lines) {
|
|
219
|
+
if (/^\s*```/.test(line)) {
|
|
220
|
+
inFence = !inFence;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (inFence) {
|
|
224
|
+
appendInline(ops, line);
|
|
225
|
+
ops.push({ insert: '\n', attributes: { 'code-block': true } });
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
229
|
+
const unordered = line.match(/^\s*[-*+]\s+(.+)$/);
|
|
230
|
+
const ordered = line.match(/^\s*\d+[.)]\s+(.+)$/);
|
|
231
|
+
const quote = line.match(/^\s*>\s?(.*)$/);
|
|
232
|
+
const content = heading?.[2] ?? unordered?.[1] ?? ordered?.[1] ?? quote?.[1] ?? line;
|
|
233
|
+
appendInline(ops, content);
|
|
234
|
+
const attributes = heading ? { header: heading[1].length }
|
|
235
|
+
: unordered ? { list: 'bullet' }
|
|
236
|
+
: ordered ? { list: 'ordered' }
|
|
237
|
+
: quote ? { blockquote: true }
|
|
238
|
+
: undefined;
|
|
239
|
+
ops.push(attributes ? { insert: '\n', attributes } : { insert: '\n' });
|
|
240
|
+
}
|
|
241
|
+
if (ops.length > 1 && lines.at(-1) === '') ops.pop();
|
|
242
|
+
return ops.length ? ops : [{ insert: '\n' }];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function appendInline(ops, text) {
|
|
246
|
+
const pattern = /(\*\*|__)(.+?)\1|(?<!\*)\*([^*\n]+)\*|(?<!_)_([^_\n]+)_|`([^`\n]+)`|\[([^\]]+)]\((https?:\/\/[^\s)]+)\)/g;
|
|
247
|
+
let cursor = 0;
|
|
248
|
+
let match;
|
|
249
|
+
while ((match = pattern.exec(text))) {
|
|
250
|
+
if (match.index > cursor) ops.push({ insert: text.slice(cursor, match.index) });
|
|
251
|
+
if (match[2]) ops.push({ insert: match[2], attributes: { bold: true } });
|
|
252
|
+
else if (match[3] || match[4]) ops.push({ insert: match[3] || match[4], attributes: { italic: true } });
|
|
253
|
+
else if (match[5]) ops.push({ insert: match[5], attributes: { code: true } });
|
|
254
|
+
else ops.push({ insert: match[6], attributes: { link: match[7] } });
|
|
255
|
+
cursor = pattern.lastIndex;
|
|
256
|
+
}
|
|
257
|
+
if (cursor < text.length) ops.push({ insert: text.slice(cursor) });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseJson(text) {
|
|
261
|
+
try { return text ? JSON.parse(text) : {}; } catch (_) { return null; }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = { run, readCookie, normalizeCookie, getCookie, splitMarkdownSections, markdownToDelta, buildPayload };
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,9 @@ module.exports = {
|
|
|
13
13
|
link: require('./commands/link'),
|
|
14
14
|
doc: require('./commands/doc'),
|
|
15
15
|
feedback: require('./commands/feedback'),
|
|
16
|
+
performance: require('./commands/performance'),
|
|
17
|
+
perf: require('./commands/performance'),
|
|
18
|
+
extractCookie: require('./commands/extract-cookie'),
|
|
16
19
|
skill: require('./commands/skill'),
|
|
17
20
|
version: require('./commands/version'),
|
|
18
21
|
update: require('./commands/update'),
|
package/src/lib/help.js
CHANGED
|
@@ -21,6 +21,8 @@ COMMANDS:
|
|
|
21
21
|
link Generate Magic share links
|
|
22
22
|
doc Create or append Feishu Doc HTML Box apps
|
|
23
23
|
feedback Create Magic feedback records
|
|
24
|
+
performance Submit Markdown content to a People performance draft (alias: perf)
|
|
25
|
+
extract-cookie Extract Cookie data from a curl command
|
|
24
26
|
skill Check or update the Magic Builder skill package
|
|
25
27
|
version Show CLI version
|
|
26
28
|
update Update this CLI package
|
|
@@ -45,6 +47,8 @@ EXAMPLES:
|
|
|
45
47
|
magic-builder link create --title "Weekly Report"
|
|
46
48
|
magic-builder doc create --html app.html --title "Demo"
|
|
47
49
|
magic-builder feedback create --feedback "打不开页面"
|
|
50
|
+
magic-builder performance --cookie ./cookie.txt --markdown ./performance.md
|
|
51
|
+
magic-builder extract-cookie --curl-file ./request.curl
|
|
48
52
|
magic-builder skill install
|
|
49
53
|
magic-builder skill check-update
|
|
50
54
|
magic-builder version
|
|
@@ -103,6 +107,11 @@ COMMANDS:
|
|
|
103
107
|
feedback create --feedback <text> [--title <title>] [--summary <text>]
|
|
104
108
|
feedback create --feedback-file <file> [--dry-run]
|
|
105
109
|
|
|
110
|
+
performance --cookie <content|file> --markdown <content|file|url> [--dry-run]
|
|
111
|
+
perf --cookie-file <file> --markdown-file <file> [--dry-run]
|
|
112
|
+
|
|
113
|
+
extract-cookie --curl <content|file> [--out <file>|--out-dir <dir>]
|
|
114
|
+
|
|
106
115
|
skill install [--environment auto|local|cloud] [--skills-root <dir>]
|
|
107
116
|
skill check-update [--environment auto|local|cloud] [--skills-root <dir>]
|
|
108
117
|
skill update [--environment auto|local|cloud] [--skills-root <dir>]
|
|
@@ -184,6 +193,44 @@ SYNTAX:
|
|
|
184
193
|
SYNTAX:
|
|
185
194
|
magic-builder feedback create --feedback <text>
|
|
186
195
|
magic-builder feedback create --feedback-file <file>
|
|
196
|
+
`,
|
|
197
|
+
performance: `@HELP magic-builder/performance
|
|
198
|
+
|
|
199
|
+
SYNTAX:
|
|
200
|
+
magic-builder performance --cookie <content|file> --markdown <content|file|url>
|
|
201
|
+
magic-builder perf --cookie-file <file> --markdown-file <file>
|
|
202
|
+
|
|
203
|
+
COOKIE:
|
|
204
|
+
When --cookie and --cookie-file are omitted, ./cookie.txt is used.
|
|
205
|
+
|
|
206
|
+
OPTIONS:
|
|
207
|
+
--dry-run Print the generated payload without sending it
|
|
208
|
+
--review-id <id> Override the performance review id
|
|
209
|
+
--form-id <id> Override the invitation/form id used by the draft key
|
|
210
|
+
--template-id <id> Override the review template id
|
|
211
|
+
--root-review-id <id> Override the root review id
|
|
212
|
+
--operator-id <id> Override the current user/operator id
|
|
213
|
+
--source-id <id> Override the form source id
|
|
214
|
+
--field-source-id <id> Override the writable field source id
|
|
215
|
+
--tenant-id <id> Override the People tenant id
|
|
216
|
+
--endpoint <url> Override the draft API endpoint
|
|
217
|
+
`,
|
|
218
|
+
perf: `@HELP magic-builder/performance
|
|
219
|
+
|
|
220
|
+
Alias of "magic-builder performance".
|
|
221
|
+
`,
|
|
222
|
+
'extract-cookie': `@HELP magic-builder/extract-cookie
|
|
223
|
+
|
|
224
|
+
SYNTAX:
|
|
225
|
+
magic-builder extract-cookie --curl <content|file> [--out <file>]
|
|
226
|
+
magic-builder extract-cookie --curl-file <file> [--out-dir <dir>]
|
|
227
|
+
cat request.curl | magic-builder extract-cookie [--out <file>]
|
|
228
|
+
|
|
229
|
+
OPTIONS:
|
|
230
|
+
--curl <content|file> curl command text or a local curl file
|
|
231
|
+
--curl-file <file> Read curl command from a local file
|
|
232
|
+
--out <file> Output file (default: ./cookie.txt)
|
|
233
|
+
--out-dir <dir> Output directory; created when missing
|
|
187
234
|
`,
|
|
188
235
|
skill: `@HELP magic-builder/skill
|
|
189
236
|
|
package/src/lib/multipart.js
CHANGED
|
@@ -26,13 +26,18 @@ async function uploadSingle(filePath, opts = {}) {
|
|
|
26
26
|
|
|
27
27
|
if (signRes.code !== 0) throw new Error(signRes.msg || 'Sign failed');
|
|
28
28
|
const { signed_url, url } = signRes.data;
|
|
29
|
+
const uploadHeaders = signRes.data.upload_headers || {};
|
|
29
30
|
|
|
30
31
|
const buf = fs.readFileSync(filePath);
|
|
31
32
|
if (!quiet) process.stderr.write('Uploading... ');
|
|
32
33
|
|
|
33
34
|
await request(signed_url, {
|
|
34
35
|
method: 'PUT',
|
|
35
|
-
headers: {
|
|
36
|
+
headers: {
|
|
37
|
+
...uploadHeaders,
|
|
38
|
+
'Content-Type': uploadHeaders['Content-Type'] || mime,
|
|
39
|
+
'Content-Disposition': uploadHeaders['Content-Disposition'] || 'inline',
|
|
40
|
+
},
|
|
36
41
|
body: buf,
|
|
37
42
|
timeout: 120000,
|
|
38
43
|
});
|