@epic-web/workshop-utils 6.85.4 → 6.85.5
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/dist/cache.server.d.ts +1 -1
- package/dist/cache.server.js +1 -1
- package/dist/diff.server.d.ts +1 -1
- package/dist/diff.server.js +70 -205
- package/package.json +1 -2
package/dist/cache.server.d.ts
CHANGED
|
@@ -167,7 +167,7 @@ export declare const playgroundAppCache: C.Cache<{
|
|
|
167
167
|
instructionsCode?: string | undefined;
|
|
168
168
|
epicVideoEmbeds?: string[] | undefined;
|
|
169
169
|
}>;
|
|
170
|
-
export declare const
|
|
170
|
+
export declare const diffPatchCache: C.Cache<string>;
|
|
171
171
|
export declare const diffFilesCache: C.Cache<DiffFile[]>;
|
|
172
172
|
export declare const copyUnignoredFilesCache: {
|
|
173
173
|
name: string;
|
package/dist/cache.server.js
CHANGED
|
@@ -168,7 +168,7 @@ export const solutionAppCache = makeSingletonFsCache('SolutionAppCache');
|
|
|
168
168
|
export const problemAppCache = makeSingletonFsCache('ProblemAppCache');
|
|
169
169
|
export const extraAppCache = makeSingletonFsCache('ExtraAppCache');
|
|
170
170
|
export const playgroundAppCache = makeSingletonFsCache('PlaygroundAppCache');
|
|
171
|
-
export const
|
|
171
|
+
export const diffPatchCache = makeSingletonFsCache('DiffPatchCache');
|
|
172
172
|
export const diffFilesCache = makeSingletonFsCache('DiffFilesCache');
|
|
173
173
|
export const copyUnignoredFilesCache = makeSingletonCache('CopyUnignoredFilesCache');
|
|
174
174
|
export const compiledMarkdownCache = makeSingletonFsCache('CompiledMarkdownCache');
|
package/dist/diff.server.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare function getDiffFiles(app1: App, app2: App, { forceFresh, timings
|
|
|
9
9
|
path: string;
|
|
10
10
|
line: number;
|
|
11
11
|
}[]>;
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function getDiffPatch(app1: App, app2: App, { forceFresh, timings, request, }?: {
|
|
13
13
|
forceFresh?: boolean;
|
|
14
14
|
timings?: Timings;
|
|
15
15
|
request?: Request;
|
package/dist/diff.server.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
// oxlint-disable-next-line import/order -- this must be first
|
|
2
|
-
import { getEnv } from "./init-env.js";
|
|
3
1
|
import os from 'os';
|
|
4
2
|
import path from 'path';
|
|
5
3
|
import { execa } from 'execa';
|
|
6
4
|
import fsExtra from 'fs-extra';
|
|
7
5
|
import ignore from 'ignore';
|
|
8
6
|
import parseGitDiff from 'parse-git-diff';
|
|
9
|
-
import { bundledLanguagesInfo } from 'shiki/langs';
|
|
10
7
|
import { z } from 'zod';
|
|
11
|
-
import { getForceFreshForDir,
|
|
12
|
-
import { cachified, copyUnignoredFilesCache,
|
|
13
|
-
import { compileMarkdownString } from "./compile-mdx.server.js";
|
|
8
|
+
import { getForceFreshForDir, getWorkshopRoot, modifiedTimes, } from "./apps.server.js";
|
|
9
|
+
import { cachified, copyUnignoredFilesCache, diffFilesCache, diffPatchCache, } from "./cache.server.js";
|
|
14
10
|
import { modifiedMoreRecentlyThan } from "./modified-time.server.js";
|
|
15
11
|
const epicshopTempDir = path.join(os.tmpdir(), 'epicshop');
|
|
16
|
-
const isDeployed = getEnv().EPICSHOP_DEPLOYED;
|
|
17
12
|
const diffTmpDir = path.join(epicshopTempDir, 'diff');
|
|
18
13
|
const DiffStatusSchema = z.enum([
|
|
19
14
|
'renamed',
|
|
@@ -65,123 +60,6 @@ function diffPathToRelative(filePath) {
|
|
|
65
60
|
.slice(3);
|
|
66
61
|
return relativePath.join(path.sep);
|
|
67
62
|
}
|
|
68
|
-
function getLanguage(ext) {
|
|
69
|
-
return (bundledLanguagesInfo.find((l) => l.id === ext || l.aliases?.includes(ext))
|
|
70
|
-
?.id ?? 'text');
|
|
71
|
-
}
|
|
72
|
-
function getFileCodeblocks(file, filePathApp1, filePathApp2, type) {
|
|
73
|
-
if (!file.chunks.length) {
|
|
74
|
-
return [
|
|
75
|
-
`<p className="m-0 p-4 border-b text-muted-foreground">No changes</p>`,
|
|
76
|
-
];
|
|
77
|
-
}
|
|
78
|
-
const filepath = diffPathToRelative(file.type === 'RenamedFile' ? file.pathAfter : file.path);
|
|
79
|
-
const extension = path.extname(filepath).slice(1);
|
|
80
|
-
const lang = getLanguage(extension);
|
|
81
|
-
const pathToCopy = file.type === 'RenamedFile' ? file.pathBefore : file.path;
|
|
82
|
-
const relativePath = diffPathToRelative(pathToCopy);
|
|
83
|
-
const markdownLines = [];
|
|
84
|
-
for (const chunk of file.chunks) {
|
|
85
|
-
const removedLineNumbers = [];
|
|
86
|
-
const addedLineNumbers = [];
|
|
87
|
-
const lines = [];
|
|
88
|
-
let toStartLine = 0;
|
|
89
|
-
let startLine = 1;
|
|
90
|
-
if (chunk.type === 'BinaryFilesChunk') {
|
|
91
|
-
lines.push(type === 'AddedFile'
|
|
92
|
-
? `Binary file added`
|
|
93
|
-
: type === 'DeletedFile'
|
|
94
|
-
? 'Binary file deleted'
|
|
95
|
-
: 'Binary file changed');
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
startLine =
|
|
99
|
-
chunk.type === 'Chunk'
|
|
100
|
-
? chunk.fromFileRange.start
|
|
101
|
-
: chunk.type === 'CombinedChunk'
|
|
102
|
-
? chunk.fromFileRangeA.start
|
|
103
|
-
: 1;
|
|
104
|
-
toStartLine = chunk.toFileRange.start;
|
|
105
|
-
for (let lineNumber = 0; lineNumber < chunk.changes.length; lineNumber++) {
|
|
106
|
-
const change = chunk.changes[lineNumber];
|
|
107
|
-
if (!change)
|
|
108
|
-
continue;
|
|
109
|
-
lines.push(change.content);
|
|
110
|
-
switch (change.type) {
|
|
111
|
-
case 'AddedLine': {
|
|
112
|
-
addedLineNumbers.push(startLine + lineNumber);
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
case 'DeletedLine': {
|
|
116
|
-
removedLineNumbers.push(startLine + lineNumber);
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
default: {
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const params = [
|
|
126
|
-
['filename', relativePath.replace(/\\/g, '\\\\')],
|
|
127
|
-
['start', startLine.toString()],
|
|
128
|
-
removedLineNumbers.length
|
|
129
|
-
? ['remove', removedLineNumbers.join(',')]
|
|
130
|
-
: null,
|
|
131
|
-
addedLineNumbers.length ? ['add', addedLineNumbers.join(',')] : null,
|
|
132
|
-
]
|
|
133
|
-
.filter(Boolean)
|
|
134
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
135
|
-
.join(' ');
|
|
136
|
-
const launchEditorClassName = 'border hover:bg-foreground/20 rounded px-2 py-0.5 font-mono text-xs font-semibold';
|
|
137
|
-
function launchEditor(appNum, line) {
|
|
138
|
-
line ||= 1; // handle 0
|
|
139
|
-
if (isDeployed) {
|
|
140
|
-
if (type === 'DeletedFile' && appNum === 2)
|
|
141
|
-
return '';
|
|
142
|
-
if (type === 'AddedFile' && appNum === 1)
|
|
143
|
-
return '';
|
|
144
|
-
}
|
|
145
|
-
const label = (type === 'AddedFile' && appNum === 1) ||
|
|
146
|
-
(type === 'DeletedFile' && appNum === 2)
|
|
147
|
-
? `CREATE in APP ${appNum}`
|
|
148
|
-
: `OPEN in APP ${appNum}`;
|
|
149
|
-
const file = JSON.stringify(appNum === 1 ? filePathApp1 : filePathApp2);
|
|
150
|
-
const fixedTitle = getRelativePath(file);
|
|
151
|
-
return `
|
|
152
|
-
<LaunchEditor file=${file} line={${line}}>
|
|
153
|
-
<span title="${fixedTitle}" className="${launchEditorClassName}">${label}</span>
|
|
154
|
-
</LaunchEditor>`;
|
|
155
|
-
}
|
|
156
|
-
markdownLines.push(`
|
|
157
|
-
<div className="relative">
|
|
158
|
-
|
|
159
|
-
\`\`\`${lang} ${params}
|
|
160
|
-
${lines.join('\n')}
|
|
161
|
-
\`\`\`
|
|
162
|
-
|
|
163
|
-
<div className="flex gap-4 absolute top-1 right-3 items-center">
|
|
164
|
-
${launchEditor(1, startLine)}
|
|
165
|
-
<div className="display-alt-down flex gap-2">
|
|
166
|
-
<LaunchEditor file=${JSON.stringify(filePathApp1)} syncTo={{file: ${JSON.stringify(filePathApp2)}}}>
|
|
167
|
-
<span className="block ${launchEditorClassName}">
|
|
168
|
-
<Icon name="ArrowLeft" title="Copy app 2 file to app 1" />
|
|
169
|
-
</span>
|
|
170
|
-
</LaunchEditor>
|
|
171
|
-
<LaunchEditor file=${JSON.stringify(filePathApp2)} syncTo={{file: ${JSON.stringify(filePathApp1)}}}>
|
|
172
|
-
<span className="block ${launchEditorClassName}">
|
|
173
|
-
<Icon name="ArrowRight" title="Copy app 1 file to app 2" />
|
|
174
|
-
</span>
|
|
175
|
-
</LaunchEditor>
|
|
176
|
-
</div>
|
|
177
|
-
${launchEditor(2, toStartLine)}
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
</div>
|
|
181
|
-
`);
|
|
182
|
-
}
|
|
183
|
-
return markdownLines;
|
|
184
|
-
}
|
|
185
63
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
186
64
|
'**/README.*',
|
|
187
65
|
'**/package-lock.json',
|
|
@@ -308,6 +186,51 @@ export async function getDiffFiles(app1, app2, { forceFresh, timings, request, }
|
|
|
308
186
|
function getAppTestFiles(app) {
|
|
309
187
|
return app.test.type === 'browser' ? app.test.testFiles : [];
|
|
310
188
|
}
|
|
189
|
+
function filterTestFilesFromPatch(patch, testFiles) {
|
|
190
|
+
if (!patch || testFiles.size === 0) {
|
|
191
|
+
return patch;
|
|
192
|
+
}
|
|
193
|
+
const normalizePath = (value) => value.replace(/^\.\/+/, '');
|
|
194
|
+
const parseDiffPaths = (line) => {
|
|
195
|
+
if (!line.startsWith('diff --git '))
|
|
196
|
+
return null;
|
|
197
|
+
const rest = line.slice('diff --git '.length);
|
|
198
|
+
const quotedMatch = rest.match(/^"a\/(.+)" "b\/(.+)"$/);
|
|
199
|
+
if (quotedMatch?.[1] && quotedMatch?.[2]) {
|
|
200
|
+
return {
|
|
201
|
+
a: normalizePath(quotedMatch[1]),
|
|
202
|
+
b: normalizePath(quotedMatch[2]),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const match = rest.match(/^a\/(.+) b\/(.+)$/);
|
|
206
|
+
if (match?.[1] && match?.[2]) {
|
|
207
|
+
return { a: normalizePath(match[1]), b: normalizePath(match[2]) };
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
};
|
|
211
|
+
const lines = patch.split('\n');
|
|
212
|
+
const filtered = [];
|
|
213
|
+
let currentBlock = [];
|
|
214
|
+
let includeBlock = true;
|
|
215
|
+
const flushBlock = () => {
|
|
216
|
+
if (currentBlock.length > 0 && includeBlock) {
|
|
217
|
+
filtered.push(...currentBlock);
|
|
218
|
+
}
|
|
219
|
+
currentBlock = [];
|
|
220
|
+
};
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
if (line.startsWith('diff --git ')) {
|
|
223
|
+
flushBlock();
|
|
224
|
+
const paths = parseDiffPaths(line);
|
|
225
|
+
includeBlock = paths
|
|
226
|
+
? !testFiles.has(paths.a) && !testFiles.has(paths.b)
|
|
227
|
+
: true;
|
|
228
|
+
}
|
|
229
|
+
currentBlock.push(line);
|
|
230
|
+
}
|
|
231
|
+
flushBlock();
|
|
232
|
+
return filtered.join('\n');
|
|
233
|
+
}
|
|
311
234
|
async function getDiffFilesImpl(app1, app2) {
|
|
312
235
|
if (app1.name === app2.name) {
|
|
313
236
|
return [];
|
|
@@ -344,26 +267,23 @@ async function getDiffFilesImpl(app1, app2) {
|
|
|
344
267
|
}))
|
|
345
268
|
.filter((file) => !testFiles.includes(file.path));
|
|
346
269
|
}
|
|
347
|
-
export async function
|
|
270
|
+
export async function getDiffPatch(app1, app2, { forceFresh, timings, request, } = {}) {
|
|
348
271
|
const key = `${app1.relativePath}__vs__${app2.relativePath}`;
|
|
349
|
-
const cacheEntry = await
|
|
272
|
+
const cacheEntry = await diffPatchCache.get(key);
|
|
350
273
|
const result = await cachified({
|
|
351
274
|
key,
|
|
352
|
-
cache:
|
|
275
|
+
cache: diffPatchCache,
|
|
353
276
|
forceFresh: forceFresh || (await getForceFreshForDiff(app1, app2, cacheEntry)),
|
|
354
277
|
timings,
|
|
355
278
|
request,
|
|
356
279
|
checkValue: z.string(),
|
|
357
|
-
getFreshValue: () =>
|
|
280
|
+
getFreshValue: () => getDiffPatchImpl(app1, app2),
|
|
358
281
|
});
|
|
359
282
|
return result;
|
|
360
283
|
}
|
|
361
|
-
async function
|
|
362
|
-
const markdownLines = [''];
|
|
284
|
+
async function getDiffPatchImpl(app1, app2) {
|
|
363
285
|
if (app1.name === app2.name) {
|
|
364
|
-
|
|
365
|
-
const code = await compileMarkdownString(markdownLines.join('\n'));
|
|
366
|
-
return code;
|
|
286
|
+
return '';
|
|
367
287
|
}
|
|
368
288
|
const { app1CopyPath, app2CopyPath } = await prepareForDiff(app1, app2);
|
|
369
289
|
const { stdout: diffOutput } = await execa('git', [
|
|
@@ -373,83 +293,28 @@ async function getDiffCodeImpl(app1, app2) {
|
|
|
373
293
|
app2CopyPath,
|
|
374
294
|
'--color=never',
|
|
375
295
|
'--color-moved-ws=allow-indentation-change',
|
|
376
|
-
'--no-prefix',
|
|
377
296
|
'--ignore-blank-lines',
|
|
378
297
|
'--ignore-space-change',
|
|
379
298
|
], { cwd: diffTmpDir }).catch((e) => e);
|
|
380
299
|
void fsExtra.remove(app1CopyPath).catch(() => { });
|
|
381
300
|
void fsExtra.remove(app2CopyPath).catch(() => { });
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
switch (file.type) {
|
|
400
|
-
case 'ChangedFile': {
|
|
401
|
-
markdownLines.push(`
|
|
402
|
-
|
|
403
|
-
<Accordion title=${JSON.stringify(relativePath)} variant="changed">
|
|
404
|
-
|
|
405
|
-
${getFileCodeblocks(file, filePathApp1, filePathApp2, file.type).join('\n')}
|
|
406
|
-
|
|
407
|
-
</Accordion>
|
|
408
|
-
|
|
409
|
-
`);
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
case 'DeletedFile': {
|
|
413
|
-
markdownLines.push(`
|
|
414
|
-
<Accordion title=${JSON.stringify(relativePath)} variant="deleted">
|
|
415
|
-
|
|
416
|
-
${getFileCodeblocks(file, filePathApp1, filePathApp2, file.type).join('\n')}
|
|
417
|
-
|
|
418
|
-
</Accordion>
|
|
419
|
-
`);
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
case 'RenamedFile': {
|
|
423
|
-
const relativeBefore = diffPathToRelative(file.pathBefore);
|
|
424
|
-
const relativeAfter = diffPathToRelative(file.pathAfter);
|
|
425
|
-
const title = JSON.stringify(`${relativeBefore} ▶️ ${relativeAfter}`);
|
|
426
|
-
markdownLines.push(`
|
|
427
|
-
<Accordion title=${title} variant="renamed">
|
|
428
|
-
|
|
429
|
-
${getFileCodeblocks(file, filePathApp1, filePathApp2, file.type).join('\n')}
|
|
430
|
-
|
|
431
|
-
</Accordion>
|
|
432
|
-
`);
|
|
433
|
-
break;
|
|
434
|
-
}
|
|
435
|
-
case 'AddedFile': {
|
|
436
|
-
markdownLines.push(`
|
|
437
|
-
<Accordion title=${JSON.stringify(relativePath)} variant="added">
|
|
438
|
-
|
|
439
|
-
${getFileCodeblocks(file, filePathApp1, filePathApp2, file.type).join('\n')}
|
|
440
|
-
|
|
441
|
-
</Accordion>
|
|
442
|
-
`);
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
default: {
|
|
446
|
-
console.error(file);
|
|
447
|
-
throw new Error(`Unknown file type: ${file}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const code = await compileMarkdownString(markdownLines.join('\n'));
|
|
452
|
-
return code;
|
|
301
|
+
const normalizedOutput = String(diffOutput ?? '');
|
|
302
|
+
const app1Relative = app1CopyPath.slice(1);
|
|
303
|
+
const app2Relative = app2CopyPath.slice(1);
|
|
304
|
+
const testFiles = new Set([
|
|
305
|
+
...getAppTestFiles(app1),
|
|
306
|
+
...getAppTestFiles(app2),
|
|
307
|
+
]);
|
|
308
|
+
const filteredOutput = filterTestFilesFromPatch(normalizedOutput
|
|
309
|
+
.replaceAll(`a${app1CopyPath}`, 'a')
|
|
310
|
+
.replaceAll(`b${app1CopyPath}`, 'b')
|
|
311
|
+
.replaceAll(`a${app2CopyPath}`, 'a')
|
|
312
|
+
.replaceAll(`b${app2CopyPath}`, 'b')
|
|
313
|
+
.replaceAll(`${app1CopyPath}/`, '')
|
|
314
|
+
.replaceAll(`${app2CopyPath}/`, '')
|
|
315
|
+
.replaceAll(`${app1Relative}/`, '')
|
|
316
|
+
.replaceAll(`${app2Relative}/`, ''), testFiles);
|
|
317
|
+
return filteredOutput;
|
|
453
318
|
}
|
|
454
319
|
export async function getDiffOutputWithRelativePaths(app1, app2) {
|
|
455
320
|
const { app1CopyPath, app2CopyPath } = await prepareForDiff(app1, app2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epic-web/workshop-utils",
|
|
3
|
-
"version": "6.85.
|
|
3
|
+
"version": "6.85.5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -237,7 +237,6 @@
|
|
|
237
237
|
"remark-emoji": "^5.0.2",
|
|
238
238
|
"remark-gfm": "^4.0.1",
|
|
239
239
|
"shell-quote": "^1.8.3",
|
|
240
|
-
"shiki": "^3.22.0",
|
|
241
240
|
"unified": "^11.0.5",
|
|
242
241
|
"unist-util-remove-position": "^5.0.0",
|
|
243
242
|
"unist-util-visit": "^5.1.0",
|