@longzai-intelligence-issues/ledger 0.0.1 → 0.0.3
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/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-lint.log +7 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +28 -0
- package/dist/index.d.ts +384 -11
- package/dist/index.js +23 -22
- package/package.json +4 -8
- package/src/__tests__/close/verify-close.commands.test.ts +57 -0
- package/src/__tests__/lint/lint.core.test.ts +47 -0
- package/src/__tests__/migrate/migrate-legacy.commands.test.ts +737 -0
- package/src/__tests__/parser/registry.parser.test.ts +15 -0
- package/src/__tests__/template/issue-template.renderer.test.ts +10 -0
- package/src/index.ts +8 -0
- package/src/lint/lint.core.ts +63 -1
- package/src/migrate/migrate-legacy.backfill.ts +149 -0
- package/src/migrate/migrate-legacy.commands.ts +475 -0
- package/src/migrate/migrate-legacy.converge.ts +384 -0
- package/src/migrate/migrate-legacy.core.ts +800 -0
- package/src/migrate/migrate-legacy.vocab.ts +242 -0
- package/src/normalize/normalize-header.commands.ts +249 -16
- package/src/numbering/numbering.core.ts +3 -3
- package/src/parser/format.utils.ts +76 -0
- package/src/parser/registry.parser.ts +8 -0
- package/src/template/issue-template.renderer.ts +15 -4
- package/tsconfig/.cache/build.tsbuildinfo +1 -1
- package/tsconfig/.cache/node.tsbuildinfo +1 -1
- package/tsconfig/.cache/test.tsbuildinfo +1 -1
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* migrate-legacy 命令层(文件 IO + 零丢失守卫 + 落盘)
|
|
3
|
+
*
|
|
4
|
+
* 存量注册表清偿唯一合法批量通道:变换核产出 → 守卫逐项对账(原字段/正文行
|
|
5
|
+
* 在场或落入变换台账)→ 幂等落盘。dry-run 全程零写入。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LziIssuesConfig } from '@longzai-intelligence-issues/config';
|
|
9
|
+
|
|
10
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { HEADING_ALONE_METADATA_KEYS } from '@/normalize/normalize-header.commands';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
DISPOSITION_CANDIDATES,
|
|
17
|
+
migrateLegacyCore,
|
|
18
|
+
type MigrationTransformation,
|
|
19
|
+
} from './migrate-legacy.core';
|
|
20
|
+
import { cleanValue } from './migrate-legacy.vocab';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 单文件迁移入参
|
|
24
|
+
*/
|
|
25
|
+
export type MigrateLegacyFileInput = {
|
|
26
|
+
/**
|
|
27
|
+
* 仓库根绝对路径
|
|
28
|
+
*/
|
|
29
|
+
root: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* issue 文件仓库相对路径(须落 scope registryDir 内)
|
|
33
|
+
*/
|
|
34
|
+
file: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 解析后配置(所属域回填取 scope title)
|
|
38
|
+
*/
|
|
39
|
+
config: LziIssuesConfig;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 优先级缺省(原案无优先级与严重程度时回填)
|
|
43
|
+
*/
|
|
44
|
+
defaultPriority?: 'P1' | 'P2' | 'P3';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 来源回填缺省文案
|
|
48
|
+
*/
|
|
49
|
+
defaultSource?: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 立案日期回填缺省(YYYY-MM-DD)
|
|
53
|
+
*/
|
|
54
|
+
defaultFilingDate?: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 无状态行历史文档的显式灯位注入(--status red|green;灯位语义由操作者定档)
|
|
58
|
+
*/
|
|
59
|
+
status?: 'red' | 'green';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 只呈现不落盘
|
|
63
|
+
*/
|
|
64
|
+
dryRun?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 单文件迁移结果
|
|
69
|
+
*/
|
|
70
|
+
export type MigrateLegacyFileResult = {
|
|
71
|
+
/**
|
|
72
|
+
* issue 文件仓库相对路径
|
|
73
|
+
*/
|
|
74
|
+
file: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 处置形态
|
|
78
|
+
*/
|
|
79
|
+
outcome: 'rewritten' | 'unchanged' | 'skipped' | 'failed';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 结果说明
|
|
83
|
+
*/
|
|
84
|
+
message: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 产物全文(rewritten/unchanged 携带,dry-run 复核消费)
|
|
88
|
+
*/
|
|
89
|
+
content: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 变换台账(人工复核消费)
|
|
93
|
+
*/
|
|
94
|
+
transformations: MigrationTransformation[];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 注册表文件名编号提取模式(NNNN-slug.md)
|
|
99
|
+
*/
|
|
100
|
+
const FILE_NUMBER_PATTERN = /^([0-9]{3,4})-.+\.md$/;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 解析文件所属 scope(registryDir 前缀最长匹配)
|
|
104
|
+
*
|
|
105
|
+
* @param config - 解析后配置
|
|
106
|
+
* @param file - 仓库相对文件路径
|
|
107
|
+
* @returns scope title;无命中为 null
|
|
108
|
+
*/
|
|
109
|
+
function resolveScopeTitle(config: LziIssuesConfig, file: string): string | null {
|
|
110
|
+
/**
|
|
111
|
+
* 候选 scope(按 registryDir 长度降序取最长前缀命中)
|
|
112
|
+
*/
|
|
113
|
+
const hit = config.scopes
|
|
114
|
+
.filter((scope) => file === scope.registryDir || file.startsWith(`${scope.registryDir}/`))
|
|
115
|
+
.sort((left, right) => right.registryDir.length - left.registryDir.length)[0];
|
|
116
|
+
|
|
117
|
+
return hit?.title ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 零丢失守卫:原文全部内容必须在产物在场(或属已登记变换:状态规范化/字段
|
|
122
|
+
* 别名/优先级映射/H1 补编号/处置段标题改名/表格脚手架丢弃)
|
|
123
|
+
*
|
|
124
|
+
* @param raw - 原文全文
|
|
125
|
+
* @param migrated - 变换核产物全文
|
|
126
|
+
* @param transformedKeys - 已变换字段键集合(状态/日期/优先级等)
|
|
127
|
+
* @returns 缺失项描述列表(空 = 通过)
|
|
128
|
+
*/
|
|
129
|
+
function findLostContent(raw: string, migrated: string, transformedKeys: string[]): string[] {
|
|
130
|
+
/**
|
|
131
|
+
* 缺失收集器
|
|
132
|
+
*/
|
|
133
|
+
const lost: string[] = [];
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 原文行数组
|
|
137
|
+
*/
|
|
138
|
+
const rawLines = raw.split(/\r?\n/);
|
|
139
|
+
|
|
140
|
+
for (let lineIndex = 0; lineIndex < rawLines.length; lineIndex += 1) {
|
|
141
|
+
/**
|
|
142
|
+
* 当前行
|
|
143
|
+
*/
|
|
144
|
+
const line = rawLines[lineIndex] ?? '';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 行首去空白形态
|
|
148
|
+
*/
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
|
|
151
|
+
if (trimmed === '') {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (migrated.includes(trimmed)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 一级标题:补编号/分隔符规范化后标题核心须在场
|
|
161
|
+
*/
|
|
162
|
+
if (trimmed.startsWith('# ')) {
|
|
163
|
+
/**
|
|
164
|
+
* 标题核心(去 `# ` 与既有编号 + 分隔符变体)
|
|
165
|
+
*/
|
|
166
|
+
const titleCore = trimmed
|
|
167
|
+
.replace(/^#\s+/, '')
|
|
168
|
+
.replace(/^[0-9]{3,4}\s*(?:[-—―·..::-]+\s*|\s+)/, '')
|
|
169
|
+
.trim();
|
|
170
|
+
|
|
171
|
+
if (titleCore !== '' && migrated.includes(titleCore)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 处置段候选标题改名(候选链与变换核 DISPOSITION_CANDIDATES 单一真源)
|
|
178
|
+
*/
|
|
179
|
+
if (
|
|
180
|
+
trimmed.startsWith('## ') &&
|
|
181
|
+
DISPOSITION_CANDIDATES.some((candidate) => candidate.pattern.test(trimmed.slice(3).trim())) &&
|
|
182
|
+
migrated.includes('处置结果/')
|
|
183
|
+
) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 表格行:脚手架(表头/分隔行)设计性丢弃;数据行按键值对账
|
|
189
|
+
*/
|
|
190
|
+
const tableMatch = /^\|([^|]*)\|([^|]*)\|$/.exec(trimmed);
|
|
191
|
+
|
|
192
|
+
if (tableMatch !== null) {
|
|
193
|
+
/**
|
|
194
|
+
* 键/值单元格
|
|
195
|
+
*/
|
|
196
|
+
const key = (tableMatch[1] ?? '').trim();
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 值单元格(与键单元格配对对账)
|
|
200
|
+
*/
|
|
201
|
+
const value = (tableMatch[2] ?? '').trim();
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 分隔行单元格形态
|
|
205
|
+
*/
|
|
206
|
+
const separatorCellPattern = /^:?-{2,}:?$/;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 次行表格匹配(表头行判定:分隔行紧前的一行是表头——与解析器同口径)
|
|
210
|
+
*/
|
|
211
|
+
const nextLineMatch = /^\|([^|]*)\|([^|]*)\|$/.exec((rawLines[lineIndex + 1] ?? '').trim());
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 次行是否表格分隔行
|
|
215
|
+
*/
|
|
216
|
+
const nextIsTableSeparator =
|
|
217
|
+
nextLineMatch !== null &&
|
|
218
|
+
separatorCellPattern.test((nextLineMatch[1] ?? '').trim()) &&
|
|
219
|
+
separatorCellPattern.test((nextLineMatch[2] ?? '').trim());
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 分隔行/表头行(表头任意列名形态均属脚手架——设计性丢弃)
|
|
223
|
+
*/
|
|
224
|
+
if (
|
|
225
|
+
(separatorCellPattern.test(key) && separatorCellPattern.test(value)) ||
|
|
226
|
+
(nextIsTableSeparator && key !== '') ||
|
|
227
|
+
key === ''
|
|
228
|
+
) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (transformedKeys.includes(key) || migrated.includes(`**${key}**: ${value}`)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
lost.push(trimmed.slice(0, 60));
|
|
237
|
+
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 管道引用行(`> 键:值 | …`):逐段键值对账(与解析器形态一同口径)
|
|
243
|
+
*/
|
|
244
|
+
if (trimmed.startsWith('>')) {
|
|
245
|
+
/**
|
|
246
|
+
* 管道分段
|
|
247
|
+
*/
|
|
248
|
+
const segments = trimmed.slice(1).split(/[||]/);
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 全部段对账通过标记
|
|
252
|
+
*/
|
|
253
|
+
let allMatched = true;
|
|
254
|
+
|
|
255
|
+
for (const segment of segments) {
|
|
256
|
+
/**
|
|
257
|
+
* 段内键值切分
|
|
258
|
+
*/
|
|
259
|
+
const pairMatch = /^([^::]{1,8})\s*[::]\s*(.+)$/.exec(segment.trim());
|
|
260
|
+
|
|
261
|
+
if (pairMatch === null) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 键(清粗体包裹——`> **键**: 值` 复合形态与头部字段行对账同口径)
|
|
267
|
+
*/
|
|
268
|
+
const key = (pairMatch[1] ?? '').replace(/^\*+|\*+$/g, '').trim();
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 清理后值(与引擎 cleanValue 同口径)
|
|
272
|
+
*/
|
|
273
|
+
const value = cleanValue(pairMatch[2] ?? '');
|
|
274
|
+
|
|
275
|
+
if (!transformedKeys.includes(key) && !migrated.includes(value)) {
|
|
276
|
+
allMatched = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (allMatched) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 多行元信息形态:空值键行(键已并入规范头部即通过)
|
|
287
|
+
*/
|
|
288
|
+
const emptyKeyMatch = /^(?:\*\*|- \*\*|- )([^*::]{1,20})(?:\*\*)?\s*[::]\s*$/.exec(trimmed);
|
|
289
|
+
|
|
290
|
+
if (emptyKeyMatch !== null && migrated.includes(`**${(emptyKeyMatch[1] ?? '').trim()}**:`)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 列表子项行:去列表标记后的文本在场即通过(多行元信息子项已并入单行值)
|
|
296
|
+
*/
|
|
297
|
+
if (trimmed.startsWith('- ') && migrated.includes(trimmed.slice(2).trim())) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 头部字段行:粗体包裹与值清理后的形态在场即可(含列表+粗体复合方言)
|
|
303
|
+
*/
|
|
304
|
+
const labeled = /^(?:\*\*|- \*\*|- |## )([^*::]{1,20})(?:\*\*)?\s*[::]\s*(.+)$/.exec(line);
|
|
305
|
+
|
|
306
|
+
if (labeled !== null) {
|
|
307
|
+
/**
|
|
308
|
+
* 键
|
|
309
|
+
*/
|
|
310
|
+
const key = (labeled[1] ?? '').trim();
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 值(与引擎 cleanValue 同口径清理——全包裹/前导/词后粗体三形态)
|
|
314
|
+
*/
|
|
315
|
+
const value = cleanValue(labeled[2] ?? '');
|
|
316
|
+
|
|
317
|
+
if (transformedKeys.includes(key) || migrated.includes(value)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 标题式隔行值键行(`## 键` 单独成行):键行属登记变换被消费进规范头部,
|
|
324
|
+
* 配对值行自身经上方 includes 对账在场
|
|
325
|
+
*/
|
|
326
|
+
const aloneKey = /^##\s*([^::]{1,10})\s*$/.exec(trimmed);
|
|
327
|
+
|
|
328
|
+
if (aloneKey !== null && HEADING_ALONE_METADATA_KEYS.has(aloneKey[1]?.trim() ?? '')) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* 标题式隔行值配对值行:前非空行是 `## 键` 且键在集合内时按字段值对账
|
|
334
|
+
* (状态词规范化会重组值文本——键属已变换集合或清理值在场即通过)
|
|
335
|
+
*/
|
|
336
|
+
let prevIndex = lineIndex - 1;
|
|
337
|
+
|
|
338
|
+
while (prevIndex >= 0 && (rawLines[prevIndex] ?? '').trim() === '') {
|
|
339
|
+
prevIndex -= 1;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 前非空行的标题式键形态
|
|
344
|
+
*/
|
|
345
|
+
const prevAlone = /^##\s*([^::]{1,10})\s*$/.exec((rawLines[prevIndex] ?? '').trim());
|
|
346
|
+
|
|
347
|
+
if (prevAlone !== null && HEADING_ALONE_METADATA_KEYS.has(prevAlone[1]?.trim() ?? '')) {
|
|
348
|
+
/**
|
|
349
|
+
* 配对键
|
|
350
|
+
*/
|
|
351
|
+
const prevKey = (prevAlone[1] ?? '').trim();
|
|
352
|
+
|
|
353
|
+
if (transformedKeys.includes(prevKey) || migrated.includes(cleanValue(trimmed))) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
lost.push(trimmed.slice(0, 60));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return lost;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 迁移并(缺省)落盘单份存量文档
|
|
366
|
+
*
|
|
367
|
+
* @param input - 命令入参
|
|
368
|
+
* @returns 单文件迁移结果
|
|
369
|
+
*/
|
|
370
|
+
export function migrateLegacyFile(input: MigrateLegacyFileInput): MigrateLegacyFileResult {
|
|
371
|
+
/**
|
|
372
|
+
* 编号(文件名前缀)
|
|
373
|
+
*/
|
|
374
|
+
const basename = input.file.split('/').pop() ?? '';
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* 文件名编号提取结果(H1 规范化用)
|
|
378
|
+
*/
|
|
379
|
+
const numberMatch = FILE_NUMBER_PATTERN.exec(basename);
|
|
380
|
+
|
|
381
|
+
if (numberMatch === null) {
|
|
382
|
+
return {
|
|
383
|
+
file: input.file,
|
|
384
|
+
outcome: 'skipped',
|
|
385
|
+
message: '文件名不属注册表编号形态(NNNN-slug.md)',
|
|
386
|
+
content: '',
|
|
387
|
+
transformations: [],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 所属域(scope title)
|
|
393
|
+
*/
|
|
394
|
+
const scopeTitle = resolveScopeTitle(input.config, input.file);
|
|
395
|
+
|
|
396
|
+
if (scopeTitle === null) {
|
|
397
|
+
return {
|
|
398
|
+
file: input.file,
|
|
399
|
+
outcome: 'skipped',
|
|
400
|
+
message: '文件不在任何已登记 scope 的 registryDir 内——交人工',
|
|
401
|
+
content: '',
|
|
402
|
+
transformations: [],
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* 文档全文
|
|
408
|
+
*/
|
|
409
|
+
let raw = '';
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
raw = readFileSync(join(input.root, input.file), 'utf8');
|
|
413
|
+
} catch {
|
|
414
|
+
return {
|
|
415
|
+
file: input.file,
|
|
416
|
+
outcome: 'failed',
|
|
417
|
+
message: '读档失败(文件不存在或不可读)',
|
|
418
|
+
content: '',
|
|
419
|
+
transformations: [],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 纯变换产物
|
|
425
|
+
*/
|
|
426
|
+
const migrated = migrateLegacyCore(raw, {
|
|
427
|
+
number: numberMatch[1] ?? '',
|
|
428
|
+
scopeTitle,
|
|
429
|
+
defaultPriority: input.defaultPriority,
|
|
430
|
+
defaultSource: input.defaultSource,
|
|
431
|
+
defaultFilingDate: input.defaultFilingDate,
|
|
432
|
+
status: input.status,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
if (migrated.outcome !== 'rewritten') {
|
|
436
|
+
return {
|
|
437
|
+
file: input.file,
|
|
438
|
+
outcome: migrated.outcome,
|
|
439
|
+
message: migrated.message,
|
|
440
|
+
content: migrated.content,
|
|
441
|
+
transformations: migrated.transformations,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 零丢失守卫(已变换字段键集合:状态/日期别名/优先级/H1;来源让位时加来源)
|
|
447
|
+
*/
|
|
448
|
+
const lost = findLostContent(
|
|
449
|
+
raw,
|
|
450
|
+
migrated.content,
|
|
451
|
+
migrated.sourceReplaced ? ['状态', '日期', '优先级', '来源'] : ['状态', '日期', '优先级'],
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
if (lost.length > 0) {
|
|
455
|
+
return {
|
|
456
|
+
file: input.file,
|
|
457
|
+
outcome: 'failed',
|
|
458
|
+
message: `零丢失守卫失败:${lost.length} 项原文内容未在产物在场(如 ${lost[0]})——不落盘`,
|
|
459
|
+
content: migrated.content,
|
|
460
|
+
transformations: migrated.transformations,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (input.dryRun !== true) {
|
|
465
|
+
writeFileSync(join(input.root, input.file), migrated.content);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
file: input.file,
|
|
470
|
+
outcome: 'rewritten',
|
|
471
|
+
message: input.dryRun === true ? 'dry-run 通过(未落盘)' : '存量头部已清偿为规范形态',
|
|
472
|
+
content: migrated.content,
|
|
473
|
+
transformations: migrated.transformations,
|
|
474
|
+
};
|
|
475
|
+
}
|