proteum 2.4.3 → 2.5.0
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 +60 -55
- package/agents/project/AGENTS.md +112 -31
- package/agents/project/CODING_STYLE.md +2 -2
- package/agents/project/app-root/AGENTS.md +1 -3
- package/agents/project/client/AGENTS.md +1 -1
- package/agents/project/client/pages/AGENTS.md +21 -9
- package/agents/project/diagnostics.md +2 -2
- package/agents/project/optimizations.md +1 -1
- package/agents/project/root/AGENTS.md +105 -22
- package/agents/project/server/routes/AGENTS.md +30 -1
- package/agents/project/tests/AGENTS.md +1 -1
- package/cli/commands/doctor.ts +54 -3
- package/cli/commands/runtime.ts +6 -0
- package/cli/commands/worktree.ts +116 -0
- package/cli/compiler/artifacts/controllers.ts +16 -15
- package/cli/compiler/artifacts/discovery.ts +129 -17
- package/cli/compiler/artifacts/routing.ts +0 -5
- package/cli/compiler/artifacts/services.ts +253 -76
- package/cli/compiler/common/controllers.ts +159 -57
- package/cli/compiler/common/generatedRouteModules.ts +457 -363
- package/cli/mcp/router.ts +47 -3
- package/cli/presentation/commands.ts +25 -15
- package/cli/runtime/commands.ts +39 -12
- package/cli/runtime/worktreeBootstrap.ts +608 -0
- package/cli/scaffold/index.ts +28 -18
- package/cli/scaffold/templates.ts +44 -33
- package/cli/utils/agents.ts +14 -1
- package/client/services/router/index.tsx +23 -3
- package/client/services/router/request/api.ts +14 -4
- package/common/dev/contractsDoctor.ts +1 -1
- package/common/dev/mcpPayloads.ts +8 -1
- package/common/env/proteumEnv.ts +14 -2
- package/common/router/contracts.ts +1 -1
- package/common/router/definitions.ts +177 -0
- package/common/router/index.ts +23 -12
- package/common/router/pageData.ts +5 -5
- package/common/router/register.ts +2 -2
- package/common/router/request/api.ts +12 -2
- package/docs/agent-routing.md +5 -2
- package/docs/diagnostics.md +2 -0
- package/docs/mcp.md +6 -3
- package/eslint.js +36 -1
- package/package.json +1 -1
- package/server/app/commands.ts +5 -1
- package/server/app/container/console/http-client-error-context.test.cjs +10 -1
- package/server/app/container/console/index.ts +2 -1
- package/server/app/controller/index.ts +98 -40
- package/server/app/index.ts +92 -1
- package/server/app/service/index.ts +5 -1
- package/server/index.ts +6 -2
- package/server/services/router/index.ts +47 -38
- package/server/services/router/response/index.ts +2 -2
- package/tests/agents-utils.test.cjs +14 -1
- package/tests/cli-mcp-command.test.cjs +84 -0
- package/tests/definition-contracts.test.cjs +453 -0
- package/tests/dev-transpile-watch.test.cjs +37 -28
- package/tests/eslint-rules.test.cjs +39 -1
- package/tests/mcp.test.cjs +90 -0
- package/tests/worktree-bootstrap.test.cjs +206 -0
- package/types/aliases.d.ts +0 -5
- package/types/controller-input.test.ts +23 -17
- package/types/controller-request-context.test.ts +10 -11
- package/cli/commands/migrate.ts +0 -51
- package/cli/migrate/pageContract.ts +0 -516
- package/docs/migrate-from-2.1.3.md +0 -396
- package/scripts/cleanup-generated-controllers.ts +0 -62
- package/scripts/fix-reference-app-typing.ts +0 -490
- package/scripts/format-router-registrations.ts +0 -119
- package/scripts/migrate-explicit-controllers-and-request.ts +0 -423
- package/scripts/refactor-client-app-imports.ts +0 -244
- package/scripts/refactor-client-pages.ts +0 -587
- package/scripts/refactor-server-controllers.ts +0 -471
- package/scripts/refactor-server-runtime-aliases.ts +0 -360
- package/scripts/restore-client-app-import-files.ts +0 -41
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { execFileSync } from 'child_process';
|
|
4
|
-
import { parse } from '@babel/parser';
|
|
5
|
-
import traverse, { NodePath } from '@babel/traverse';
|
|
6
|
-
import generate from '@babel/generator';
|
|
7
|
-
import * as types from '@babel/types';
|
|
8
|
-
|
|
9
|
-
type TRuntimeImportSource = '@app' | '@request' | '@models';
|
|
10
|
-
|
|
11
|
-
type TRuntimeBinding = { local: string; imported: string; source: TRuntimeImportSource };
|
|
12
|
-
|
|
13
|
-
type TClassContext = { hasApp: boolean; hasRequest: boolean };
|
|
14
|
-
|
|
15
|
-
const fallbackBindings = new Map<string, TRuntimeBinding>([
|
|
16
|
-
['Models', { local: 'Models', imported: 'Models', source: '@app' }],
|
|
17
|
-
['Disks', { local: 'Disks', imported: 'Disks', source: '@app' }],
|
|
18
|
-
['Router', { local: 'Router', imported: 'Router', source: '@app' }],
|
|
19
|
-
['Environment', { local: 'Environment', imported: 'Environment', source: '@app' }],
|
|
20
|
-
['Identity', { local: 'Identity', imported: 'Identity', source: '@app' }],
|
|
21
|
-
['auth', { local: 'auth', imported: 'auth', source: '@request' }],
|
|
22
|
-
['context', { local: 'context', imported: 'context', source: '@request' }],
|
|
23
|
-
]);
|
|
24
|
-
|
|
25
|
-
const parseCode = (code: string, filename: string) =>
|
|
26
|
-
parse(code, {
|
|
27
|
-
sourceType: 'module',
|
|
28
|
-
sourceFilename: filename,
|
|
29
|
-
plugins: [
|
|
30
|
-
'typescript',
|
|
31
|
-
'jsx',
|
|
32
|
-
'decorators-legacy',
|
|
33
|
-
'classProperties',
|
|
34
|
-
'classPrivateProperties',
|
|
35
|
-
'classPrivateMethods',
|
|
36
|
-
],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const findFiles = (dir: string): string[] => {
|
|
40
|
-
if (!fs.existsSync(dir)) return [];
|
|
41
|
-
|
|
42
|
-
const files: string[] = [];
|
|
43
|
-
|
|
44
|
-
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
45
|
-
const filepath = path.join(dir, dirent.name);
|
|
46
|
-
|
|
47
|
-
if (dirent.isDirectory()) {
|
|
48
|
-
files.push(...findFiles(filepath));
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (dirent.isFile() && /\.(tsx?|jsx?)$/.test(filepath) && !filepath.endsWith('.d.ts')) files.push(filepath);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return files;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const lowerFirst = (value: string) => (value.length ? value[0].toLowerCase() + value.substring(1) : value);
|
|
59
|
-
|
|
60
|
-
const getOriginalFileContent = (repoRoot: string, filepath: string) => {
|
|
61
|
-
const relativeFilepath = path.relative(repoRoot, filepath).replace(/\\/g, '/');
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
return execFileSync('git', ['-C', repoRoot, 'show', `HEAD:${relativeFilepath}`], { encoding: 'utf8' });
|
|
65
|
-
} catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const extractRuntimeBindings = (code: string, filename: string) => {
|
|
71
|
-
const ast = parseCode(code, filename);
|
|
72
|
-
const runtimeBindings = new Map<string, TRuntimeBinding>();
|
|
73
|
-
|
|
74
|
-
traverse(ast, {
|
|
75
|
-
ImportDeclaration(importPath) {
|
|
76
|
-
const source = importPath.node.source.value;
|
|
77
|
-
if (source !== '@app' && source !== '@request' && source !== '@models') return;
|
|
78
|
-
|
|
79
|
-
for (const specifier of importPath.node.specifiers) {
|
|
80
|
-
if (specifier.type !== 'ImportSpecifier' || specifier.imported.type !== 'Identifier') continue;
|
|
81
|
-
|
|
82
|
-
runtimeBindings.set(specifier.local.name, {
|
|
83
|
-
local: specifier.local.name,
|
|
84
|
-
imported: specifier.imported.name,
|
|
85
|
-
source,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return runtimeBindings;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const getClassContext = (path: NodePath) => {
|
|
95
|
-
const classPath = path.findParent((parentPath) => parentPath.isClass()) as NodePath<types.Class> | null;
|
|
96
|
-
if (!classPath) return null;
|
|
97
|
-
|
|
98
|
-
const classNode = classPath.node;
|
|
99
|
-
let hasApp = false;
|
|
100
|
-
let hasRequest = false;
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
types.isIdentifier(classNode.superClass) &&
|
|
104
|
-
(classNode.superClass.name === 'Service' ||
|
|
105
|
-
classNode.superClass.name === 'Controller' ||
|
|
106
|
-
classNode.superClass.name.endsWith('Service') ||
|
|
107
|
-
classNode.superClass.name.endsWith('Controller'))
|
|
108
|
-
) {
|
|
109
|
-
hasApp = true;
|
|
110
|
-
hasRequest = true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (classNode.superClass) {
|
|
114
|
-
hasApp = true;
|
|
115
|
-
hasRequest = true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const member of classNode.body.body) {
|
|
119
|
-
if (
|
|
120
|
-
(types.isClassProperty(member) || types.isClassAccessorProperty?.(member)) &&
|
|
121
|
-
types.isIdentifier(member.key)
|
|
122
|
-
) {
|
|
123
|
-
if (member.key.name === 'app') hasApp = true;
|
|
124
|
-
if (member.key.name === 'request') hasRequest = true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (types.isClassMethod(member) && types.isIdentifier(member.key)) {
|
|
128
|
-
if (member.kind === 'get' && member.key.name === 'request') hasRequest = true;
|
|
129
|
-
|
|
130
|
-
if (member.kind === 'constructor') {
|
|
131
|
-
for (const param of member.params) {
|
|
132
|
-
if (types.isTSParameterProperty(param)) {
|
|
133
|
-
const parameter = param.parameter;
|
|
134
|
-
const identifier = types.isIdentifier(parameter)
|
|
135
|
-
? parameter
|
|
136
|
-
: types.isAssignmentPattern(parameter) && types.isIdentifier(parameter.left)
|
|
137
|
-
? parameter.left
|
|
138
|
-
: null;
|
|
139
|
-
|
|
140
|
-
if (!identifier) continue;
|
|
141
|
-
|
|
142
|
-
if (identifier.name === 'app') hasApp = true;
|
|
143
|
-
if (identifier.name === 'request') hasRequest = true;
|
|
144
|
-
} else if (types.isIdentifier(param)) {
|
|
145
|
-
if (param.name === 'app') hasApp = true;
|
|
146
|
-
if (param.name === 'request') hasRequest = true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
traverse(classPath.node, {
|
|
154
|
-
noScope: true,
|
|
155
|
-
MemberExpression(memberPath) {
|
|
156
|
-
if (!types.isThisExpression(memberPath.node.object)) return;
|
|
157
|
-
if (!types.isIdentifier(memberPath.node.property)) return;
|
|
158
|
-
|
|
159
|
-
if (memberPath.node.property.name === 'app') hasApp = true;
|
|
160
|
-
if (memberPath.node.property.name === 'request') hasRequest = true;
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
if (!hasApp && !hasRequest) return null;
|
|
165
|
-
|
|
166
|
-
return { hasApp, hasRequest } satisfies TClassContext;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const isInTypePosition = (path: NodePath<types.Identifier>) => {
|
|
170
|
-
let current: NodePath | null = path;
|
|
171
|
-
|
|
172
|
-
while (current?.parentPath) {
|
|
173
|
-
const parent = current.parentPath;
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
(parent.isTSAsExpression() ||
|
|
177
|
-
parent.isTSSatisfiesExpression() ||
|
|
178
|
-
parent.isTSNonNullExpression() ||
|
|
179
|
-
parent.isTSInstantiationExpression()) &&
|
|
180
|
-
current.key === 'expression'
|
|
181
|
-
) {
|
|
182
|
-
current = parent;
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (parent.node.type.startsWith('TS')) return true;
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
parent.isExpression() ||
|
|
190
|
-
parent.isStatement() ||
|
|
191
|
-
parent.isProgram() ||
|
|
192
|
-
parent.isClassBody() ||
|
|
193
|
-
parent.isClassMethod() ||
|
|
194
|
-
parent.isClassProperty() ||
|
|
195
|
-
parent.isObjectProperty()
|
|
196
|
-
)
|
|
197
|
-
return false;
|
|
198
|
-
|
|
199
|
-
current = parent;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return false;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const isSafeImportBinding = (bindingPath: NodePath) => {
|
|
206
|
-
if (
|
|
207
|
-
!bindingPath.isImportSpecifier() &&
|
|
208
|
-
!bindingPath.isImportNamespaceSpecifier() &&
|
|
209
|
-
!bindingPath.isImportDefaultSpecifier()
|
|
210
|
-
)
|
|
211
|
-
return false;
|
|
212
|
-
|
|
213
|
-
const importDeclaration = bindingPath.parentPath;
|
|
214
|
-
if (!importDeclaration?.isImportDeclaration()) return false;
|
|
215
|
-
|
|
216
|
-
if (importDeclaration.node.importKind === 'type') return true;
|
|
217
|
-
if (importDeclaration.node.source.value === '@models/types') return true;
|
|
218
|
-
|
|
219
|
-
return false;
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const buildReplacementExpression = (binding: TRuntimeBinding, classContext: TClassContext | null) => {
|
|
223
|
-
if (!classContext) return null;
|
|
224
|
-
|
|
225
|
-
if (binding.source === '@request') {
|
|
226
|
-
if (!classContext.hasRequest) return null;
|
|
227
|
-
|
|
228
|
-
if (binding.imported === 'context')
|
|
229
|
-
return types.memberExpression(types.thisExpression(), types.identifier('request'));
|
|
230
|
-
|
|
231
|
-
return types.memberExpression(
|
|
232
|
-
types.memberExpression(types.thisExpression(), types.identifier('request')),
|
|
233
|
-
types.identifier(binding.imported),
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (!classContext.hasApp) return null;
|
|
238
|
-
|
|
239
|
-
if (binding.source === '@app') {
|
|
240
|
-
if (binding.imported === 'Environment')
|
|
241
|
-
return types.memberExpression(
|
|
242
|
-
types.memberExpression(types.thisExpression(), types.identifier('app')),
|
|
243
|
-
types.identifier('env'),
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
if (binding.imported === 'Identity')
|
|
247
|
-
return types.memberExpression(
|
|
248
|
-
types.memberExpression(types.thisExpression(), types.identifier('app')),
|
|
249
|
-
types.identifier('identity'),
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
return types.memberExpression(
|
|
253
|
-
types.memberExpression(types.thisExpression(), types.identifier('app')),
|
|
254
|
-
types.identifier(binding.imported),
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return types.memberExpression(
|
|
259
|
-
types.memberExpression(
|
|
260
|
-
types.memberExpression(
|
|
261
|
-
types.memberExpression(types.thisExpression(), types.identifier('app')),
|
|
262
|
-
types.identifier('Models'),
|
|
263
|
-
),
|
|
264
|
-
types.identifier('client'),
|
|
265
|
-
),
|
|
266
|
-
types.identifier(lowerFirst(binding.imported)),
|
|
267
|
-
);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const repoRoots = process.argv.slice(2);
|
|
271
|
-
if (!repoRoots.length)
|
|
272
|
-
throw new Error('Usage: ts-node scripts/refactor-server-runtime-aliases.ts <repo-root> [repo-root...]');
|
|
273
|
-
|
|
274
|
-
for (const repoRoot of repoRoots) {
|
|
275
|
-
const serviceRoot = path.join(repoRoot, 'server', 'services');
|
|
276
|
-
const files = findFiles(serviceRoot);
|
|
277
|
-
|
|
278
|
-
let changedFiles = 0;
|
|
279
|
-
let replacementCount = 0;
|
|
280
|
-
const skipped: string[] = [];
|
|
281
|
-
|
|
282
|
-
for (const filepath of files) {
|
|
283
|
-
const originalCode = getOriginalFileContent(repoRoot, filepath);
|
|
284
|
-
if (!originalCode) continue;
|
|
285
|
-
|
|
286
|
-
const runtimeBindings = extractRuntimeBindings(originalCode, filepath);
|
|
287
|
-
if (!runtimeBindings.size) continue;
|
|
288
|
-
|
|
289
|
-
const code = fs.readFileSync(filepath, 'utf8');
|
|
290
|
-
const ast = parseCode(code, filepath);
|
|
291
|
-
let fileChanged = false;
|
|
292
|
-
|
|
293
|
-
traverse(ast, {
|
|
294
|
-
MemberExpression(memberPath) {
|
|
295
|
-
if (!types.isMemberExpression(memberPath.node.object)) return;
|
|
296
|
-
if (!types.isThisExpression(memberPath.node.object.object)) return;
|
|
297
|
-
if (!types.isIdentifier(memberPath.node.object.property, { name: 'app' })) return;
|
|
298
|
-
if (!types.isIdentifier(memberPath.node.property)) return;
|
|
299
|
-
|
|
300
|
-
if (memberPath.node.property.name === 'Environment') {
|
|
301
|
-
memberPath.node.property = types.identifier('env');
|
|
302
|
-
fileChanged = true;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (memberPath.node.property.name === 'Identity') {
|
|
306
|
-
memberPath.node.property = types.identifier('identity');
|
|
307
|
-
fileChanged = true;
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
Identifier(identifierPath) {
|
|
311
|
-
const binding =
|
|
312
|
-
runtimeBindings.get(identifierPath.node.name) || fallbackBindings.get(identifierPath.node.name);
|
|
313
|
-
if (!binding) return;
|
|
314
|
-
if (!identifierPath.isReferencedIdentifier()) return;
|
|
315
|
-
if (isInTypePosition(identifierPath)) return;
|
|
316
|
-
|
|
317
|
-
const currentBinding = identifierPath.scope.getBinding(identifierPath.node.name);
|
|
318
|
-
if (currentBinding && !isSafeImportBinding(currentBinding.path)) return;
|
|
319
|
-
|
|
320
|
-
const classContext = getClassContext(identifierPath);
|
|
321
|
-
const replacement = buildReplacementExpression(binding, classContext);
|
|
322
|
-
if (!replacement) {
|
|
323
|
-
const location = identifierPath.node.loc?.start;
|
|
324
|
-
skipped.push(
|
|
325
|
-
`${path.relative(repoRoot, filepath)}:${location?.line ?? 0}:${identifierPath.node.name}`,
|
|
326
|
-
);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
identifierPath.parentPath?.isObjectProperty() &&
|
|
332
|
-
identifierPath.parentPath.node.shorthand &&
|
|
333
|
-
identifierPath.parentKey === 'value'
|
|
334
|
-
) {
|
|
335
|
-
identifierPath.parentPath.node.shorthand = false;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
identifierPath.replaceWith(types.cloneNode(replacement, true));
|
|
339
|
-
fileChanged = true;
|
|
340
|
-
replacementCount += 1;
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
if (!fileChanged) continue;
|
|
345
|
-
|
|
346
|
-
const output = generate(ast, { retainLines: false, decoratorsBeforeExport: true }, code).code;
|
|
347
|
-
|
|
348
|
-
fs.writeFileSync(filepath, output + '\n');
|
|
349
|
-
changedFiles += 1;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
console.info(`[runtime-aliases] ${repoRoot}: changed ${changedFiles} files, ${replacementCount} replacements`);
|
|
353
|
-
|
|
354
|
-
if (skipped.length) {
|
|
355
|
-
console.info('[runtime-aliases] skipped references:');
|
|
356
|
-
for (const item of skipped.slice(0, 50)) console.info(' -', item);
|
|
357
|
-
|
|
358
|
-
if (skipped.length > 50) console.info(` - ... ${skipped.length - 50} more`);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs-extra';
|
|
4
|
-
|
|
5
|
-
const repoRoots = process.argv.slice(2);
|
|
6
|
-
if (!repoRoots.length)
|
|
7
|
-
throw new Error('Usage: ts-node scripts/restore-client-app-import-files.ts <repo-root> [repo-root...]');
|
|
8
|
-
|
|
9
|
-
for (const repoRoot of repoRoots) {
|
|
10
|
-
const diffOutput = execFileSync(
|
|
11
|
-
'git',
|
|
12
|
-
['-C', repoRoot, 'diff', '--name-only', 'HEAD', '--', 'client/components', 'client/hooks'],
|
|
13
|
-
{ encoding: 'utf8' },
|
|
14
|
-
);
|
|
15
|
-
const currentDiffFiles = diffOutput
|
|
16
|
-
.split('\n')
|
|
17
|
-
.map((line) => line.trim())
|
|
18
|
-
.filter(Boolean);
|
|
19
|
-
|
|
20
|
-
const grepOutput = execFileSync(
|
|
21
|
-
'git',
|
|
22
|
-
['-C', repoRoot, 'grep', '-l', `from '@app'\\|from "@app"`, 'HEAD', '--', 'client'],
|
|
23
|
-
{ encoding: 'utf8' },
|
|
24
|
-
);
|
|
25
|
-
const headAppImportFiles = new Set(
|
|
26
|
-
grepOutput
|
|
27
|
-
.split('\n')
|
|
28
|
-
.map((line) => line.trim().replace(/^HEAD:/, ''))
|
|
29
|
-
.filter((line) => line.startsWith('client/components/') || line.startsWith('client/hooks/')),
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const filesToRestore = currentDiffFiles.filter((filepath) => headAppImportFiles.has(filepath));
|
|
33
|
-
|
|
34
|
-
for (const relativeFile of filesToRestore) {
|
|
35
|
-
const nextContent = execFileSync('git', ['-C', repoRoot, 'show', `HEAD:${relativeFile}`], { encoding: 'utf8' });
|
|
36
|
-
const filepath = path.join(repoRoot, relativeFile);
|
|
37
|
-
fs.ensureDirSync(path.dirname(filepath));
|
|
38
|
-
fs.writeFileSync(filepath, nextContent);
|
|
39
|
-
console.log(`[restore-client-app-import-files] restored ${filepath}`);
|
|
40
|
-
}
|
|
41
|
-
}
|