@teambit/component-compare 1.0.1080 → 1.0.1082
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/component-compare-page.module.scss +124 -0
- package/component-compare-page.tsx +509 -0
- package/component-compare.ui.runtime.tsx +98 -16
- package/dist/compare-component-pairs.d.ts +20 -0
- package/dist/compare-component-pairs.js +36 -0
- package/dist/compare-component-pairs.js.map +1 -0
- package/dist/compare-component-pairs.spec.d.ts +1 -0
- package/dist/compare-component-pairs.spec.js +94 -0
- package/dist/compare-component-pairs.spec.js.map +1 -0
- package/dist/component-compare-page.d.ts +16 -0
- package/dist/component-compare-page.js +504 -0
- package/dist/component-compare-page.js.map +1 -0
- package/dist/component-compare-page.module.scss +124 -0
- package/dist/component-compare.graphql.js +34 -0
- package/dist/component-compare.graphql.js.map +1 -1
- package/dist/component-compare.main.runtime.d.ts +64 -4
- package/dist/component-compare.main.runtime.js +187 -6
- package/dist/component-compare.main.runtime.js.map +1 -1
- package/dist/component-compare.ui.runtime.d.ts +43 -3
- package/dist/component-compare.ui.runtime.js +83 -21
- package/dist/component-compare.ui.runtime.js.map +1 -1
- package/dist/diff-cmd.d.ts +3 -3
- package/dist/{preview-1785267101230.js → preview-1785362864561.js} +2 -2
- package/package.json +35 -21
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
.page {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
|
+
height: 100%;
|
|
6
|
+
min-height: 0;
|
|
7
|
+
background: var(--surface-base-color, #fff);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.versionPickerRow {
|
|
11
|
+
flex: 0 0 auto;
|
|
12
|
+
padding: 8px 16px;
|
|
13
|
+
border-bottom: 1px solid var(--border-color, #e6e8eb);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.blankState {
|
|
17
|
+
flex: 1 1 auto;
|
|
18
|
+
min-height: 0;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes compareSkeletonShimmer {
|
|
25
|
+
0% {
|
|
26
|
+
background-position: 200% 0;
|
|
27
|
+
}
|
|
28
|
+
100% {
|
|
29
|
+
background-position: -200% 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
%compareSkeletonShape {
|
|
34
|
+
border-radius: 6px;
|
|
35
|
+
background: linear-gradient(
|
|
36
|
+
90deg,
|
|
37
|
+
var(--surface-neutral-color, #f0f2f5) 25%,
|
|
38
|
+
var(--surface-hover-color, #e8ecf0) 37%,
|
|
39
|
+
var(--surface-neutral-color, #f0f2f5) 63%
|
|
40
|
+
);
|
|
41
|
+
background-size: 400% 100%;
|
|
42
|
+
animation: compareSkeletonShimmer 1.4s ease infinite;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.toolbarSkeleton {
|
|
46
|
+
flex: 0 0 auto;
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
padding: 10px 16px;
|
|
50
|
+
border-bottom: 1px solid var(--border-color, #e6e8eb);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.skelPill {
|
|
54
|
+
@extend %compareSkeletonShape;
|
|
55
|
+
width: 92px;
|
|
56
|
+
height: 28px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.bodySkeleton {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 10px;
|
|
63
|
+
padding: 16px 20px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.skelHeader {
|
|
67
|
+
@extend %compareSkeletonShape;
|
|
68
|
+
width: 40%;
|
|
69
|
+
height: 16px;
|
|
70
|
+
margin-bottom: 8px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.skelLine {
|
|
74
|
+
@extend %compareSkeletonShape;
|
|
75
|
+
height: 12px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.diffPane {
|
|
79
|
+
flex: 1 1 auto;
|
|
80
|
+
min-height: 0;
|
|
81
|
+
// shrink below content width so a wide diff scrolls inside its own file body instead of forcing
|
|
82
|
+
// the whole page horizontally wider.
|
|
83
|
+
min-width: 0;
|
|
84
|
+
overflow: auto;
|
|
85
|
+
// No padding/gap on the scroll container itself: the sticky component header (top:
|
|
86
|
+
// `--group-header-height`) and diff-file headers (top: `--group-header-height` +
|
|
87
|
+
// `--component-header-height`) are measured from the scrollport edge, so any padding here
|
|
88
|
+
// offsets them and they stop stacking flush. Inner cards/headers carry their own padding
|
|
89
|
+
// (mirrors lane-compare's padding-less `.diffPane`).
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
|
|
93
|
+
// Tab visibility driven by `data-view-mode` on the pane. All `DeferredTab` siblings are mounted
|
|
94
|
+
// simultaneously so their internal query state survives mode switches; CSS hides the non-active
|
|
95
|
+
// tabs. Mirror of lane-compare's panel selectors so the same inline-* tabs work in both pages.
|
|
96
|
+
&[data-view-mode='code'] [data-tab-id]:not([data-tab-id='inline-code']) {
|
|
97
|
+
display: none;
|
|
98
|
+
}
|
|
99
|
+
&[data-view-mode='preview'] [data-tab-id]:not([data-tab-id='inline-preview']) {
|
|
100
|
+
display: none;
|
|
101
|
+
}
|
|
102
|
+
&[data-view-mode='dependencies'] [data-tab-id]:not([data-tab-id='inline-deps']) {
|
|
103
|
+
display: none;
|
|
104
|
+
}
|
|
105
|
+
&[data-view-mode='tests'] [data-tab-id]:not([data-tab-id='inline-tests']) {
|
|
106
|
+
display: none;
|
|
107
|
+
}
|
|
108
|
+
&[data-view-mode='config'] [data-tab-id]:not([data-tab-id='inline-config']) {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
111
|
+
&[data-view-mode='docs'] [data-tab-id]:not([data-tab-id='inline-docs']) {
|
|
112
|
+
display: none;
|
|
113
|
+
}
|
|
114
|
+
// The API view is not an inline `[data-tab-id]` tab — it's rendered as a direct child of the
|
|
115
|
+
// inline compare (only while active). Hide every inline tab so only the API element shows.
|
|
116
|
+
&[data-view-mode='api'] [data-tab-id] {
|
|
117
|
+
display: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// reveal the header note a view registered for the active view (e.g. the deps change tally).
|
|
121
|
+
&[data-view-mode='dependencies'] [data-header-extra-view='dependencies'] {
|
|
122
|
+
display: inline-flex;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -35,14 +35,25 @@ function componentCompareSchema(componentCompareMain) {
|
|
|
35
35
|
type ComponentCompareResult {
|
|
36
36
|
# unique id for graphql - baseId + compareId
|
|
37
37
|
id: String!
|
|
38
|
+
baseId: String!
|
|
39
|
+
compareId: String!
|
|
38
40
|
code(fileName: String): [FileCompareResult!]!
|
|
39
41
|
aspects(aspectName: String): [FieldCompareResult!]!
|
|
40
42
|
tests(fileName: String): [FileCompareResult!]
|
|
41
43
|
api: APIDiffResult
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
input ComponentComparePair {
|
|
47
|
+
baseId: String!
|
|
48
|
+
compareId: String!
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
extend type ComponentHost {
|
|
45
52
|
compareComponent(baseId: String!, compareId: String!): ComponentCompareResult
|
|
53
|
+
# bulk compare a paginated slice of pairs; an element is null if that pair failed to compare
|
|
54
|
+
compareComponents(pairs: [ComponentComparePair!]!, offset: Int, limit: Int): [ComponentCompareResult]!
|
|
55
|
+
# bulk api-diff a paginated slice of pairs; an element is null if that pair's diff couldn't be computed
|
|
56
|
+
apiDiffs(pairs: [ComponentComparePair!]!, offset: Int, limit: Int): [APIDiffResult]!
|
|
46
57
|
}
|
|
47
58
|
`,
|
|
48
59
|
resolvers: {
|
|
@@ -52,6 +63,29 @@ function componentCompareSchema(componentCompareMain) {
|
|
|
52
63
|
compareId
|
|
53
64
|
}) => {
|
|
54
65
|
return componentCompareMain.compare(baseId, compareId);
|
|
66
|
+
},
|
|
67
|
+
compareComponents: async (_, {
|
|
68
|
+
pairs,
|
|
69
|
+
offset,
|
|
70
|
+
limit
|
|
71
|
+
}) => {
|
|
72
|
+
return componentCompareMain.compareComponents(pairs, {
|
|
73
|
+
offset,
|
|
74
|
+
limit
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
apiDiffs: async (_, {
|
|
78
|
+
pairs,
|
|
79
|
+
offset,
|
|
80
|
+
limit
|
|
81
|
+
}) => {
|
|
82
|
+
// each element is the plain record `getAPIDiff` returns (or null); the APIDiffResult
|
|
83
|
+
// fields resolve from it via graphql's default field resolvers, same as the single
|
|
84
|
+
// `apiDiff` resolver in @teambit/semantics.schema.
|
|
85
|
+
return componentCompareMain.apiDiffs(pairs, {
|
|
86
|
+
offset,
|
|
87
|
+
limit
|
|
88
|
+
});
|
|
55
89
|
}
|
|
56
90
|
},
|
|
57
91
|
ComponentCompareResult: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_graphqlTag","data","require","ownKeys","e","r","t","Object","keys","getOwnPropertySymbols","o","filter","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty","getOwnPropertyDescriptors","defineProperties","defineProperty","_toPropertyKey","value","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","componentCompareSchema","componentCompareMain","typeDefs","gql","resolvers","ComponentHost","compareComponent","_","baseId","compareId","compare","ComponentCompareResult","id","result","code","fileName","codeFile","filePath","map","c","baseContent","fromContent","compareContent","toContent","tests","testFile","aspects","fieldName","fields","field","api","getAPIDiff"],"sources":["component-compare.graphql.ts"],"sourcesContent":["import { gql } from 'graphql-tag';\nimport type { Schema } from '@teambit/graphql';\nimport type { ComponentCompareMain, ComponentCompareResult } from './component-compare.main.runtime';\n\nexport function componentCompareSchema(componentCompareMain: ComponentCompareMain): Schema {\n return {\n typeDefs: gql`\n type FileCompareResult {\n fileName: String!\n baseContent: String!\n compareContent: String!\n status: String\n diffOutput: String\n }\n\n type FieldCompareResult {\n fieldName: String!\n diffOutput: String\n }\n\n type ComponentCompareResult {\n # unique id for graphql - baseId + compareId\n id: String!\n code(fileName: String): [FileCompareResult!]!\n aspects(aspectName: String): [FieldCompareResult!]!\n tests(fileName: String): [FileCompareResult!]\n api: APIDiffResult\n }\n\n extend type ComponentHost {\n compareComponent(baseId: String!, compareId: String!): ComponentCompareResult\n }\n `,\n resolvers: {\n ComponentHost: {\n compareComponent: async (_, { baseId, compareId }: { baseId: string; compareId: string }) => {\n return componentCompareMain.compare(baseId, compareId);\n },\n },\n ComponentCompareResult: {\n id: (result: ComponentCompareResult) => result.id,\n code: (result: ComponentCompareResult, { fileName }: { fileName?: string }) => {\n if (fileName) {\n return result.code\n .filter((codeFile) => codeFile.filePath === fileName)\n .map((c) => ({ ...c, fileName: c.filePath, baseContent: c.fromContent, compareContent: c.toContent }));\n }\n\n return result.code.map((c) => ({\n ...c,\n fileName: c.filePath,\n baseContent: c.fromContent,\n compareContent: c.toContent,\n }));\n },\n tests: (result: ComponentCompareResult, { fileName }: { fileName?: string }) => {\n if (fileName) {\n return result.tests\n .filter((testFile) => testFile.filePath === fileName)\n .map((c) => ({ ...c, fileName: c.filePath, baseContent: c.fromContent, compareContent: c.toContent }));\n }\n\n return result.tests.map((c) => ({\n ...c,\n fileName: c.filePath,\n baseContent: c.fromContent,\n compareContent: c.toContent,\n }));\n },\n aspects: (result: ComponentCompareResult, { fieldName }: { fieldName?: string }) => {\n if (fieldName) {\n return result.fields.filter((field) => field.fieldName === fieldName);\n }\n return result.fields;\n },\n api: async (result: ComponentCompareResult) => {\n return (await componentCompareMain.getAPIDiff(result.baseId, result.compareId)) ?? null;\n },\n },\n },\n };\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"names":["_graphqlTag","data","require","ownKeys","e","r","t","Object","keys","getOwnPropertySymbols","o","filter","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty","getOwnPropertyDescriptors","defineProperties","defineProperty","_toPropertyKey","value","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","componentCompareSchema","componentCompareMain","typeDefs","gql","resolvers","ComponentHost","compareComponent","_","baseId","compareId","compare","compareComponents","pairs","offset","limit","apiDiffs","ComponentCompareResult","id","result","code","fileName","codeFile","filePath","map","c","baseContent","fromContent","compareContent","toContent","tests","testFile","aspects","fieldName","fields","field","api","getAPIDiff"],"sources":["component-compare.graphql.ts"],"sourcesContent":["import { gql } from 'graphql-tag';\nimport type { Schema } from '@teambit/graphql';\nimport type { ComponentCompareMain, ComponentCompareResult } from './component-compare.main.runtime';\n\nexport function componentCompareSchema(componentCompareMain: ComponentCompareMain): Schema {\n return {\n typeDefs: gql`\n type FileCompareResult {\n fileName: String!\n baseContent: String!\n compareContent: String!\n status: String\n diffOutput: String\n }\n\n type FieldCompareResult {\n fieldName: String!\n diffOutput: String\n }\n\n type ComponentCompareResult {\n # unique id for graphql - baseId + compareId\n id: String!\n baseId: String!\n compareId: String!\n code(fileName: String): [FileCompareResult!]!\n aspects(aspectName: String): [FieldCompareResult!]!\n tests(fileName: String): [FileCompareResult!]\n api: APIDiffResult\n }\n\n input ComponentComparePair {\n baseId: String!\n compareId: String!\n }\n\n extend type ComponentHost {\n compareComponent(baseId: String!, compareId: String!): ComponentCompareResult\n # bulk compare a paginated slice of pairs; an element is null if that pair failed to compare\n compareComponents(pairs: [ComponentComparePair!]!, offset: Int, limit: Int): [ComponentCompareResult]!\n # bulk api-diff a paginated slice of pairs; an element is null if that pair's diff couldn't be computed\n apiDiffs(pairs: [ComponentComparePair!]!, offset: Int, limit: Int): [APIDiffResult]!\n }\n `,\n resolvers: {\n ComponentHost: {\n compareComponent: async (_, { baseId, compareId }: { baseId: string; compareId: string }) => {\n return componentCompareMain.compare(baseId, compareId);\n },\n compareComponents: async (\n _,\n {\n pairs,\n offset,\n limit,\n }: { pairs: Array<{ baseId: string; compareId: string }>; offset?: number; limit?: number }\n ) => {\n return componentCompareMain.compareComponents(pairs, { offset, limit });\n },\n apiDiffs: async (\n _,\n {\n pairs,\n offset,\n limit,\n }: { pairs: Array<{ baseId: string; compareId: string }>; offset?: number; limit?: number }\n ) => {\n // each element is the plain record `getAPIDiff` returns (or null); the APIDiffResult\n // fields resolve from it via graphql's default field resolvers, same as the single\n // `apiDiff` resolver in @teambit/semantics.schema.\n return componentCompareMain.apiDiffs(pairs, { offset, limit });\n },\n },\n ComponentCompareResult: {\n id: (result: ComponentCompareResult) => result.id,\n code: (result: ComponentCompareResult, { fileName }: { fileName?: string }) => {\n if (fileName) {\n return result.code\n .filter((codeFile) => codeFile.filePath === fileName)\n .map((c) => ({ ...c, fileName: c.filePath, baseContent: c.fromContent, compareContent: c.toContent }));\n }\n\n return result.code.map((c) => ({\n ...c,\n fileName: c.filePath,\n baseContent: c.fromContent,\n compareContent: c.toContent,\n }));\n },\n tests: (result: ComponentCompareResult, { fileName }: { fileName?: string }) => {\n if (fileName) {\n return result.tests\n .filter((testFile) => testFile.filePath === fileName)\n .map((c) => ({ ...c, fileName: c.filePath, baseContent: c.fromContent, compareContent: c.toContent }));\n }\n\n return result.tests.map((c) => ({\n ...c,\n fileName: c.filePath,\n baseContent: c.fromContent,\n compareContent: c.toContent,\n }));\n },\n aspects: (result: ComponentCompareResult, { fieldName }: { fieldName?: string }) => {\n if (fieldName) {\n return result.fields.filter((field) => field.fieldName === fieldName);\n }\n return result.fields;\n },\n api: async (result: ComponentCompareResult) => {\n return (await componentCompareMain.getAPIDiff(result.baseId, result.compareId)) ?? null;\n },\n },\n },\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CLIMain } from '@teambit/cli';
|
|
2
2
|
import type { Workspace } from '@teambit/workspace';
|
|
3
|
-
import
|
|
3
|
+
import { ComponentID } from '@teambit/component-id';
|
|
4
4
|
import type { ScopeMain } from '@teambit/scope';
|
|
5
5
|
import type { GraphqlMain } from '@teambit/graphql';
|
|
6
6
|
import type { ModelComponent, Version } from '@teambit/objects';
|
|
@@ -10,7 +10,9 @@ import type { DiffOptions, DiffResults, FieldsDiff, FileDiff } from '@teambit/le
|
|
|
10
10
|
import type { TesterMain } from '@teambit/tester';
|
|
11
11
|
import type { Component, ComponentMain } from '@teambit/component';
|
|
12
12
|
import type { SchemaMain } from '@teambit/schema';
|
|
13
|
+
import type { CacheMain } from '@teambit/cache';
|
|
13
14
|
import type { ImporterMain } from '@teambit/importer';
|
|
15
|
+
import type { ComponentComparePair } from './compare-component-pairs';
|
|
14
16
|
export type ComponentCompareResult = {
|
|
15
17
|
id: string;
|
|
16
18
|
baseId: string;
|
|
@@ -18,6 +20,12 @@ export type ComponentCompareResult = {
|
|
|
18
20
|
code: FileDiff[];
|
|
19
21
|
fields: FieldsDiff[];
|
|
20
22
|
tests: FileDiff[];
|
|
23
|
+
/**
|
|
24
|
+
* true when the compare side is the live workspace (on-disk files, incl. uncommitted changes).
|
|
25
|
+
* such a result is inherently mutable, so it must never be persisted to the cross-run cache —
|
|
26
|
+
* only the in-flight single-flight dedupe applies. not exposed via graphql.
|
|
27
|
+
*/
|
|
28
|
+
isLiveWorkspace?: boolean;
|
|
21
29
|
};
|
|
22
30
|
type ConfigDiff = {
|
|
23
31
|
version?: string;
|
|
@@ -32,10 +40,61 @@ export declare class ComponentCompareMain {
|
|
|
32
40
|
private depResolver;
|
|
33
41
|
private importer;
|
|
34
42
|
private schema;
|
|
43
|
+
private cache;
|
|
35
44
|
private workspace?;
|
|
36
|
-
constructor(componentAspect: ComponentMain, scope: ScopeMain, logger: Logger, tester: TesterMain, depResolver: DependencyResolverMain, importer: ImporterMain, schema: SchemaMain, workspace?: Workspace | undefined);
|
|
45
|
+
constructor(componentAspect: ComponentMain, scope: ScopeMain, logger: Logger, tester: TesterMain, depResolver: DependencyResolverMain, importer: ImporterMain, schema: SchemaMain, cache: CacheMain, workspace?: Workspace | undefined);
|
|
46
|
+
private compareInflight;
|
|
47
|
+
private apiDiffInflight;
|
|
48
|
+
/**
|
|
49
|
+
* Read-through cache with single-flight dedupe: serve a persisted result, else share an in-flight
|
|
50
|
+
* computation, else compute once and persist. Most `(baseId, compareId)` pairs are immutable (keyed
|
|
51
|
+
* on snap hashes), so a cached result never goes stale. `cacheable` gates which results are persisted.
|
|
52
|
+
*
|
|
53
|
+
* `skipPersistentCache` bypasses the persistent cache entirely (neither read nor write) while still
|
|
54
|
+
* sharing the in-flight computation. Callers must set it whenever the result depends on mutable state
|
|
55
|
+
* the key does not capture — e.g. a live-workspace diff against on-disk files — so a previously
|
|
56
|
+
* persisted snap-to-snap result for the same key is never served in its place.
|
|
57
|
+
*/
|
|
58
|
+
private getOrCompute;
|
|
37
59
|
compare(baseIdStr: string, compareIdStr: string): Promise<ComponentCompareResult>;
|
|
60
|
+
/**
|
|
61
|
+
* cheap, synchronous pre-check mirroring the `comparingWithLocalChanges` / `compareIsLiveWorkspace`
|
|
62
|
+
* logic in `computeCompare`: will this compare diff against live on-disk workspace files rather than
|
|
63
|
+
* two immutable snaps? errs toward `true` (skip the persistent cache) whenever the id cannot be
|
|
64
|
+
* classified, so a stale snap-to-snap result is never served in place of a live one.
|
|
65
|
+
*/
|
|
66
|
+
private comparesLiveWorkspace;
|
|
67
|
+
/**
|
|
68
|
+
* whether this id refers to the component version currently checked out on disk — the one case
|
|
69
|
+
* where "the same versioned id" can produce different data over time (the user edits files). errs
|
|
70
|
+
* toward `true` when the id cannot be classified, so a stale cached result is never served.
|
|
71
|
+
*/
|
|
72
|
+
private isLiveCheckout;
|
|
73
|
+
/** The original `compare()` body — moved here so the public method can wrap with memo + single-flight. */
|
|
74
|
+
private computeCompare;
|
|
75
|
+
/**
|
|
76
|
+
* compare a paginated slice of component pairs in one call.
|
|
77
|
+
* a pair that fails to compare (e.g. a component without versions) becomes `null` in the
|
|
78
|
+
* returned array rather than failing the whole batch. the array is aligned to the requested
|
|
79
|
+
* slice (`pairs[offset .. offset + limit]`).
|
|
80
|
+
*/
|
|
81
|
+
compareComponents(pairs: ComponentComparePair[], options?: {
|
|
82
|
+
offset?: number;
|
|
83
|
+
limit?: number;
|
|
84
|
+
}): Promise<Array<ComponentCompareResult | null>>;
|
|
85
|
+
/**
|
|
86
|
+
* api-diff a paginated slice of component pairs in one call — the bulk counterpart of the single
|
|
87
|
+
* `getAPIDiff`, mirroring `compareComponents`. reuses `getAPIDiff` per pair (so its disk memo +
|
|
88
|
+
* single-flight dedupe still apply), turning a pair whose diff throws into `null` rather than
|
|
89
|
+
* failing the whole batch. the returned array is aligned to the requested slice.
|
|
90
|
+
*/
|
|
91
|
+
apiDiffs(pairs: ComponentComparePair[], options?: {
|
|
92
|
+
offset?: number;
|
|
93
|
+
limit?: number;
|
|
94
|
+
}): Promise<Array<Record<string, any> | null>>;
|
|
95
|
+
private static isApiDiffCacheable;
|
|
38
96
|
getAPIDiff(baseIdStr: string, compareIdStr: string): Promise<Record<string, any> | null>;
|
|
97
|
+
private computeAPIDiff;
|
|
39
98
|
diffByCLIValues(pattern?: string, version?: string, toVersion?: string, { verbose, table, parent }?: {
|
|
40
99
|
verbose?: boolean;
|
|
41
100
|
table?: boolean;
|
|
@@ -58,7 +117,7 @@ export declare class ComponentCompareMain {
|
|
|
58
117
|
static slots: never[];
|
|
59
118
|
static dependencies: import("@teambit/harmony").Aspect[];
|
|
60
119
|
static runtime: import("@teambit/harmony").RuntimeDefinition;
|
|
61
|
-
static provider([graphql, component, scope, loggerMain, cli, workspace, tester, depResolver, importer, schema,]: [
|
|
120
|
+
static provider([graphql, component, scope, loggerMain, cli, workspace, tester, depResolver, importer, schema, cache,]: [
|
|
62
121
|
GraphqlMain,
|
|
63
122
|
ComponentMain,
|
|
64
123
|
ScopeMain,
|
|
@@ -68,7 +127,8 @@ export declare class ComponentCompareMain {
|
|
|
68
127
|
TesterMain,
|
|
69
128
|
DependencyResolverMain,
|
|
70
129
|
ImporterMain,
|
|
71
|
-
SchemaMain
|
|
130
|
+
SchemaMain,
|
|
131
|
+
CacheMain
|
|
72
132
|
]): Promise<ComponentCompareMain>;
|
|
73
133
|
}
|
|
74
134
|
export default ComponentCompareMain;
|
|
@@ -102,6 +102,13 @@ function _schema() {
|
|
|
102
102
|
};
|
|
103
103
|
return data;
|
|
104
104
|
}
|
|
105
|
+
function _cache() {
|
|
106
|
+
const data = require("@teambit/cache");
|
|
107
|
+
_cache = function () {
|
|
108
|
+
return data;
|
|
109
|
+
};
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
105
112
|
function _componentCompare() {
|
|
106
113
|
const data = require("./component-compare.graphql");
|
|
107
114
|
_componentCompare = function () {
|
|
@@ -130,11 +137,31 @@ function _importer() {
|
|
|
130
137
|
};
|
|
131
138
|
return data;
|
|
132
139
|
}
|
|
140
|
+
function _harmonyModules() {
|
|
141
|
+
const data = require("@teambit/harmony.modules.concurrency");
|
|
142
|
+
_harmonyModules = function () {
|
|
143
|
+
return data;
|
|
144
|
+
};
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
function _compareComponentPairs() {
|
|
148
|
+
const data = require("./compare-component-pairs");
|
|
149
|
+
_compareComponentPairs = function () {
|
|
150
|
+
return data;
|
|
151
|
+
};
|
|
152
|
+
return data;
|
|
153
|
+
}
|
|
133
154
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
134
155
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
135
156
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
157
|
+
/**
|
|
158
|
+
* expiry for persisted compare/api-diff results. the results themselves are immutable (keyed on snap
|
|
159
|
+
* hashes), but the payloads are heavy — full per-file contents per pair — so without a TTL the cache
|
|
160
|
+
* directory grows with every pair ever viewed. two weeks comfortably covers a review cycle.
|
|
161
|
+
*/
|
|
162
|
+
const PERSISTENT_CACHE_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
136
163
|
class ComponentCompareMain {
|
|
137
|
-
constructor(componentAspect, scope, logger, tester, depResolver, importer, schema, workspace) {
|
|
164
|
+
constructor(componentAspect, scope, logger, tester, depResolver, importer, schema, cache, workspace) {
|
|
138
165
|
this.componentAspect = componentAspect;
|
|
139
166
|
this.scope = scope;
|
|
140
167
|
this.logger = logger;
|
|
@@ -142,9 +169,89 @@ class ComponentCompareMain {
|
|
|
142
169
|
this.depResolver = depResolver;
|
|
143
170
|
this.importer = importer;
|
|
144
171
|
this.schema = schema;
|
|
172
|
+
this.cache = cache;
|
|
145
173
|
this.workspace = workspace;
|
|
174
|
+
// in-flight `compute` promises, so concurrent callers for the same pair share one computation
|
|
175
|
+
// instead of recomputing (the lane compare UI and lane-diff status hit the same pairs in parallel
|
|
176
|
+
// on a cold load). Persisted results survive restarts via the global `@teambit/cache` aspect.
|
|
177
|
+
_defineProperty(this, "compareInflight", new Map());
|
|
178
|
+
_defineProperty(this, "apiDiffInflight", new Map());
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Read-through cache with single-flight dedupe: serve a persisted result, else share an in-flight
|
|
182
|
+
* computation, else compute once and persist. Most `(baseId, compareId)` pairs are immutable (keyed
|
|
183
|
+
* on snap hashes), so a cached result never goes stale. `cacheable` gates which results are persisted.
|
|
184
|
+
*
|
|
185
|
+
* `skipPersistentCache` bypasses the persistent cache entirely (neither read nor write) while still
|
|
186
|
+
* sharing the in-flight computation. Callers must set it whenever the result depends on mutable state
|
|
187
|
+
* the key does not capture — e.g. a live-workspace diff against on-disk files — so a previously
|
|
188
|
+
* persisted snap-to-snap result for the same key is never served in its place.
|
|
189
|
+
*/
|
|
190
|
+
async getOrCompute(inflight, cacheKey, compute, cacheable = () => true, skipPersistentCache = false) {
|
|
191
|
+
const pending = inflight.get(cacheKey);
|
|
192
|
+
if (pending) return pending;
|
|
193
|
+
if (!skipPersistentCache) {
|
|
194
|
+
const cached = await this.cache.get(cacheKey);
|
|
195
|
+
if (cached !== undefined) return cached;
|
|
196
|
+
// a concurrent caller may have started computing while we awaited the cache read.
|
|
197
|
+
const started = inflight.get(cacheKey);
|
|
198
|
+
if (started) return started;
|
|
199
|
+
}
|
|
200
|
+
const promise = compute().then(result => {
|
|
201
|
+
// TTL keeps the cache bounded: compare payloads embed full per-file contents for every pair,
|
|
202
|
+
// so without an expiry every pair ever viewed stays on disk forever. entries are cheap to
|
|
203
|
+
// recompute after expiry (sources still cached in the scope), so a stale-eviction is harmless.
|
|
204
|
+
if (!skipPersistentCache && cacheable(result)) void this.cache.set(cacheKey, result, PERSISTENT_CACHE_TTL_MS);
|
|
205
|
+
return result;
|
|
206
|
+
}).finally(() => inflight.delete(cacheKey));
|
|
207
|
+
inflight.set(cacheKey, promise);
|
|
208
|
+
return promise;
|
|
146
209
|
}
|
|
147
210
|
async compare(baseIdStr, compareIdStr) {
|
|
211
|
+
return this.getOrCompute(this.compareInflight, `component-compare:result:${baseIdStr}|${compareIdStr}`, () => this.computeCompare(baseIdStr, compareIdStr),
|
|
212
|
+
// never persist a live-workspace diff: it reflects on-disk files (incl. uncommitted changes),
|
|
213
|
+
// so a cached copy would go stale the moment the user edits a file. the (baseId, compareId)
|
|
214
|
+
// pair is otherwise immutable (keyed on snap hashes), so those stay cacheable.
|
|
215
|
+
result => !result.isLiveWorkspace,
|
|
216
|
+
// whether this call *reads* the persistent cache is decided up front from the same signal:
|
|
217
|
+
// a live-workspace compare must skip the cache entirely, otherwise a snap-to-snap result
|
|
218
|
+
// persisted for this key in a prior run (or a non-live context) would mask on-disk changes.
|
|
219
|
+
this.comparesLiveWorkspace(baseIdStr, compareIdStr));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* cheap, synchronous pre-check mirroring the `comparingWithLocalChanges` / `compareIsLiveWorkspace`
|
|
224
|
+
* logic in `computeCompare`: will this compare diff against live on-disk workspace files rather than
|
|
225
|
+
* two immutable snaps? errs toward `true` (skip the persistent cache) whenever the id cannot be
|
|
226
|
+
* classified, so a stale snap-to-snap result is never served in place of a live one.
|
|
227
|
+
*/
|
|
228
|
+
comparesLiveWorkspace(baseIdStr, compareIdStr) {
|
|
229
|
+
if (!this.workspace) return false; // scope/remote host: every compare is an immutable snap-to-snap pair
|
|
230
|
+
if (baseIdStr === compareIdStr) return true; // the "local changes" view: checked-out snap vs on-disk files
|
|
231
|
+
return this.isLiveCheckout(compareIdStr);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* whether this id refers to the component version currently checked out on disk — the one case
|
|
236
|
+
* where "the same versioned id" can produce different data over time (the user edits files). errs
|
|
237
|
+
* toward `true` when the id cannot be classified, so a stale cached result is never served.
|
|
238
|
+
*/
|
|
239
|
+
isLiveCheckout(idStr) {
|
|
240
|
+
if (!this.workspace) return false;
|
|
241
|
+
let id;
|
|
242
|
+
try {
|
|
243
|
+
id = _componentId().ComponentID.fromString(idStr);
|
|
244
|
+
} catch {
|
|
245
|
+
return true; // unclassifiable id → assume live so a stale cached diff is never returned
|
|
246
|
+
}
|
|
247
|
+
const checkedOut = this.workspace.getIdIfExist(id);
|
|
248
|
+
if (!checkedOut) return false; // not checked out → a stored snap, safe to cache
|
|
249
|
+
// live only when this side is the exact version currently checked out on disk.
|
|
250
|
+
return !id.hasVersion() || checkedOut.version === id.version;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** The original `compare()` body — moved here so the public method can wrap with memo + single-flight. */
|
|
254
|
+
async computeCompare(baseIdStr, compareIdStr) {
|
|
148
255
|
const host = this.componentAspect.getHost();
|
|
149
256
|
const [baseCompId, compareCompId] = await host.resolveMultipleComponentIds([baseIdStr, compareIdStr]);
|
|
150
257
|
const modelComponent = await this.scope.legacyScope.getModelComponentIfExist(compareCompId);
|
|
@@ -163,7 +270,20 @@ class ComponentCompareMain {
|
|
|
163
270
|
const baseComponent = components?.[0];
|
|
164
271
|
const compareComponent = components?.[1];
|
|
165
272
|
const componentWithoutVersion = await host.get((baseCompId || compareCompId).changeVersion(undefined));
|
|
166
|
-
|
|
273
|
+
|
|
274
|
+
// When the compare side is the component currently checked out in the workspace, diff against the
|
|
275
|
+
// on-disk files rather than a stored snap: passing `undefined` as the compare version makes
|
|
276
|
+
// `computeDiff` fall back to `consumerComponent.files`, so uncommitted local changes are included.
|
|
277
|
+
// This covers two cases with one code path:
|
|
278
|
+
// - base === compare (the classic "local changes" view): checked-out model → workspace files.
|
|
279
|
+
// - base = an earlier version: that version's committed changes + any uncommitted changes on top.
|
|
280
|
+
// Without this, the default workspace compare resolves base and compare to the same checked-out
|
|
281
|
+
// snap and reports no changes, collapsing the compare view to only its always-on sections.
|
|
282
|
+
const checkedOutVersion = componentWithoutVersion?.id.version;
|
|
283
|
+
const compareIsLiveWorkspace = Boolean(this.workspace && checkedOutVersion && compareVersion === checkedOutVersion);
|
|
284
|
+
const effectiveBaseVersion = comparingWithLocalChanges ? undefined : baseVersion;
|
|
285
|
+
const effectiveCompareVersion = comparingWithLocalChanges || compareIsLiveWorkspace ? undefined : compareVersion;
|
|
286
|
+
const diff = componentWithoutVersion ? await this.computeDiff(componentWithoutVersion, effectiveBaseVersion, effectiveCompareVersion, {}) : {
|
|
167
287
|
filesDiff: [],
|
|
168
288
|
fieldsDiff: []
|
|
169
289
|
};
|
|
@@ -177,10 +297,71 @@ class ComponentCompareMain {
|
|
|
177
297
|
compareId: compareIdStr,
|
|
178
298
|
code: diff.filesDiff || [],
|
|
179
299
|
fields: diff.fieldsDiff || [],
|
|
180
|
-
tests: testFilesDiff
|
|
300
|
+
tests: testFilesDiff,
|
|
301
|
+
isLiveWorkspace: compareIsLiveWorkspace
|
|
181
302
|
};
|
|
182
303
|
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* compare a paginated slice of component pairs in one call.
|
|
307
|
+
* a pair that fails to compare (e.g. a component without versions) becomes `null` in the
|
|
308
|
+
* returned array rather than failing the whole batch. the array is aligned to the requested
|
|
309
|
+
* slice (`pairs[offset .. offset + limit]`).
|
|
310
|
+
*/
|
|
311
|
+
async compareComponents(pairs, options) {
|
|
312
|
+
return (0, _compareComponentPairs().compareComponentPairs)(pairs, (baseId, compareId) => this.compare(baseId, compareId), {
|
|
313
|
+
offset: options?.offset,
|
|
314
|
+
limit: options?.limit,
|
|
315
|
+
concurrency: (0, _harmonyModules().concurrentComponentsLimit)(),
|
|
316
|
+
onError: (pair, err) => {
|
|
317
|
+
this.logger.warn(`compareComponents: failed to compare ${pair.baseId} <> ${pair.compareId}`, err);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* api-diff a paginated slice of component pairs in one call — the bulk counterpart of the single
|
|
324
|
+
* `getAPIDiff`, mirroring `compareComponents`. reuses `getAPIDiff` per pair (so its disk memo +
|
|
325
|
+
* single-flight dedupe still apply), turning a pair whose diff throws into `null` rather than
|
|
326
|
+
* failing the whole batch. the returned array is aligned to the requested slice.
|
|
327
|
+
*/
|
|
328
|
+
async apiDiffs(pairs, options) {
|
|
329
|
+
return (0, _compareComponentPairs().compareComponentPairs)(pairs, (baseId, compareId) => this.getAPIDiff(baseId, compareId), {
|
|
330
|
+
offset: options?.offset,
|
|
331
|
+
limit: options?.limit,
|
|
332
|
+
concurrency: (0, _harmonyModules().concurrentComponentsLimit)(),
|
|
333
|
+
onError: (pair, err) => {
|
|
334
|
+
this.logger.warn(`apiDiffs: failed to compute api diff ${pair.baseId} <> ${pair.compareId}`, err);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
static isApiDiffCacheable(result) {
|
|
339
|
+
// a live-extracted side reflects the current working tree, not the snap the cache key names —
|
|
340
|
+
// persisting it would serve a stale (possibly degraded) diff for that pair forever.
|
|
341
|
+
if (result.base?.live || result.compare?.live) return false;
|
|
342
|
+
if (result.status === 'COMPUTED') return true;
|
|
343
|
+
// A non-COMPUTED result is only safe to persist (disk cache, keyed on the immutable snap pair, no
|
|
344
|
+
// TTL) when it can never change for that pair. FAILED is transient. NOT_BUILT is *pending*: the snap
|
|
345
|
+
// simply hasn't been built yet, and once CI builds it (same hash) the schema appears — caching the
|
|
346
|
+
// pre-build "unavailable" answer would keep the API view blank forever. NO_EXTRACTOR/DISABLED are
|
|
347
|
+
// stable properties of the snap's env, so they stay cacheable.
|
|
348
|
+
const pendingOrTransient = reason => reason === 'FAILED' || reason === 'NOT_BUILT';
|
|
349
|
+
return !pendingOrTransient(result.base?.reason) && !pendingOrTransient(result.compare?.reason);
|
|
350
|
+
}
|
|
183
351
|
async getAPIDiff(baseIdStr, compareIdStr) {
|
|
352
|
+
// never persist a result that can still change: `null` (snaps couldn't load), FAILED (schema
|
|
353
|
+
// retrieval threw) and NOT_BUILT (snap not yet built) must recompute next call; NO_EXTRACTOR/
|
|
354
|
+
// DISABLED are stable env properties and safe to cache (see `isApiDiffCacheable`).
|
|
355
|
+
// the version namespace invalidates older computed results on engine changes:
|
|
356
|
+
// v2 — availability-aware results; v3 — self-referential-returnType display fix.
|
|
357
|
+
// skip the persistent cache when EITHER side is the live checkout: SchemaMain live-extracts the
|
|
358
|
+
// schema of a modified checkout of the exact same versioned id, so a snap-to-snap result cached
|
|
359
|
+
// under this key in a prior (unmodified) run would mask the user's on-disk API changes. both
|
|
360
|
+
// sides are checked (unlike `compare()`, where only the compare side can be live) because the
|
|
361
|
+
// checked-out version can appear on either side of an API diff pair.
|
|
362
|
+
return this.getOrCompute(this.apiDiffInflight, `component-compare:api-diff:v3:${baseIdStr}|${compareIdStr}`, () => this.computeAPIDiff(baseIdStr, compareIdStr), v => v !== null && ComponentCompareMain.isApiDiffCacheable(v), this.isLiveCheckout(baseIdStr) || this.isLiveCheckout(compareIdStr));
|
|
363
|
+
}
|
|
364
|
+
async computeAPIDiff(baseIdStr, compareIdStr) {
|
|
184
365
|
const host = this.componentAspect.getHost();
|
|
185
366
|
const [baseCompId, compareCompId] = await host.resolveMultipleComponentIds([baseIdStr, compareIdStr]);
|
|
186
367
|
await this.importer.importObjectsFromMainIfExist([baseCompId, compareCompId], {
|
|
@@ -329,9 +510,9 @@ class ComponentCompareMain {
|
|
|
329
510
|
await updateFieldsDiff(fromVersionComponent, toVersionComponent, diffResult, diffOpts);
|
|
330
511
|
return diffResult;
|
|
331
512
|
}
|
|
332
|
-
static async provider([graphql, component, scope, loggerMain, cli, workspace, tester, depResolver, importer, schema]) {
|
|
513
|
+
static async provider([graphql, component, scope, loggerMain, cli, workspace, tester, depResolver, importer, schema, cache]) {
|
|
333
514
|
const logger = loggerMain.createLogger(_componentCompare2().ComponentCompareAspect.id);
|
|
334
|
-
const componentCompareMain = new ComponentCompareMain(component, scope, logger, tester, depResolver, importer, schema, workspace);
|
|
515
|
+
const componentCompareMain = new ComponentCompareMain(component, scope, logger, tester, depResolver, importer, schema, cache, workspace);
|
|
335
516
|
cli.register(new (_diffCmd().DiffCmd)(componentCompareMain));
|
|
336
517
|
graphql.register(() => (0, _componentCompare().componentCompareSchema)(componentCompareMain));
|
|
337
518
|
return componentCompareMain;
|
|
@@ -339,7 +520,7 @@ class ComponentCompareMain {
|
|
|
339
520
|
}
|
|
340
521
|
exports.ComponentCompareMain = ComponentCompareMain;
|
|
341
522
|
_defineProperty(ComponentCompareMain, "slots", []);
|
|
342
|
-
_defineProperty(ComponentCompareMain, "dependencies", [_graphql().GraphqlAspect, _component().ComponentAspect, _scope().ScopeAspect, _logger().LoggerAspect, _cli().CLIAspect, _workspace().WorkspaceAspect, _tester().TesterAspect, _dependencyResolver().DependencyResolverAspect, _importer().ImporterAspect, _schema().SchemaAspect]);
|
|
523
|
+
_defineProperty(ComponentCompareMain, "dependencies", [_graphql().GraphqlAspect, _component().ComponentAspect, _scope().ScopeAspect, _logger().LoggerAspect, _cli().CLIAspect, _workspace().WorkspaceAspect, _tester().TesterAspect, _dependencyResolver().DependencyResolverAspect, _importer().ImporterAspect, _schema().SchemaAspect, _cache().CacheAspect]);
|
|
343
524
|
_defineProperty(ComponentCompareMain, "runtime", _cli().MainRuntime);
|
|
344
525
|
function hasDiff(diffResult) {
|
|
345
526
|
return !!(diffResult.filesDiff && diffResult.filesDiff.find(file => file.diffOutput) || diffResult.fieldsDiff);
|