@pbiplint/core 0.1.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.
Files changed (189) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +29 -0
  3. package/README.md +34 -0
  4. package/dist/engine/config.d.ts +33 -0
  5. package/dist/engine/config.d.ts.map +1 -0
  6. package/dist/engine/config.js +61 -0
  7. package/dist/engine/config.js.map +1 -0
  8. package/dist/engine/ignore.d.ts +8 -0
  9. package/dist/engine/ignore.d.ts.map +1 -0
  10. package/dist/engine/ignore.js +17 -0
  11. package/dist/engine/ignore.js.map +1 -0
  12. package/dist/engine/lint.d.ts +38 -0
  13. package/dist/engine/lint.d.ts.map +1 -0
  14. package/dist/engine/lint.js +33 -0
  15. package/dist/engine/lint.js.map +1 -0
  16. package/dist/engine/rank.d.ts +24 -0
  17. package/dist/engine/rank.d.ts.map +1 -0
  18. package/dist/engine/rank.js +38 -0
  19. package/dist/engine/rank.js.map +1 -0
  20. package/dist/engine/run.d.ts +21 -0
  21. package/dist/engine/run.d.ts.map +1 -0
  22. package/dist/engine/run.js +43 -0
  23. package/dist/engine/run.js.map +1 -0
  24. package/dist/format/index.d.ts +11 -0
  25. package/dist/format/index.d.ts.map +1 -0
  26. package/dist/format/index.js +21 -0
  27. package/dist/format/index.js.map +1 -0
  28. package/dist/format/json.d.ts +4 -0
  29. package/dist/format/json.d.ts.map +1 -0
  30. package/dist/format/json.js +20 -0
  31. package/dist/format/json.js.map +1 -0
  32. package/dist/format/markdown.d.ts +4 -0
  33. package/dist/format/markdown.d.ts.map +1 -0
  34. package/dist/format/markdown.js +28 -0
  35. package/dist/format/markdown.js.map +1 -0
  36. package/dist/format/sarif.d.ts +4 -0
  37. package/dist/format/sarif.d.ts.map +1 -0
  38. package/dist/format/sarif.js +77 -0
  39. package/dist/format/sarif.js.map +1 -0
  40. package/dist/format/text.d.ts +31 -0
  41. package/dist/format/text.d.ts.map +1 -0
  42. package/dist/format/text.js +57 -0
  43. package/dist/format/text.js.map +1 -0
  44. package/dist/index/build.d.ts +11 -0
  45. package/dist/index/build.d.ts.map +1 -0
  46. package/dist/index/build.js +11 -0
  47. package/dist/index/build.js.map +1 -0
  48. package/dist/index/references.d.ts +37 -0
  49. package/dist/index/references.d.ts.map +1 -0
  50. package/dist/index/references.js +126 -0
  51. package/dist/index/references.js.map +1 -0
  52. package/dist/index/relationships.d.ts +10 -0
  53. package/dist/index/relationships.d.ts.map +1 -0
  54. package/dist/index/relationships.js +24 -0
  55. package/dist/index/relationships.js.map +1 -0
  56. package/dist/index/usage.d.ts +11 -0
  57. package/dist/index/usage.d.ts.map +1 -0
  58. package/dist/index/usage.js +25 -0
  59. package/dist/index/usage.js.map +1 -0
  60. package/dist/index.d.ts +25 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +21 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/model/build.d.ts +9 -0
  65. package/dist/model/build.d.ts.map +1 -0
  66. package/dist/model/build.js +282 -0
  67. package/dist/model/build.js.map +1 -0
  68. package/dist/model/names.d.ts +12 -0
  69. package/dist/model/names.d.ts.map +1 -0
  70. package/dist/model/names.js +21 -0
  71. package/dist/model/names.js.map +1 -0
  72. package/dist/model/types.d.ts +150 -0
  73. package/dist/model/types.d.ts.map +1 -0
  74. package/dist/model/types.js +2 -0
  75. package/dist/model/types.js.map +1 -0
  76. package/dist/rules/helpers.d.ts +54 -0
  77. package/dist/rules/helpers.d.ts.map +1 -0
  78. package/dist/rules/helpers.js +189 -0
  79. package/dist/rules/helpers.js.map +1 -0
  80. package/dist/rules/index.d.ts +3 -0
  81. package/dist/rules/index.d.ts.map +1 -0
  82. package/dist/rules/index.js +4 -0
  83. package/dist/rules/index.js.map +1 -0
  84. package/dist/rules/microsoft-bpa/bpa-rules.data.d.ts +12 -0
  85. package/dist/rules/microsoft-bpa/bpa-rules.data.d.ts.map +1 -0
  86. package/dist/rules/microsoft-bpa/bpa-rules.data.js +664 -0
  87. package/dist/rules/microsoft-bpa/bpa-rules.data.js.map +1 -0
  88. package/dist/rules/microsoft-bpa/columns.d.ts +14 -0
  89. package/dist/rules/microsoft-bpa/columns.d.ts.map +1 -0
  90. package/dist/rules/microsoft-bpa/columns.js +117 -0
  91. package/dist/rules/microsoft-bpa/columns.js.map +1 -0
  92. package/dist/rules/microsoft-bpa/define.d.ts +14 -0
  93. package/dist/rules/microsoft-bpa/define.d.ts.map +1 -0
  94. package/dist/rules/microsoft-bpa/define.js +75 -0
  95. package/dist/rules/microsoft-bpa/define.js.map +1 -0
  96. package/dist/rules/microsoft-bpa/dependencies.d.ts +11 -0
  97. package/dist/rules/microsoft-bpa/dependencies.d.ts.map +1 -0
  98. package/dist/rules/microsoft-bpa/dependencies.js +56 -0
  99. package/dist/rules/microsoft-bpa/dependencies.js.map +1 -0
  100. package/dist/rules/microsoft-bpa/index.d.ts +4 -0
  101. package/dist/rules/microsoft-bpa/index.d.ts.map +1 -0
  102. package/dist/rules/microsoft-bpa/index.js +20 -0
  103. package/dist/rules/microsoft-bpa/index.js.map +1 -0
  104. package/dist/rules/microsoft-bpa/live-model.d.ts +3 -0
  105. package/dist/rules/microsoft-bpa/live-model.d.ts.map +1 -0
  106. package/dist/rules/microsoft-bpa/live-model.js +10 -0
  107. package/dist/rules/microsoft-bpa/live-model.js.map +1 -0
  108. package/dist/rules/microsoft-bpa/measures.d.ts +15 -0
  109. package/dist/rules/microsoft-bpa/measures.d.ts.map +1 -0
  110. package/dist/rules/microsoft-bpa/measures.js +69 -0
  111. package/dist/rules/microsoft-bpa/measures.js.map +1 -0
  112. package/dist/rules/microsoft-bpa/naming.d.ts +14 -0
  113. package/dist/rules/microsoft-bpa/naming.d.ts.map +1 -0
  114. package/dist/rules/microsoft-bpa/naming.js +47 -0
  115. package/dist/rules/microsoft-bpa/naming.js.map +1 -0
  116. package/dist/rules/microsoft-bpa/relationships.d.ts +14 -0
  117. package/dist/rules/microsoft-bpa/relationships.d.ts.map +1 -0
  118. package/dist/rules/microsoft-bpa/relationships.js +97 -0
  119. package/dist/rules/microsoft-bpa/relationships.js.map +1 -0
  120. package/dist/rules/microsoft-bpa/tables.d.ts +16 -0
  121. package/dist/rules/microsoft-bpa/tables.d.ts.map +1 -0
  122. package/dist/rules/microsoft-bpa/tables.js +147 -0
  123. package/dist/rules/microsoft-bpa/tables.js.map +1 -0
  124. package/dist/rules/parse-issue.d.ts +4 -0
  125. package/dist/rules/parse-issue.d.ts.map +1 -0
  126. package/dist/rules/parse-issue.js +19 -0
  127. package/dist/rules/parse-issue.js.map +1 -0
  128. package/dist/rules/rule-summaries.data.d.ts +3 -0
  129. package/dist/rules/rule-summaries.data.d.ts.map +1 -0
  130. package/dist/rules/rule-summaries.data.js +78 -0
  131. package/dist/rules/rule-summaries.data.js.map +1 -0
  132. package/dist/rules/types.d.ts +42 -0
  133. package/dist/rules/types.d.ts.map +1 -0
  134. package/dist/rules/types.js +15 -0
  135. package/dist/rules/types.js.map +1 -0
  136. package/dist/tmdl/parse.d.ts +7 -0
  137. package/dist/tmdl/parse.d.ts.map +1 -0
  138. package/dist/tmdl/parse.js +196 -0
  139. package/dist/tmdl/parse.js.map +1 -0
  140. package/dist/tmdl/quote.d.ts +5 -0
  141. package/dist/tmdl/quote.d.ts.map +1 -0
  142. package/dist/tmdl/quote.js +15 -0
  143. package/dist/tmdl/quote.js.map +1 -0
  144. package/dist/tmdl/types.d.ts +32 -0
  145. package/dist/tmdl/types.d.ts.map +1 -0
  146. package/dist/tmdl/types.js +2 -0
  147. package/dist/tmdl/types.js.map +1 -0
  148. package/dist/version.d.ts +2 -0
  149. package/dist/version.d.ts.map +1 -0
  150. package/dist/version.js +3 -0
  151. package/dist/version.js.map +1 -0
  152. package/package.json +51 -0
  153. package/src/engine/config.ts +91 -0
  154. package/src/engine/ignore.ts +18 -0
  155. package/src/engine/lint.ts +79 -0
  156. package/src/engine/rank.ts +67 -0
  157. package/src/engine/run.ts +67 -0
  158. package/src/format/index.ts +37 -0
  159. package/src/format/json.ts +22 -0
  160. package/src/format/markdown.ts +37 -0
  161. package/src/format/sarif.ts +85 -0
  162. package/src/format/text.ts +90 -0
  163. package/src/index/build.ts +18 -0
  164. package/src/index/references.ts +170 -0
  165. package/src/index/relationships.ts +33 -0
  166. package/src/index/usage.ts +33 -0
  167. package/src/index.ts +80 -0
  168. package/src/model/build.ts +306 -0
  169. package/src/model/names.ts +31 -0
  170. package/src/model/types.ts +165 -0
  171. package/src/rules/helpers.ts +223 -0
  172. package/src/rules/index.ts +5 -0
  173. package/src/rules/microsoft-bpa/bpa-rules.data.ts +782 -0
  174. package/src/rules/microsoft-bpa/columns.ts +192 -0
  175. package/src/rules/microsoft-bpa/define.ts +84 -0
  176. package/src/rules/microsoft-bpa/dependencies.ts +95 -0
  177. package/src/rules/microsoft-bpa/index.ts +22 -0
  178. package/src/rules/microsoft-bpa/live-model.ts +10 -0
  179. package/src/rules/microsoft-bpa/measures.ts +143 -0
  180. package/src/rules/microsoft-bpa/naming.ts +104 -0
  181. package/src/rules/microsoft-bpa/relationships.ts +189 -0
  182. package/src/rules/microsoft-bpa/tables.ts +249 -0
  183. package/src/rules/parse-issue.ts +23 -0
  184. package/src/rules/rule-summaries.data.ts +144 -0
  185. package/src/rules/types.ts +87 -0
  186. package/src/tmdl/parse.ts +194 -0
  187. package/src/tmdl/quote.ts +15 -0
  188. package/src/tmdl/types.ts +34 -0
  189. package/src/version.ts +2 -0
@@ -0,0 +1,87 @@
1
+ import type { Indexes } from "../index/build.js";
2
+ import type { Model, Named, SourceLocation } from "../model/types.js";
3
+
4
+ export type Category =
5
+ | "Performance"
6
+ | "Error Prevention"
7
+ | "DAX Expressions"
8
+ | "Maintenance"
9
+ | "Formatting"
10
+ | "Naming Conventions";
11
+
12
+ /** Ranking order of categories (spec section 6). */
13
+ export const CATEGORY_ORDER: readonly Category[] = [
14
+ "Performance",
15
+ "Error Prevention",
16
+ "DAX Expressions",
17
+ "Maintenance",
18
+ "Formatting",
19
+ "Naming Conventions",
20
+ ];
21
+
22
+ /** 1 info, 2 warning, 3 error, as in BPARules.json. */
23
+ export type Severity = 1 | 2 | 3;
24
+
25
+ export const SEVERITY_LABEL: Record<Severity, "info" | "warning" | "error"> = {
26
+ 1: "info",
27
+ 2: "warning",
28
+ 3: "error",
29
+ };
30
+
31
+ export type ObjectType =
32
+ | "Model"
33
+ | "Table"
34
+ | "CalculatedTable"
35
+ | "CalculationGroupTable"
36
+ | "Column"
37
+ | "CalculatedColumn"
38
+ | "CalculatedTableColumn"
39
+ | "Measure"
40
+ | "Partition"
41
+ | "Relationship"
42
+ | "Role"
43
+ | "TablePermission"
44
+ | "Perspective"
45
+ | "Hierarchy"
46
+ | "Level"
47
+ | "CalculationItem"
48
+ | "NamedExpression"
49
+ | "DataSource"
50
+ | "File";
51
+
52
+ export type RuleStatus = "ported" | "needsLiveModel" | "builtin";
53
+
54
+ export interface RuleContext {
55
+ indexes: Indexes;
56
+ }
57
+
58
+ /** What a rule returns. `object` is used for ignore annotations and stripped before output. */
59
+ export interface RuleFinding {
60
+ objectType: ObjectType;
61
+ objectName: string;
62
+ location?: SourceLocation;
63
+ detail?: string;
64
+ object?: Named;
65
+ }
66
+
67
+ export interface Finding {
68
+ ruleId: string;
69
+ objectType: ObjectType;
70
+ objectName: string;
71
+ location?: SourceLocation;
72
+ detail?: string;
73
+ }
74
+
75
+ export interface Rule {
76
+ id: string;
77
+ name: string;
78
+ category: Category;
79
+ severity: Severity;
80
+ scope: ObjectType[];
81
+ /** What the rule checks, one paragraph from the rule page in pbiplint's own words. */
82
+ description: string;
83
+ fixExpression?: string;
84
+ references: string[];
85
+ status: RuleStatus;
86
+ check(model: Model, ctx: RuleContext): RuleFinding[];
87
+ }
@@ -0,0 +1,194 @@
1
+ import { unquoteName, unquoteValue } from "./quote.js";
2
+ import type { ParsedFile, ParseIssue, TmdlNode } from "./types.js";
3
+
4
+ const HEADER = /^([A-Za-z_]\w*)(?:\s+(.+))?$/;
5
+ const PROP = /^([A-Za-z_]\w*):(?:\s(.*))?$/;
6
+ const REF = /^ref\s+([A-Za-z_]\w*)\s+(.+)$/;
7
+
8
+ const tabIndent = (line: string): number => {
9
+ let n = 0;
10
+ while (line[n] === "\t") n++;
11
+ return n;
12
+ };
13
+ const leadingWs = (line: string): number => line.length - line.trimStart().length;
14
+
15
+ /** Split `<type> <name> [= expr]` on the first `=` outside single quotes. */
16
+ function splitHeader(
17
+ content: string,
18
+ ): { type: string; name?: string; hasEq: boolean; inline: string } | null {
19
+ let inQuote = false;
20
+ let eqAt = -1;
21
+ for (let i = 0; i < content.length; i++) {
22
+ const ch = content[i];
23
+ if (ch === "'") inQuote = !inQuote;
24
+ else if (ch === "=" && !inQuote) {
25
+ eqAt = i;
26
+ break;
27
+ }
28
+ }
29
+ const left = (eqAt >= 0 ? content.slice(0, eqAt) : content).trim();
30
+ const inline = eqAt >= 0 ? content.slice(eqAt + 1).trim() : "";
31
+ const m = HEADER.exec(left);
32
+ if (!m) return null;
33
+ return {
34
+ type: m[1]!,
35
+ name: m[2] === undefined ? undefined : unquoteName(m[2]),
36
+ hasEq: eqAt >= 0,
37
+ inline,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Generic TMDL tree parser. Unknown object types and properties parse as generic nodes,
43
+ * so a construct this code has never seen never aborts a run.
44
+ */
45
+ export function parseTmdl(file: string, text: string): ParsedFile {
46
+ // Power BI Desktop writes TMDL as UTF-8 with a BOM; it is not part of the first line.
47
+ const body = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
48
+ const lines = body.replace(/\r\n?/g, "\n").split("\n");
49
+ const roots: TmdlNode[] = [];
50
+ const issues: ParseIssue[] = [];
51
+ const stack: TmdlNode[] = [];
52
+ let pendingDescription: string[] = [];
53
+ // The first `///` line of the pending run, for the issue reported when it leads nowhere.
54
+ let descriptionLine = 0;
55
+ let descriptionText = "";
56
+ let i = 0;
57
+
58
+ while (i < lines.length) {
59
+ const raw = lines[i]!;
60
+ const lineNo = i + 1;
61
+ if (raw.trim() === "") {
62
+ // Tabular Editor's TMDL reader rejects a blank line after a `///` description, so a
63
+ // description separated from its declaration never reaches it. Report and drop it.
64
+ if (pendingDescription.length) {
65
+ issues.push({
66
+ file,
67
+ line: descriptionLine,
68
+ text: descriptionText,
69
+ reason: "description is not followed by a declaration",
70
+ });
71
+ pendingDescription = [];
72
+ }
73
+ i++;
74
+ continue;
75
+ }
76
+ const indent = tabIndent(raw);
77
+ const content = raw.slice(indent);
78
+ if (content.startsWith("///")) {
79
+ if (!pendingDescription.length) {
80
+ descriptionLine = lineNo;
81
+ descriptionText = raw;
82
+ }
83
+ pendingDescription.push(content.replace(/^\/\/\/ ?/, ""));
84
+ i++;
85
+ continue;
86
+ }
87
+ if (/^\s/.test(content)) {
88
+ issues.push({
89
+ file,
90
+ line: lineNo,
91
+ text: raw,
92
+ reason: "space indentation (TMDL requires tabs)",
93
+ });
94
+ i++;
95
+ continue;
96
+ }
97
+
98
+ // Indented multi-line expression. The first non-blank line after the header sets the block
99
+ // indentation; the block continues while lines are blank or indented at least that much.
100
+ const collectBlock = (): string => {
101
+ let j = i + 1;
102
+ while (j < lines.length && lines[j]!.trim() === "") j++;
103
+ if (j >= lines.length) return "";
104
+ const blockIndent = leadingWs(lines[j]!);
105
+ if (blockIndent <= indent) return "";
106
+ const out: string[] = [];
107
+ let lastNonBlank = -1;
108
+ for (; j < lines.length; j++) {
109
+ const l = lines[j]!;
110
+ if (l.trim() === "") {
111
+ out.push("");
112
+ continue;
113
+ }
114
+ if (leadingWs(l) < blockIndent) break;
115
+ out.push(l.slice(blockIndent));
116
+ lastNonBlank = out.length - 1;
117
+ }
118
+ i = j - 1;
119
+ return out.slice(0, lastNonBlank + 1).join("\n");
120
+ };
121
+
122
+ // Fenced expression: header ends with ```; closed by a line that is only ```; that closing
123
+ // line's leading whitespace is the left boundary stripped from every line.
124
+ const collectFenced = (): string => {
125
+ const out: string[] = [];
126
+ let j = i + 1;
127
+ while (j < lines.length && lines[j]!.trim() !== "```") {
128
+ out.push(lines[j]!);
129
+ j++;
130
+ }
131
+ if (j >= lines.length)
132
+ issues.push({ file, line: lineNo, text: raw, reason: "unterminated ``` fence" });
133
+ const boundary = j < lines.length ? leadingWs(lines[j]!) : 0;
134
+ i = j;
135
+ return out.map((l) => l.slice(Math.min(boundary, leadingWs(l)))).join("\n");
136
+ };
137
+
138
+ const base = {
139
+ props: {} as Record<string, string | true>,
140
+ children: [] as TmdlNode[],
141
+ file,
142
+ line: lineNo,
143
+ indent,
144
+ };
145
+ let node: TmdlNode;
146
+ let m: RegExpExecArray | null;
147
+ if ((m = REF.exec(content))) {
148
+ node = { ...base, kind: "ref", type: m[1]!.toLowerCase(), name: unquoteName(m[2]!) };
149
+ } else if ((m = PROP.exec(content))) {
150
+ node = { ...base, kind: "prop", type: m[1]!.toLowerCase(), value: unquoteValue(m[2] ?? "") };
151
+ } else {
152
+ const h = splitHeader(content);
153
+ if (!h) {
154
+ issues.push({ file, line: lineNo, text: raw, reason: "unrecognized line" });
155
+ i++;
156
+ continue;
157
+ }
158
+ if (h.hasEq) {
159
+ const value =
160
+ h.inline === "```" ? collectFenced() : h.inline === "" ? collectBlock() : h.inline;
161
+ node =
162
+ h.name === undefined
163
+ ? { ...base, kind: "expr", type: h.type.toLowerCase(), value }
164
+ : { ...base, kind: "object", type: h.type.toLowerCase(), name: h.name, value };
165
+ } else if (h.name !== undefined) {
166
+ node = { ...base, kind: "object", type: h.type.toLowerCase(), name: h.name };
167
+ } else {
168
+ node = { ...base, kind: "flag", type: h.type.toLowerCase() };
169
+ }
170
+ }
171
+
172
+ if (pendingDescription.length) {
173
+ node.description = pendingDescription.join("\n");
174
+ pendingDescription = [];
175
+ }
176
+ stack.length = indent;
177
+ const parent = indent > 0 ? stack[indent - 1] : undefined;
178
+ if (indent > 0 && !parent) {
179
+ issues.push({ file, line: lineNo, text: raw, reason: "orphan indentation" });
180
+ i++;
181
+ continue;
182
+ }
183
+ if (parent) {
184
+ if (node.kind === "prop" || node.kind === "expr") parent.props[node.type] = node.value ?? "";
185
+ else if (node.kind === "flag") parent.props[node.type] = true;
186
+ parent.children.push(node);
187
+ } else {
188
+ roots.push(node);
189
+ }
190
+ stack[indent] = node;
191
+ i++;
192
+ }
193
+ return { file, roots, issues, lineCount: lines.length };
194
+ }
@@ -0,0 +1,15 @@
1
+ /** Strip single quotes from a TMDL object name; `''` inside is one quote. */
2
+ export function unquoteName(s: string): string {
3
+ const t = s.trim();
4
+ if (t.length >= 2 && t.startsWith("'") && t.endsWith("'"))
5
+ return t.slice(1, -1).replace(/''/g, "'");
6
+ return t;
7
+ }
8
+
9
+ /** Strip double quotes from a TMDL property value; `""` inside is one quote. */
10
+ export function unquoteValue(s: string): string {
11
+ const t = s.trim();
12
+ if (t.length >= 2 && t.startsWith('"') && t.endsWith('"'))
13
+ return t.slice(1, -1).replace(/""/g, '"');
14
+ return t;
15
+ }
@@ -0,0 +1,34 @@
1
+ export type TmdlNodeKind = "object" | "prop" | "flag" | "ref" | "expr";
2
+
3
+ /** One line of TMDL and everything indented beneath it. `type` and `props` keys are lowercased. */
4
+ export interface TmdlNode {
5
+ kind: TmdlNodeKind;
6
+ /** Object type (`table`, `column`), property key (`datatype`), flag (`ishidden`), or ref target type. */
7
+ type: string;
8
+ /** Unquoted object name, for `object` and `ref` nodes. */
9
+ name?: string;
10
+ /** Property value (unquoted) for `prop`; expression text for `expr` and for `object` nodes declared with `=`. */
11
+ value?: string;
12
+ /** Child properties, flags, and expressions by lowercased key. Flags are `true`. */
13
+ props: Record<string, string | true>;
14
+ children: TmdlNode[];
15
+ /** Joined `///` lines that preceded the declaration. */
16
+ description?: string;
17
+ file: string;
18
+ line: number;
19
+ indent: number;
20
+ }
21
+
22
+ export interface ParseIssue {
23
+ file: string;
24
+ line: number;
25
+ text: string;
26
+ reason: string;
27
+ }
28
+
29
+ export interface ParsedFile {
30
+ file: string;
31
+ roots: TmdlNode[];
32
+ issues: ParseIssue[];
33
+ lineCount: number;
34
+ }
package/src/version.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/sync-version.mjs from packages/core/package.json. Do not edit by hand.
2
+ export const VERSION = "0.1.0";