@lark-apaas/fullstack-cli 1.1.9 → 1.1.10

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.
@@ -1,25 +0,0 @@
1
- import { readFileTailLines } from './tail.js';
2
- import { readStdLinesTailFromLastMarker, stripPrefixFromStdLine } from './std-utils.js';
3
- export function readServerStdSegment(filePath, maxLines) {
4
- const marker = (line) => {
5
- if (!line)
6
- return false;
7
- if (/\bdev:server\b/.test(line))
8
- return true;
9
- if (line.includes('Starting compilation in watch mode'))
10
- return true;
11
- if (line.includes('File change detected. Starting incremental compilation'))
12
- return true;
13
- if (line.includes('Starting Nest application'))
14
- return true;
15
- if (line.includes('Nest application successfully started'))
16
- return true;
17
- return false;
18
- };
19
- const segment = readStdLinesTailFromLastMarker(filePath, maxLines, marker);
20
- if (segment.markerFound) {
21
- return segment.lines;
22
- }
23
- const lines = readFileTailLines(filePath, maxLines);
24
- return lines.map(stripPrefixFromStdLine);
25
- }
@@ -1,5 +0,0 @@
1
- export declare function stripPrefixFromStdLine(line: string): string;
2
- export declare function readStdLinesTailFromLastMarker(filePath: string, maxLines: number, isMarker: (line: string) => boolean): {
3
- lines: string[];
4
- markerFound: boolean;
5
- };
@@ -1,61 +0,0 @@
1
- import fs from 'node:fs';
2
- export function stripPrefixFromStdLine(line) {
3
- const match = line.match(/^(\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(server|client)\] )(.*)$/);
4
- if (!match) {
5
- return line;
6
- }
7
- return match[4] || '';
8
- }
9
- export function readStdLinesTailFromLastMarker(filePath, maxLines, isMarker) {
10
- const stat = fs.statSync(filePath);
11
- if (stat.size === 0) {
12
- return { lines: [], markerFound: false };
13
- }
14
- const fd = fs.openSync(filePath, 'r');
15
- const chunkSize = 64 * 1024;
16
- let position = stat.size;
17
- let remainder = '';
18
- let markerFound = false;
19
- let finished = false;
20
- const collected = [];
21
- try {
22
- while (position > 0 && !finished) {
23
- const length = Math.min(chunkSize, position);
24
- position -= length;
25
- const buffer = Buffer.alloc(length);
26
- fs.readSync(fd, buffer, 0, length, position);
27
- let chunk = buffer.toString('utf8');
28
- if (remainder) {
29
- chunk += remainder;
30
- remainder = '';
31
- }
32
- const parts = chunk.split('\n');
33
- remainder = parts.shift() ?? '';
34
- for (let i = parts.length - 1; i >= 0; i -= 1) {
35
- const rawLine = parts[i];
36
- const line = stripPrefixFromStdLine(rawLine);
37
- if (collected.length < maxLines) {
38
- collected.push(line);
39
- }
40
- if (isMarker(line)) {
41
- markerFound = true;
42
- finished = true;
43
- break;
44
- }
45
- }
46
- }
47
- if (!finished && remainder) {
48
- const line = stripPrefixFromStdLine(remainder);
49
- if (collected.length < maxLines) {
50
- collected.push(line);
51
- }
52
- if (isMarker(line)) {
53
- markerFound = true;
54
- }
55
- }
56
- }
57
- finally {
58
- fs.closeSync(fd);
59
- }
60
- return { lines: collected.reverse(), markerFound };
61
- }
@@ -1,2 +0,0 @@
1
- export declare function fileExists(filePath: string): boolean;
2
- export declare function readFileTailLines(filePath: string, maxLines: number): string[];
@@ -1,47 +0,0 @@
1
- import fs from 'node:fs';
2
- export function fileExists(filePath) {
3
- try {
4
- fs.accessSync(filePath, fs.constants.F_OK | fs.constants.R_OK);
5
- return true;
6
- }
7
- catch {
8
- return false;
9
- }
10
- }
11
- export function readFileTailLines(filePath, maxLines) {
12
- const stat = fs.statSync(filePath);
13
- if (stat.size === 0) {
14
- return [];
15
- }
16
- const fd = fs.openSync(filePath, 'r');
17
- const chunkSize = 64 * 1024;
18
- const chunks = [];
19
- let position = stat.size;
20
- let collectedLines = 0;
21
- try {
22
- while (position > 0 && collectedLines <= maxLines) {
23
- const length = Math.min(chunkSize, position);
24
- position -= length;
25
- const buffer = Buffer.alloc(length);
26
- fs.readSync(fd, buffer, 0, length, position);
27
- chunks.unshift(buffer.toString('utf8'));
28
- const chunkLines = buffer.toString('utf8').split('\n').length - 1;
29
- collectedLines += chunkLines;
30
- }
31
- }
32
- finally {
33
- fs.closeSync(fd);
34
- }
35
- const content = chunks.join('');
36
- const allLines = content.split('\n');
37
- if (allLines.length === 0) {
38
- return [];
39
- }
40
- if (allLines[allLines.length - 1] === '') {
41
- allLines.pop();
42
- }
43
- if (allLines.length <= maxLines) {
44
- return allLines;
45
- }
46
- return allLines.slice(allLines.length - maxLines);
47
- }
@@ -1,16 +0,0 @@
1
- type LogType = 'server' | 'trace' | 'server-std' | 'client-std' | 'browser';
2
- export interface ReadLogsJsonResult {
3
- hasError: boolean;
4
- message: string;
5
- logs?: unknown[];
6
- }
7
- interface ReadLogsOptions {
8
- logDir: string;
9
- type: LogType;
10
- maxLines?: number;
11
- traceId?: string;
12
- }
13
- export declare function readLatestLogLines(options: ReadLogsOptions): Promise<string[]>;
14
- export declare function readLogsJsonResult(options: ReadLogsOptions): Promise<ReadLogsJsonResult>;
15
- export declare function run(options: ReadLogsOptions): Promise<void>;
16
- export {};
@@ -1,153 +0,0 @@
1
- import path from 'node:path';
2
- import { readServerStdSegment } from './read-logs/server-std.js';
3
- import { readClientStdSegment } from './read-logs/client-std.js';
4
- import { readJsonLinesByTraceId, readJsonLinesLastPid, readJsonLinesTail } from './read-logs/json-lines.js';
5
- import { fileExists, readFileTailLines } from './read-logs/tail.js';
6
- function hasErrorInStdLines(lines) {
7
- const combined = lines.join('\n');
8
- if (!combined)
9
- return false;
10
- const strong = [
11
- /compiled with errors/i,
12
- /failed to compile/i,
13
- /error: \w+/i,
14
- /uncaught/i,
15
- /unhandled/i,
16
- /eaddrinuse/i,
17
- /cannot find module/i,
18
- /module not found/i,
19
- /ts\d{3,5}:/i,
20
- ];
21
- if (strong.some((re) => re.test(combined)))
22
- return true;
23
- const weakLine = /\b(error|fatal|exception)\b/i;
24
- const ignorePatterns = [
25
- /\b0\s+errors?\b/i,
26
- /Server Error \d{3}/i,
27
- ];
28
- return lines.some((line) => {
29
- const text = line.trim();
30
- if (!text)
31
- return false;
32
- if (ignorePatterns.some((re) => re.test(text)))
33
- return false;
34
- return weakLine.test(text);
35
- });
36
- }
37
- function hasErrorInLogObject(value) {
38
- if (!value || typeof value !== 'object')
39
- return false;
40
- const obj = value;
41
- const level = typeof obj.level === 'string' ? obj.level.toLowerCase() : '';
42
- if (level === 'error' || level === 'fatal')
43
- return true;
44
- if (level === 'err')
45
- return true;
46
- if (level === 'warn' && typeof obj.stack === 'string' && obj.stack.length > 0)
47
- return true;
48
- if (typeof obj.stack === 'string' && obj.stack.length > 0)
49
- return true;
50
- const statusCode = obj.statusCode;
51
- const status_code = obj.status_code;
52
- const meta = obj.meta;
53
- const metaObj = meta && typeof meta === 'object' ? meta : null;
54
- const metaStatusCode = metaObj?.statusCode;
55
- const metaStatus_code = metaObj?.status_code;
56
- const candidates = [statusCode, status_code, metaStatusCode, metaStatus_code];
57
- for (const candidate of candidates) {
58
- if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate >= 400) {
59
- return true;
60
- }
61
- }
62
- const message = typeof obj.message === 'string' ? obj.message : '';
63
- if (message && hasErrorInStdLines([message]))
64
- return true;
65
- return false;
66
- }
67
- export async function readLatestLogLines(options) {
68
- const maxLines = options.maxLines ?? 200;
69
- const filePath = resolveLogFilePath(options.logDir, options.type);
70
- if (!fileExists(filePath)) {
71
- throw new Error(`Log file not found: ${filePath}`);
72
- }
73
- if (options.type === 'server-std') {
74
- return readServerStdSegment(filePath, maxLines);
75
- }
76
- if (options.type === 'client-std') {
77
- return readClientStdSegment(filePath, maxLines);
78
- }
79
- const traceId = typeof options.traceId === 'string' ? options.traceId.trim() : '';
80
- if (traceId) {
81
- return readJsonLinesByTraceId(filePath, traceId, maxLines);
82
- }
83
- let lines = readFileTailLines(filePath, maxLines * 5);
84
- if (options.type === 'server' || options.type === 'trace') {
85
- lines = readJsonLinesLastPid(filePath, maxLines);
86
- }
87
- else if (options.type === 'browser') {
88
- lines = readJsonLinesTail(lines, maxLines);
89
- }
90
- return lines;
91
- }
92
- export async function readLogsJsonResult(options) {
93
- const lines = await readLatestLogLines(options);
94
- if (options.type === 'server-std' || options.type === 'client-std') {
95
- return {
96
- hasError: hasErrorInStdLines(lines),
97
- message: lines.join('\n'),
98
- };
99
- }
100
- const logs = [];
101
- let hasError = false;
102
- for (const line of lines) {
103
- if (!hasError && hasErrorInStdLines([line])) {
104
- hasError = true;
105
- }
106
- try {
107
- const parsed = JSON.parse(line);
108
- logs.push(parsed);
109
- if (!hasError && hasErrorInLogObject(parsed)) {
110
- hasError = true;
111
- }
112
- }
113
- catch {
114
- continue;
115
- }
116
- }
117
- return {
118
- hasError,
119
- message: '',
120
- logs,
121
- };
122
- }
123
- function resolveLogFilePath(logDir, type) {
124
- const base = path.isAbsolute(logDir) ? logDir : path.join(process.cwd(), logDir);
125
- if (type === 'server') {
126
- return path.join(base, 'server.log');
127
- }
128
- if (type === 'trace') {
129
- return path.join(base, 'trace.log');
130
- }
131
- if (type === 'server-std') {
132
- return path.join(base, 'server.std.log');
133
- }
134
- if (type === 'client-std') {
135
- return path.join(base, 'client.std.log');
136
- }
137
- if (type === 'browser') {
138
- return path.join(base, 'browser.log');
139
- }
140
- throw new Error(`Unsupported log type: ${type}`);
141
- }
142
- export async function run(options) {
143
- try {
144
- const result = await readLogsJsonResult(options);
145
- process.stdout.write(JSON.stringify(result) + '\n');
146
- }
147
- catch (error) {
148
- const message = error instanceof Error ? error.message : String(error);
149
- const result = { hasError: true, message };
150
- process.stdout.write(JSON.stringify(result) + '\n');
151
- process.exitCode = 1;
152
- }
153
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,199 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
- import fs from 'node:fs';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { readLatestLogLines, readLogsJsonResult } from './read-logs';
6
- async function writeFile(dir, fileName, content) {
7
- await fs.promises.mkdir(dir, { recursive: true });
8
- await fs.promises.writeFile(path.join(dir, fileName), content, 'utf8');
9
- }
10
- describe('readLatestLogLines', () => {
11
- let tmpDir;
12
- beforeEach(async () => {
13
- tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'fullstack-cli-logs-'));
14
- });
15
- afterEach(async () => {
16
- await fs.promises.rm(tmpDir, { recursive: true, force: true });
17
- });
18
- it('returns only the last server-std segment', async () => {
19
- const content = [
20
- '[2026-01-05 10:00:00] [server] > app@1.0.0 dev:server',
21
- '[2026-01-05 10:00:01] [server] booting',
22
- '[2026-01-05 10:01:00] [server] > app@1.0.0 dev:server',
23
- '[2026-01-05 10:01:01] [server] started',
24
- '[2026-01-05 10:01:02] [server] ready',
25
- ].join('\n');
26
- await writeFile(tmpDir, 'server.std.log', content);
27
- const lines = await readLatestLogLines({
28
- logDir: tmpDir,
29
- type: 'server-std',
30
- maxLines: 200,
31
- });
32
- expect(lines).toEqual(['> app@1.0.0 dev:server', 'started', 'ready']);
33
- });
34
- it('returns only the last server-std segment by latest marker', async () => {
35
- const content = [
36
- '[2026-01-05 10:00:00] [server] > app@1.0.0 dev:server',
37
- '[2026-01-05 10:00:01] [server] [10:00:01] Starting compilation in watch mode...',
38
- '[2026-01-05 10:00:02] [server] other',
39
- '[2026-01-05 10:00:03] [server] [10:00:03] File change detected. Starting incremental compilation...',
40
- '[2026-01-05 10:00:04] [server] other2',
41
- '[2026-01-05 10:00:05] [server] [Nest] 1 - 2026/01/05 10:00:05 LOG [NestFactory] Starting Nest application...',
42
- '[2026-01-05 10:00:06] [server] after-old',
43
- '[2026-01-05 10:01:00] [server] [10:01:00] File change detected. Starting incremental compilation...',
44
- '[2026-01-05 10:01:01] [server] tail-1',
45
- '[2026-01-05 10:01:02] [server] tail-2',
46
- ].join('\n');
47
- await writeFile(tmpDir, 'server.std.log', content);
48
- const lines = await readLatestLogLines({
49
- logDir: tmpDir,
50
- type: 'server-std',
51
- maxLines: 200,
52
- });
53
- expect(lines).toEqual([
54
- '[10:01:00] File change detected. Starting incremental compilation...',
55
- 'tail-1',
56
- 'tail-2',
57
- ]);
58
- });
59
- it('returns only the last client-std segment by hot reload markers', async () => {
60
- const content = [
61
- '[2026-01-05 10:00:00] [client] > app@1.0.0 dev:client',
62
- '[2026-01-05 10:00:05] [client] compiled successfully',
63
- '[2026-01-05 10:00:06] [client] initial done',
64
- '[2026-01-05 10:02:00] [client] compiled successfully',
65
- '[2026-01-05 10:02:01] [client] hmr update',
66
- '[2026-01-05 10:02:02] [client] updated',
67
- ].join('\n');
68
- await writeFile(tmpDir, 'client.std.log', content);
69
- const lines = await readLatestLogLines({
70
- logDir: tmpDir,
71
- type: 'client-std',
72
- maxLines: 200,
73
- });
74
- expect(lines).toEqual(['compiled successfully', 'hmr update', 'updated']);
75
- });
76
- it('returns only the last server pid segment for server.log', async () => {
77
- const content = [
78
- JSON.stringify({ pid: 100, time: '2026-01-05T10:00:00.000Z', level: 'INFO', message: 'old' }),
79
- JSON.stringify({ pid: 100, time: '2026-01-05T10:00:01.000Z', level: 'INFO', message: 'old2' }),
80
- JSON.stringify({ pid: 200, time: '2026-01-05T11:00:00.000Z', level: 'INFO', message: 'new' }),
81
- JSON.stringify({ pid: 200, time: '2026-01-05T11:00:01.000Z', level: 'INFO', message: 'new2' }),
82
- ].join('\n');
83
- await writeFile(tmpDir, 'server.log', content);
84
- const lines = await readLatestLogLines({
85
- logDir: tmpDir,
86
- type: 'server',
87
- maxLines: 200,
88
- });
89
- expect(lines).toEqual([
90
- JSON.stringify({ pid: 200, time: '2026-01-05T11:00:00.000Z', level: 'INFO', message: 'new' }),
91
- JSON.stringify({ pid: 200, time: '2026-01-05T11:00:01.000Z', level: 'INFO', message: 'new2' }),
92
- ]);
93
- });
94
- it('filters server.log by traceId when provided', async () => {
95
- const content = [
96
- JSON.stringify({ pid: 200, trace_id: 't1', level: 'INFO', message: 'a' }),
97
- JSON.stringify({ pid: 200, trace_id: 't2', level: 'INFO', message: 'b' }),
98
- JSON.stringify({ pid: 200, trace_id: 't2', level: 'INFO', message: 'c' }),
99
- JSON.stringify({ pid: 200, trace_id: 't3', level: 'INFO', message: 'd' }),
100
- ].join('\n');
101
- await writeFile(tmpDir, 'server.log', content);
102
- const result = await readLogsJsonResult({
103
- logDir: tmpDir,
104
- type: 'server',
105
- maxLines: 200,
106
- traceId: 't2',
107
- });
108
- expect(result).toEqual({
109
- hasError: false,
110
- message: '',
111
- logs: [
112
- { pid: 200, trace_id: 't2', level: 'INFO', message: 'b' },
113
- { pid: 200, trace_id: 't2', level: 'INFO', message: 'c' },
114
- ],
115
- });
116
- });
117
- it('filters browser.log by traceId when provided', async () => {
118
- const content = [
119
- JSON.stringify({ level: 'info', message: 'x', trace_id: 't1' }),
120
- JSON.stringify({ level: 'info', message: 'y', trace_id: 't2' }),
121
- JSON.stringify({ level: 'info', message: 'z', trace_id: 't2' }),
122
- ].join('\n');
123
- await writeFile(tmpDir, 'browser.log', content);
124
- const result = await readLogsJsonResult({
125
- logDir: tmpDir,
126
- type: 'browser',
127
- maxLines: 200,
128
- traceId: 't2',
129
- });
130
- expect(result).toEqual({
131
- hasError: false,
132
- message: '',
133
- logs: [
134
- { level: 'info', message: 'y', trace_id: 't2' },
135
- { level: 'info', message: 'z', trace_id: 't2' },
136
- ],
137
- });
138
- });
139
- it('returns JSON result for server-std as message', async () => {
140
- const content = [
141
- '[2026-01-05 10:00:00] [server] > app@1.0.0 dev:server',
142
- '[2026-01-05 10:00:01] [server] booting',
143
- ].join('\n');
144
- await writeFile(tmpDir, 'server.std.log', content);
145
- const result = await readLogsJsonResult({
146
- logDir: tmpDir,
147
- type: 'server-std',
148
- maxLines: 200,
149
- });
150
- expect(result).toEqual({
151
- hasError: false,
152
- message: '> app@1.0.0 dev:server\nbooting',
153
- });
154
- });
155
- it('returns JSON result for server as logs', async () => {
156
- const content = [
157
- JSON.stringify({ pid: 100, message: 'old' }),
158
- JSON.stringify({ pid: 200, message: 'new' }),
159
- JSON.stringify({ pid: 200, message: 'new2' }),
160
- ].join('\n');
161
- await writeFile(tmpDir, 'server.log', content);
162
- const result = await readLogsJsonResult({
163
- logDir: tmpDir,
164
- type: 'server',
165
- maxLines: 200,
166
- });
167
- expect(result).toEqual({
168
- hasError: false,
169
- message: '',
170
- logs: [{ pid: 200, message: 'new' }, { pid: 200, message: 'new2' }],
171
- });
172
- });
173
- it('sets hasError=true for server-std error keywords', async () => {
174
- const content = [
175
- '[2026-01-05 10:00:00] [server] > app@1.0.0 dev:server',
176
- '[2026-01-05 10:00:01] [server] ERROR something bad happened',
177
- ].join('\n');
178
- await writeFile(tmpDir, 'server.std.log', content);
179
- const result = await readLogsJsonResult({
180
- logDir: tmpDir,
181
- type: 'server-std',
182
- maxLines: 200,
183
- });
184
- expect(result.hasError).toBe(true);
185
- });
186
- it('sets hasError=true for server JSON logs with error level', async () => {
187
- const content = [
188
- JSON.stringify({ pid: 200, level: 'INFO', message: 'ok' }),
189
- JSON.stringify({ pid: 200, level: 'ERROR', message: 'failed' }),
190
- ].join('\n');
191
- await writeFile(tmpDir, 'server.log', content);
192
- const result = await readLogsJsonResult({
193
- logDir: tmpDir,
194
- type: 'server',
195
- maxLines: 200,
196
- });
197
- expect(result.hasError).toBe(true);
198
- });
199
- });
@@ -1,6 +0,0 @@
1
- /**
2
- * 同步模板文件到用户项目
3
- */
4
- export declare function run(options: {
5
- disableGenOpenapi?: boolean;
6
- }): Promise<void>;