@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.
@@ -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 diffCodeCache: C.Cache<string>;
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;
@@ -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 diffCodeCache = makeSingletonFsCache('DiffCodeCache');
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');
@@ -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 getDiffCode(app1: App, app2: App, { forceFresh, timings, request, }?: {
12
+ export declare function getDiffPatch(app1: App, app2: App, { forceFresh, timings, request, }?: {
13
13
  forceFresh?: boolean;
14
14
  timings?: Timings;
15
15
  request?: Request;
@@ -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, getRelativePath, getWorkshopRoot, modifiedTimes, } from "./apps.server.js";
12
- import { cachified, copyUnignoredFilesCache, diffCodeCache, diffFilesCache, } from "./cache.server.js";
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 getDiffCode(app1, app2, { forceFresh, timings, request, } = {}) {
270
+ export async function getDiffPatch(app1, app2, { forceFresh, timings, request, } = {}) {
348
271
  const key = `${app1.relativePath}__vs__${app2.relativePath}`;
349
- const cacheEntry = await diffCodeCache.get(key);
272
+ const cacheEntry = await diffPatchCache.get(key);
350
273
  const result = await cachified({
351
274
  key,
352
- cache: diffCodeCache,
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: () => getDiffCodeImpl(app1, app2),
280
+ getFreshValue: () => getDiffPatchImpl(app1, app2),
358
281
  });
359
282
  return result;
360
283
  }
361
- async function getDiffCodeImpl(app1, app2) {
362
- const markdownLines = [''];
284
+ async function getDiffPatchImpl(app1, app2) {
363
285
  if (app1.name === app2.name) {
364
- markdownLines.push('<p className="p-4 text-center">You are comparing the same app</p>');
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 parsed = parseGitDiff(diffOutput);
383
- if (!parsed.files.length) {
384
- markdownLines.push('<div className="m-5 inline-flex items-center justify-center bg-foreground px-1 py-0.5 font-mono text-sm uppercase text-background">No changes</div>');
385
- }
386
- const app1TestFiles = getAppTestFiles(app1);
387
- const app2TestFiles = getAppTestFiles(app2);
388
- for (const file of parsed.files) {
389
- const pathToCopy = file.type === 'RenamedFile' ? file.pathBefore : file.path;
390
- const relativePath = diffPathToRelative(pathToCopy);
391
- if (app1TestFiles.includes(relativePath))
392
- continue;
393
- const filePathApp1 = path.join(app1.fullPath, relativePath);
394
- const pathToApp2 = file.type === 'RenamedFile' ? file.pathAfter : file.path;
395
- const relativePathApp2 = diffPathToRelative(pathToApp2);
396
- if (app2TestFiles.includes(relativePathApp2))
397
- continue;
398
- const filePathApp2 = path.join(app2.fullPath, relativePathApp2);
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.4",
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",