@component-compass/plugin-core 0.0.1 → 0.0.2

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.
@@ -0,0 +1,35 @@
1
+ import type { IndexedComponent } from "./scan-types.js";
2
+ /**
3
+ * Manifest source for enrichment of a resolver-discovered leaf package.
4
+ *
5
+ * - "authored": leaf shipped a `custom-elements.json` or
6
+ * `component-compass-manifest.json` at its package root.
7
+ * - "derived": leaf carries no authored manifest but `manifest-react` /
8
+ * `manifest-vue` extractors (Phase 4) produced one from source.
9
+ * - null: no enrichment available — identity stays opaque
10
+ * (`components[].manifest === null` in the artifact).
11
+ *
12
+ * Open envelope: `data` shape grows in Phase 3 (CEM depth) and Phase 4
13
+ * (extractor maturity) without changing this discriminator.
14
+ */
15
+ export type Enrichment = {
16
+ source: "authored";
17
+ data: AuthoredManifestData;
18
+ } | {
19
+ source: "derived";
20
+ data: DerivedManifestData;
21
+ } | null;
22
+ export type AuthoredManifestData = {
23
+ /** Every component declared in the leaf's authored manifest. */
24
+ components: IndexedComponent[];
25
+ /** Absolute filesystem path of the loaded manifest file. */
26
+ manifestPath: string;
27
+ /** Authored format the manifest was loaded from. */
28
+ manifestKind: "cem" | "component-compass-manifest";
29
+ /** Leaf package's `version` field, when readable. */
30
+ packageVersion?: string;
31
+ };
32
+ export type DerivedManifestData = {
33
+ /** Components extracted from leaf source by Phase 4 extractors. */
34
+ components: IndexedComponent[];
35
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=enrichment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrichment.js","sourceRoot":"","sources":["../src/enrichment.ts"],"names":[],"mappings":""}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,9 @@ export { validateManifest } from "./validate.js";
4
4
  export type { ValidationResult } from "./validate.js";
5
5
  export { manifestJsonSchema } from "./schema.js";
6
6
  export { ManifestBuilder } from "./builder.js";
7
- export type { TagName, WrapperId, ImportRole, ImportEntry, IndexedComponent, ComponentIndex, Loc, WarningCode, Warning, ScanConfig, PropUsage, OccurrenceVia, Occurrence, WrapperDef, FileImport, ParseResult, ParseContext, ParserPlugin, ScanEnvelope, ScanOutput, DetectorId, LocalDefinition, LocalDefinitionIndex, } from "./scan-types.js";
7
+ export type { TagName, WrapperId, ImportRole, ImportEntry, IndexedComponent, Loc, WarningCode, Warning, ScanConfig, PropUsage, OccurrenceVia, Occurrence, WrapperDef, FileImport, ParseResult, ParseContext, ParserPlugin, ScanEnvelope, ScanOutput, ManifestRef, ScanStats, DetectorId, LocalDefinition, LocalDefinitionIndex, ResolveLazyManifest, LookupByTag, LazyResolverHit, } from "./scan-types.js";
8
8
  export { asTagName, asWrapperId, emptyLocalIndex } from "./scan-types.js";
9
+ export type { Enrichment, AuthoredManifestData, DerivedManifestData } from "./enrichment.js";
10
+ export { computeOccurrenceId } from "./occurrence-id.js";
11
+ export type { CompositionRollup } from "./output-types.js";
9
12
  export * from "./output-types.js";
package/dist/index.js CHANGED
@@ -3,5 +3,6 @@ export { validateManifest } from "./validate.js";
3
3
  export { manifestJsonSchema } from "./schema.js";
4
4
  export { ManifestBuilder } from "./builder.js";
5
5
  export { asTagName, asWrapperId, emptyLocalIndex } from "./scan-types.js";
6
+ export { computeOccurrenceId } from "./occurrence-id.js";
6
7
  export * from "./output-types.js";
7
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAa/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE1E,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAc/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAI1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Stable per-occurrence id keyed on (componentId, filePath, line, column).
3
+ *
4
+ * Inputs use the FINAL artifact componentId string (the hashed form that
5
+ * appears on `OutputOccurrence.componentId`), so downstream consumers can
6
+ * re-derive: `id === computeOccurrenceId(o.componentId, o.filePath, o.line, o.column)`.
7
+ *
8
+ * 64 bits of entropy via 16-char SHA-256 truncation. Birthday-bound collision
9
+ * at ~4 billion occurrences; full hash is overkill for current scale.
10
+ */
11
+ export declare function computeOccurrenceId(componentId: string, filePath: string, line: number, column: number): string;
@@ -0,0 +1,18 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Stable per-occurrence id keyed on (componentId, filePath, line, column).
4
+ *
5
+ * Inputs use the FINAL artifact componentId string (the hashed form that
6
+ * appears on `OutputOccurrence.componentId`), so downstream consumers can
7
+ * re-derive: `id === computeOccurrenceId(o.componentId, o.filePath, o.line, o.column)`.
8
+ *
9
+ * 64 bits of entropy via 16-char SHA-256 truncation. Birthday-bound collision
10
+ * at ~4 billion occurrences; full hash is overkill for current scale.
11
+ */
12
+ export function computeOccurrenceId(componentId, filePath, line, column) {
13
+ return createHash("sha256")
14
+ .update(`${componentId}|${filePath}|${line}|${column}`)
15
+ .digest("hex")
16
+ .slice(0, 16);
17
+ }
18
+ //# sourceMappingURL=occurrence-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occurrence-id.js","sourceRoot":"","sources":["../src/occurrence-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,MAAc;IAEd,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,GAAG,WAAW,IAAI,QAAQ,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;SACtD,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC"}
@@ -3,6 +3,7 @@
3
3
  * occurrences). Lives in plugin-core so consumers (cli, future analytics,
4
4
  * agents) share one source of truth without depending on cli internals.
5
5
  */
6
+ import type { OccurrenceVia } from "./via-types.js";
6
7
  export type ComponentScope = "ds" | "external" | "local";
7
8
  export type ComponentKindOutput = "custom-element" | "react-component" | "vue-component";
8
9
  export type ViaKindOutput = "html-tag" | "lit-template" | "react-wrapper" | "direct-import" | "vue-template";
@@ -24,6 +25,7 @@ export type OutputIdentity = {
24
25
  filePath?: string;
25
26
  };
26
27
  export type OutputManifest = {
28
+ source: "authored" | "derived";
27
29
  lifecycle?: {
28
30
  status: "stable" | "deprecated";
29
31
  supersededBy?: {
@@ -52,11 +54,23 @@ export type PropDistribution = {
52
54
  omitted: number;
53
55
  defaultApplied: number;
54
56
  };
55
- export type OutputEdges = {
56
- /** componentIds this component renders. Empty in v1, schema slot. */
57
- renders: string[];
58
- /** componentIds that render this component. Empty in v1, schema slot. */
59
- renderedBy: string[];
57
+ export type CompositionRollup = {
58
+ /** child componentId count of direct-child relationships */
59
+ rendersByCount: Record<string, number>;
60
+ /** parent componentId count of being-direct-child relationships */
61
+ renderedByCount: Record<string, number>;
62
+ /** # of this component's occurrences with no tracked parent */
63
+ isRootCount: number;
64
+ /** # of this component's occurrences with no tracked direct children */
65
+ isLeafCount: number;
66
+ /** mean of all occurrences' depth */
67
+ avgDepth: number;
68
+ /** deepest observed depth */
69
+ maxDepth: number;
70
+ /** mean direct children per occurrence */
71
+ avgChildCount: number;
72
+ /** most direct children on any occurrence */
73
+ maxChildCount: number;
60
74
  };
61
75
  export type OutputComponent = {
62
76
  id: string;
@@ -68,12 +82,22 @@ export type OutputComponent = {
68
82
  fileCount: number;
69
83
  };
70
84
  props: Record<string, PropDistribution>;
71
- edges: OutputEdges;
85
+ /** Phase 2: per-component composition rollup. Omitted when occurrenceCount is 0. */
86
+ composition?: CompositionRollup;
72
87
  };
73
88
  export type OutputOccurrence = {
74
89
  componentId: string;
75
90
  filePath: string;
76
91
  line: number;
77
92
  column: number;
93
+ via: OccurrenceVia;
78
94
  props: Record<string, PropValueState>;
95
+ /** Phase 2: stable id keyed on (componentId, filePath, line, column). */
96
+ occurrenceId: string;
97
+ /** Phase 2: occurrenceId of nearest tracked ancestor. Absent if root in this scope. */
98
+ parentOccurrenceId?: string;
99
+ /** Phase 2: denormalisation of parent's componentId; convenience for downstream queries. */
100
+ parentComponentId?: string;
101
+ /** Phase 2: distance from highest tracked-rootless ancestor in same lexical scope. */
102
+ depth?: number;
79
103
  };
@@ -1,7 +1,2 @@
1
- /**
2
- * Types describing the CLI's emitted JSON artifact (two-grain: components +
3
- * occurrences). Lives in plugin-core so consumers (cli, future analytics,
4
- * agents) share one source of truth without depending on cli internals.
5
- */
6
1
  export {};
7
2
  //# sourceMappingURL=output-types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"output-types.js","sourceRoot":"","sources":["../src/output-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
1
+ {"version":3,"file":"output-types.js","sourceRoot":"","sources":["../src/output-types.ts"],"names":[],"mappings":""}
@@ -6,7 +6,8 @@
6
6
  * parser ↔ CLI boundary lives here in `plugin-core`.
7
7
  */
8
8
  import type { OutputComponent, OutputOccurrence } from "./output-types.js";
9
- import type { ComponentId, ManifestComponent } from "./types.js";
9
+ import type { ComponentId, ManifestComponent, ManifestSource } from "./types.js";
10
+ import type { OccurrenceVia } from "./via-types.js";
10
11
  declare const __brand: unique symbol;
11
12
  type Brand<K, T> = K & {
12
13
  readonly [__brand]: T;
@@ -29,22 +30,21 @@ export type IndexedComponent = (Extract<ManifestComponent, {
29
30
  }) | Extract<ManifestComponent, {
30
31
  kind: "react-component" | "vue-component";
31
32
  }>;
32
- export type ComponentIndex = {
33
- /** key = serialiseComponentId(id) */
34
- byId: Map<string, IndexedComponent>;
35
- /** custom-element only, keyed by tagName */
36
- byTag: Map<string, Extract<IndexedComponent, {
37
- kind: "custom-element";
38
- }>>;
39
- /** key = `${specifier}::${export}` — covers WC react-wrappers AND direct react/vue imports */
40
- byImport: Map<string, IndexedComponent>;
41
- /** key = native specifier (WC bare imports for side-effect registration) */
42
- byNativeSpecifier: Map<string, Extract<IndexedComponent, {
43
- kind: "custom-element";
44
- }>>;
45
- /** Local-component definitions discovered in repo source. */
46
- local: LocalDefinitionIndex;
33
+ /**
34
+ * A single resolution outcome for one `(fromFile, specifier, exportName)`
35
+ * triple. `matched` is null when the resolved leaf has no manifest entry
36
+ * for `exportName` the caller should fall back to opaque opportunistic
37
+ * emission (current `react-component` / `vue-component` with `manifest: null`
38
+ * shape, package = `leafPackage`).
39
+ */
40
+ export type LazyResolverHit = {
41
+ leafPackage: string;
42
+ matched: IndexedComponent | null;
47
43
  };
44
+ export type ResolveLazyManifest = (fromFile: string, specifier: string, exportName: string) => LazyResolverHit | null;
45
+ export type LookupByTag = (tagName: string) => Extract<IndexedComponent, {
46
+ kind: "custom-element";
47
+ }> | null;
48
48
  export type DetectorId = "react-default-fn" | "react-arrow" | "react-class" | "react-forward-ref" | "react-memo" | "vue-sfc" | "vue-define-component" | "vue-functional" | "wc-decorator" | "wc-customelements-define";
49
49
  export type LocalDefinition = {
50
50
  /** componentId.source is always { type: "local", filePath }. */
@@ -71,7 +71,7 @@ export type Loc = {
71
71
  line: number;
72
72
  column: number;
73
73
  };
74
- export type WarningCode = "MISSING_REACT_EXPORT_EXTENSION" | "PARSE_FAILURE" | "IMPORT_UNRESOLVABLE" | "MANIFEST_TAG_COLLISION" | "EMPTY_INCLUDE";
74
+ export type WarningCode = "PARSE_FAILURE" | "IMPORT_UNRESOLVABLE" | "MANIFEST_TAG_COLLISION" | "EMPTY_INCLUDE";
75
75
  interface WarningBase {
76
76
  message: string;
77
77
  }
@@ -83,10 +83,6 @@ export type Warning = (WarningBase & {
83
83
  code: "IMPORT_UNRESOLVABLE";
84
84
  file: string;
85
85
  line: number;
86
- }) | (WarningBase & {
87
- code: "MISSING_REACT_EXPORT_EXTENSION";
88
- package: string;
89
- file: string;
90
86
  }) | (WarningBase & {
91
87
  code: "MANIFEST_TAG_COLLISION";
92
88
  package: string;
@@ -115,30 +111,28 @@ export type PropUsage = {
115
111
  */
116
112
  dynamicKind?: "spread" | "expr" | "identifier";
117
113
  };
118
- export type OccurrenceVia = {
119
- kind: "html-tag";
120
- } | {
121
- kind: "lit-template";
122
- specifier?: string;
123
- } | {
124
- kind: "react-wrapper";
125
- specifier: string;
126
- import: string;
127
- } | {
128
- kind: "direct-import";
129
- specifier: string;
130
- import: string;
131
- } | {
132
- kind: "vue-template";
133
- specifier: string;
134
- import: string;
135
- };
114
+ export type { OccurrenceVia } from "./via-types.js";
136
115
  export type Occurrence = {
137
116
  componentId: ComponentId;
138
117
  loc: Loc;
139
118
  via: OccurrenceVia;
140
119
  props: PropUsage[];
141
120
  events: string[];
121
+ /**
122
+ * Phase 2: parser-side parent reference. Index into the same
123
+ * ParseResult.occurrences array. `undefined` = no tracked parent in this
124
+ * lexical scope. The CLI's projection layer (`parseResultsToOccurrences`)
125
+ * resolves this index into `parentOccurrenceId` / `parentComponentId`
126
+ * strings. Cross-file composition is out of scope (per spec non-goal),
127
+ * so the index is local to one ParseResult.
128
+ */
129
+ parentRef?: number;
130
+ /**
131
+ * Phase 2: distance from the highest tracked-rootless ancestor in the
132
+ * same lexical scope. 0 for roots; matches `parentStack.length` at the
133
+ * time the occurrence was emitted.
134
+ */
135
+ depth?: number;
142
136
  };
143
137
  export type WrapperDef = {
144
138
  id: WrapperId;
@@ -161,9 +155,12 @@ export type ParseResult = {
161
155
  warnings: Warning[];
162
156
  };
163
157
  export type ParseContext = {
164
- index: ComponentIndex;
158
+ /** Local-component definitions discovered in repo source. */
159
+ localIndex: LocalDefinitionIndex;
165
160
  config: ScanConfig;
166
161
  resolveImport: (from: string, spec: string) => string | null;
162
+ resolveLazyManifest: ResolveLazyManifest;
163
+ lookupByTag: LookupByTag;
167
164
  repoRoot: string;
168
165
  };
169
166
  export interface ParserPlugin {
@@ -180,9 +177,23 @@ export type ScanEnvelope = {
180
177
  toolVersion: string;
181
178
  cwd: string;
182
179
  };
180
+ export type ManifestRef = {
181
+ source: ManifestSource;
182
+ package: string;
183
+ path: string;
184
+ version: string | null;
185
+ };
186
+ export type ScanStats = {
187
+ filesScanned: number;
188
+ filesWithUsage: number;
189
+ scanDurationMs: number;
190
+ componentsDetected: number;
191
+ totalOccurrences: number;
192
+ };
183
193
  export type ScanOutput = {
184
194
  envelope: ScanEnvelope;
195
+ manifests: ManifestRef[];
196
+ stats: ScanStats;
185
197
  components: OutputComponent[];
186
198
  occurrences: OutputOccurrence[];
187
199
  };
188
- export {};
@@ -1 +1 @@
1
- {"version":3,"file":"scan-types.js","sourceRoot":"","sources":["../src/scan-types.ts"],"names":[],"mappings":"AAoBA,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,CAAY,CAAC;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAS,EAAa,EAAE,CAAC,CAAc,CAAC;AAsEpE,MAAM,CAAC,MAAM,eAAe,GAAyB;IACnD,MAAM,EAAE,IAAI,GAAG,EAAE;IACjB,KAAK,EAAE,IAAI,GAAG,EAAE;CACjB,CAAC"}
1
+ {"version":3,"file":"scan-types.js","sourceRoot":"","sources":["../src/scan-types.ts"],"names":[],"mappings":"AAqBA,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,CAAY,CAAC;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAS,EAAa,EAAE,CAAC,CAAc,CAAC;AAoFpE,MAAM,CAAC,MAAM,eAAe,GAAyB;IACnD,MAAM,EAAE,IAAI,GAAG,EAAE;IACjB,KAAK,EAAE,IAAI,GAAG,EAAE;CACjB,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Discriminated union describing how a component instance was used.
3
+ * Shared between parser-emitted Occurrence (scan-types.ts) and the artifact-shape
4
+ * OutputOccurrence (output-types.ts). Lives in its own module to keep the two
5
+ * type files acyclic.
6
+ */
7
+ export type OccurrenceVia = {
8
+ kind: "html-tag";
9
+ } | {
10
+ kind: "lit-template";
11
+ specifier?: string;
12
+ } | {
13
+ kind: "react-wrapper";
14
+ specifier: string;
15
+ import: string;
16
+ } | {
17
+ kind: "direct-import";
18
+ specifier: string;
19
+ import: string;
20
+ } | {
21
+ kind: "vue-template";
22
+ specifier: string;
23
+ import: string;
24
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=via-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"via-types.js","sourceRoot":"","sources":["../src/via-types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@component-compass/plugin-core",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",