firebase-contract 0.1.0-rc.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.
- package/README.md +560 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/lib/_internal/compiler/compiler.d.ts +71 -0
- package/dist/lib/_internal/compiler/compiler.d.ts.map +1 -0
- package/dist/lib/_internal/compiler/compiler.js +104 -0
- package/dist/lib/_internal/compiler/index.d.ts +3 -0
- package/dist/lib/_internal/compiler/index.d.ts.map +1 -0
- package/dist/lib/_internal/compiler/index.js +1 -0
- package/dist/lib/_internal/diagnostics.d.ts +29 -0
- package/dist/lib/_internal/diagnostics.d.ts.map +1 -0
- package/dist/lib/_internal/diagnostics.js +26 -0
- package/dist/lib/_internal/format-diagnostics.d.ts +8 -0
- package/dist/lib/_internal/format-diagnostics.d.ts.map +1 -0
- package/dist/lib/_internal/format-diagnostics.js +10 -0
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/api-dto-generator.js +134 -0
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/api-types-generator.js +105 -0
- package/dist/lib/_internal/generators/api/api-validation-generator.d.ts +7 -0
- package/dist/lib/_internal/generators/api/api-validation-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/api-validation-generator.js +73 -0
- package/dist/lib/_internal/generators/api/task-payload-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/api/task-payload-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/task-payload-generator.js +96 -0
- package/dist/lib/_internal/generators/config/config-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/config/config-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/config/config-generator.js +71 -0
- package/dist/lib/_internal/generators/data-connect/adapter-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/data-connect/adapter-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/data-connect/adapter-generator.js +142 -0
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts +15 -0
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/data-connect/graphql-generator.js +209 -0
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts +18 -0
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/data-connect/operations-generator.js +539 -0
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts +11 -0
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.js +112 -0
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts +11 -0
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.js +375 -0
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.d.ts +12 -0
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.js +81 -0
- package/dist/lib/_internal/generators/generator.d.ts +23 -0
- package/dist/lib/_internal/generators/generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/generator.js +1 -0
- package/dist/lib/_internal/generators/id/id-codec-generator.d.ts +16 -0
- package/dist/lib/_internal/generators/id/id-codec-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/id/id-codec-generator.js +140 -0
- package/dist/lib/_internal/generators/index.d.ts +31 -0
- package/dist/lib/_internal/generators/index.d.ts.map +1 -0
- package/dist/lib/_internal/generators/index.js +64 -0
- package/dist/lib/_internal/generators/registry.d.ts +15 -0
- package/dist/lib/_internal/generators/registry.d.ts.map +1 -0
- package/dist/lib/_internal/generators/registry.js +24 -0
- package/dist/lib/_internal/generators/sql/sql-migration-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/sql/sql-migration-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/sql/sql-migration-generator.js +44 -0
- package/dist/lib/_internal/generators/support/constraints.d.ts +8 -0
- package/dist/lib/_internal/generators/support/constraints.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/constraints.js +39 -0
- package/dist/lib/_internal/generators/support/header.d.ts +13 -0
- package/dist/lib/_internal/generators/support/header.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/header.js +17 -0
- package/dist/lib/_internal/generators/support/naming.d.ts +10 -0
- package/dist/lib/_internal/generators/support/naming.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/naming.js +30 -0
- package/dist/lib/_internal/generators/support/relations.d.ts +12 -0
- package/dist/lib/_internal/generators/support/relations.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/relations.js +11 -0
- package/dist/lib/_internal/generators/support/split.d.ts +34 -0
- package/dist/lib/_internal/generators/support/split.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/split.js +75 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts +18 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.js +99 -0
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts +10 -0
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.js +175 -0
- package/dist/lib/_internal/generators/union/union-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/union/union-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/union/union-generator.js +37 -0
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts +7 -0
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/zod/zod-generator.js +68 -0
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/zod/zod-split-generator.js +136 -0
- package/dist/lib/_internal/ir/build-ir.d.ts +18 -0
- package/dist/lib/_internal/ir/build-ir.d.ts.map +1 -0
- package/dist/lib/_internal/ir/build-ir.js +430 -0
- package/dist/lib/_internal/ir/ir.d.ts +393 -0
- package/dist/lib/_internal/ir/ir.d.ts.map +1 -0
- package/dist/lib/_internal/ir/ir.js +22 -0
- package/dist/lib/_internal/parser/parse.d.ts +9 -0
- package/dist/lib/_internal/parser/parse.d.ts.map +1 -0
- package/dist/lib/_internal/parser/parse.js +682 -0
- package/dist/lib/_internal/parser/raw-document.d.ts +230 -0
- package/dist/lib/_internal/parser/raw-document.d.ts.map +1 -0
- package/dist/lib/_internal/parser/raw-document.js +6 -0
- package/dist/lib/_internal/resolver/import-resolver.d.ts +25 -0
- package/dist/lib/_internal/resolver/import-resolver.d.ts.map +1 -0
- package/dist/lib/_internal/resolver/import-resolver.js +73 -0
- package/dist/lib/_internal/resolver/module-loader.d.ts +25 -0
- package/dist/lib/_internal/resolver/module-loader.d.ts.map +1 -0
- package/dist/lib/_internal/resolver/module-loader.js +62 -0
- package/dist/lib/_internal/runtime/data-connect-adapter.d.ts +25 -0
- package/dist/lib/_internal/runtime/data-connect-adapter.d.ts.map +1 -0
- package/dist/lib/_internal/runtime/data-connect-adapter.js +24 -0
- package/dist/lib/_internal/runtime/index.d.ts +3 -0
- package/dist/lib/_internal/runtime/index.d.ts.map +1 -0
- package/dist/lib/_internal/runtime/index.js +1 -0
- package/dist/lib/_internal/validation/rules.d.ts +32 -0
- package/dist/lib/_internal/validation/rules.d.ts.map +1 -0
- package/dist/lib/_internal/validation/rules.js +282 -0
- package/dist/lib/_internal/validation/validate.d.ts +9 -0
- package/dist/lib/_internal/validation/validate.d.ts.map +1 -0
- package/dist/lib/_internal/validation/validate.js +6 -0
- package/dist/lib/cli/commands/generate.d.ts +3 -0
- package/dist/lib/cli/commands/generate.d.ts.map +1 -0
- package/dist/lib/cli/commands/generate.js +66 -0
- package/dist/lib/cli/commands/index.d.ts +4 -0
- package/dist/lib/cli/commands/index.d.ts.map +1 -0
- package/dist/lib/cli/commands/index.js +11 -0
- package/dist/lib/cli/commands/init.d.ts +3 -0
- package/dist/lib/cli/commands/init.d.ts.map +1 -0
- package/dist/lib/cli/commands/init.js +72 -0
- package/dist/lib/cli/commands/inspect.d.ts +3 -0
- package/dist/lib/cli/commands/inspect.d.ts.map +1 -0
- package/dist/lib/cli/commands/inspect.js +23 -0
- package/dist/lib/cli/commands/validate.d.ts +3 -0
- package/dist/lib/cli/commands/validate.d.ts.map +1 -0
- package/dist/lib/cli/commands/validate.js +23 -0
- package/dist/lib/cli/config.d.ts +12 -0
- package/dist/lib/cli/config.d.ts.map +1 -0
- package/dist/lib/cli/config.js +20 -0
- package/dist/lib/cli/index.d.ts +3 -0
- package/dist/lib/cli/index.d.ts.map +1 -0
- package/dist/lib/cli/index.js +13 -0
- package/dist/lib/cli/version.d.ts +3 -0
- package/dist/lib/cli/version.d.ts.map +1 -0
- package/dist/lib/cli/version.js +15 -0
- package/dist/lib/modules/runtime.d.ts +2 -0
- package/dist/lib/modules/runtime.d.ts.map +1 -0
- package/dist/lib/modules/runtime.js +3 -0
- package/package.json +60 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { error, warning } from '../diagnostics.js';
|
|
2
|
+
import { findModel } from '../ir/ir.js';
|
|
3
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
4
|
+
/** Every field type must resolve to a known scalar, enum, or model. */
|
|
5
|
+
export const unresolvedTypes = ir => ir.models.flatMap(model => model.fields
|
|
6
|
+
.filter(field => field.type.kind === 'unresolved')
|
|
7
|
+
.map(field => error('UNRESOLVED_TYPE', `Unknown type "${field.type.name}" on ${model.name}.${field.name}`, {
|
|
8
|
+
file: model.sourceFile,
|
|
9
|
+
path: `models.${model.name}.fields.${field.name}`,
|
|
10
|
+
})));
|
|
11
|
+
/** Enums must declare at least one value. */
|
|
12
|
+
export const emptyEnums = ir => ir.enums
|
|
13
|
+
.filter(irEnum => irEnum.values.length === 0)
|
|
14
|
+
.map(irEnum => error('EMPTY_ENUM', `Enum "${irEnum.name}" has no values`, {
|
|
15
|
+
file: irEnum.sourceFile,
|
|
16
|
+
path: `enums.${irEnum.name}`,
|
|
17
|
+
}));
|
|
18
|
+
/** Enum values should be unique. */
|
|
19
|
+
export const duplicateEnumValues = ir => ir.enums.flatMap(irEnum => {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
for (const value of irEnum.values) {
|
|
23
|
+
if (seen.has(value)) {
|
|
24
|
+
diagnostics.push(warning('DUPLICATE_ENUM_VALUE', `Enum "${irEnum.name}" repeats value "${value}"`, {
|
|
25
|
+
file: irEnum.sourceFile,
|
|
26
|
+
path: `enums.${irEnum.name}`,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
seen.add(value);
|
|
30
|
+
}
|
|
31
|
+
return diagnostics;
|
|
32
|
+
});
|
|
33
|
+
/** Model and enum names must be valid identifiers so generators can emit them. */
|
|
34
|
+
export const validNames = ir => {
|
|
35
|
+
const diagnostics = [];
|
|
36
|
+
for (const irEnum of ir.enums) {
|
|
37
|
+
if (!IDENTIFIER.test(irEnum.name)) {
|
|
38
|
+
diagnostics.push(error('INVALID_NAME', `Enum name "${irEnum.name}" is not a valid identifier`, {
|
|
39
|
+
file: irEnum.sourceFile,
|
|
40
|
+
path: `enums.${irEnum.name}`,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const model of ir.models) {
|
|
45
|
+
if (!IDENTIFIER.test(model.name)) {
|
|
46
|
+
diagnostics.push(error('INVALID_NAME', `Model name "${model.name}" is not a valid identifier`, {
|
|
47
|
+
file: model.sourceFile,
|
|
48
|
+
path: `models.${model.name}`,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
for (const field of model.fields) {
|
|
52
|
+
if (!IDENTIFIER.test(field.name)) {
|
|
53
|
+
diagnostics.push(error('INVALID_NAME', `Field name "${field.name}" on ${model.name} is not a valid identifier`, {
|
|
54
|
+
file: model.sourceFile,
|
|
55
|
+
path: `models.${model.name}.fields.${field.name}`,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return diagnostics;
|
|
61
|
+
};
|
|
62
|
+
/** Primary-identifier constraints: at most one, not a list, ideally required. */
|
|
63
|
+
export const idFields = ir => ir.models.flatMap(model => {
|
|
64
|
+
const diagnostics = [];
|
|
65
|
+
const ids = model.fields.filter(field => field.isId);
|
|
66
|
+
if (ids.length > 1) {
|
|
67
|
+
diagnostics.push(error('MULTIPLE_IDS', `Model "${model.name}" declares ${ids.length} id fields; only one is allowed`, {
|
|
68
|
+
file: model.sourceFile,
|
|
69
|
+
path: `models.${model.name}`,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
for (const field of ids) {
|
|
73
|
+
if (field.list) {
|
|
74
|
+
diagnostics.push(error('INVALID_ID', `Id field "${model.name}.${field.name}" cannot be a list`, {
|
|
75
|
+
file: model.sourceFile,
|
|
76
|
+
path: `models.${model.name}.fields.${field.name}`,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
if (field.optional) {
|
|
80
|
+
diagnostics.push(warning('OPTIONAL_ID', `Id field "${model.name}.${field.name}" is optional`, {
|
|
81
|
+
file: model.sourceFile,
|
|
82
|
+
path: `models.${model.name}.fields.${field.name}`,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return diagnostics;
|
|
87
|
+
});
|
|
88
|
+
/** Composite key and index field names must exist on the model. */
|
|
89
|
+
export const modelKeysAndIndexes = ir => ir.models.flatMap(model => {
|
|
90
|
+
const diagnostics = [];
|
|
91
|
+
const fieldNames = new Set(model.fields.map(field => field.name));
|
|
92
|
+
// Index/unique fields may also reference a relation's FK *column* name
|
|
93
|
+
// (`ownerId` for relation field `owner`), matching real DC schemas.
|
|
94
|
+
for (const field of model.fields) {
|
|
95
|
+
if (field.relation && field.type.kind === 'model') {
|
|
96
|
+
fieldNames.add(`${field.name}Id`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const name of model.key) {
|
|
100
|
+
if (!fieldNames.has(name)) {
|
|
101
|
+
diagnostics.push(error('UNKNOWN_KEY_FIELD', `Model "${model.name}" key references unknown field "${name}"`, {
|
|
102
|
+
file: model.sourceFile,
|
|
103
|
+
path: `models.${model.name}.key`,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const index of model.indexes) {
|
|
108
|
+
for (const name of index.fields) {
|
|
109
|
+
if (!fieldNames.has(name)) {
|
|
110
|
+
diagnostics.push(error('UNKNOWN_INDEX_FIELD', `Model "${model.name}" index references unknown field "${name}"`, {
|
|
111
|
+
file: model.sourceFile,
|
|
112
|
+
path: `models.${model.name}.indexes`,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return diagnostics;
|
|
118
|
+
});
|
|
119
|
+
/** A `relation` flag is only meaningful on a model-typed field. */
|
|
120
|
+
export const relationFields = ir => ir.models.flatMap(model => model.fields
|
|
121
|
+
.filter(field => field.relation && field.type.kind !== 'model')
|
|
122
|
+
.map(field => error('INVALID_RELATION', `Field "${model.name}.${field.name}" is marked relation but its type is not a model`, {
|
|
123
|
+
file: model.sourceFile,
|
|
124
|
+
path: `models.${model.name}.fields.${field.name}`,
|
|
125
|
+
})));
|
|
126
|
+
/** Operations must target a known model, use valid field names, and pair type/action correctly. */
|
|
127
|
+
export const operations = ir => ir.operations.flatMap(op => {
|
|
128
|
+
const diagnostics = [];
|
|
129
|
+
const model = findModel(ir, op.model);
|
|
130
|
+
const loc = { file: op.sourceFile, path: `operations.${op.name}` };
|
|
131
|
+
if (!model) {
|
|
132
|
+
diagnostics.push(error('UNKNOWN_OPERATION_MODEL', `Operation "${op.name}" targets unknown model "${op.model}"`, loc));
|
|
133
|
+
return diagnostics;
|
|
134
|
+
}
|
|
135
|
+
// Raw operations are emitted verbatim; their body is not described by
|
|
136
|
+
// inputs/where/select, so field-level checks do not apply.
|
|
137
|
+
if (op.raw !== undefined)
|
|
138
|
+
return diagnostics;
|
|
139
|
+
const fieldNames = new Set(model.fields.map(field => field.name));
|
|
140
|
+
const checkField = (kind, name) => {
|
|
141
|
+
if (!fieldNames.has(name)) {
|
|
142
|
+
diagnostics.push(error('UNKNOWN_OPERATION_FIELD', `Operation "${op.name}" ${kind} references unknown field "${name}" on ${op.model}`, loc));
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
for (const input of op.inputs)
|
|
146
|
+
checkField('inputs', input.field);
|
|
147
|
+
const relFkNames = new Set(model.fields.filter(f => f.relation && f.type.kind === 'model').map(f => `${f.name}Id`));
|
|
148
|
+
const checkWherePath = (path) => {
|
|
149
|
+
const dot = path.indexOf('.');
|
|
150
|
+
if (dot <= 0) {
|
|
151
|
+
checkField('where', path);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const rel = model.fields.find(f => f.name === path.slice(0, dot));
|
|
155
|
+
if (!rel || rel.type.kind !== 'model') {
|
|
156
|
+
checkField('where', path.slice(0, dot));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const related = findModel(ir, rel.type.name);
|
|
160
|
+
if (related && !related.fields.some(f => f.name === path.slice(dot + 1))) {
|
|
161
|
+
diagnostics.push(error('UNKNOWN_OPERATION_FIELD', `Operation "${op.name}" where references unknown field "${path}" on ${op.model}`, loc));
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const checkSelect = (entries, m) => {
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
// Reverse-join selections (x_on_y) reference the related table's side;
|
|
167
|
+
// they are rendered verbatim and not validated against this model.
|
|
168
|
+
if (entry.field.includes('_on_'))
|
|
169
|
+
continue;
|
|
170
|
+
const field = m.fields.find(f => f.name === entry.field);
|
|
171
|
+
if (!field) {
|
|
172
|
+
diagnostics.push(error('UNKNOWN_OPERATION_FIELD', `Operation "${op.name}" select references unknown field "${entry.field}" on ${m.name}`, loc));
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (entry.select && entry.select.length > 0) {
|
|
176
|
+
if (field.type.kind === 'model') {
|
|
177
|
+
const related = findModel(ir, field.type.name);
|
|
178
|
+
if (related)
|
|
179
|
+
checkSelect(entry.select, related);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
checkSelect(op.select, model);
|
|
185
|
+
for (const where of op.where)
|
|
186
|
+
checkWherePath(where.field);
|
|
187
|
+
for (const order of op.orderBy) {
|
|
188
|
+
if (!relFkNames.has(order.field))
|
|
189
|
+
checkField('orderBy', order.field);
|
|
190
|
+
}
|
|
191
|
+
for (const expr of op.exprs)
|
|
192
|
+
checkField('exprs', expr.field);
|
|
193
|
+
for (const entry of op.inc)
|
|
194
|
+
checkField('inc', entry.field);
|
|
195
|
+
if (op.operationType === 'mutation' && !op.action) {
|
|
196
|
+
diagnostics.push(error('MISSING_MUTATION_ACTION', `Mutation "${op.name}" needs an "action" (insert|update|upsert|delete)`, loc));
|
|
197
|
+
}
|
|
198
|
+
if (op.operationType === 'query' && op.action) {
|
|
199
|
+
diagnostics.push(warning('UNUSED_ACTION', `Query "${op.name}" ignores its "action"`, loc));
|
|
200
|
+
}
|
|
201
|
+
if (op.auth === 'PUBLIC' && !op.authReason) {
|
|
202
|
+
diagnostics.push(warning('MISSING_AUTH_REASON', `Operation "${op.name}" is PUBLIC without an "authReason"`, loc));
|
|
203
|
+
}
|
|
204
|
+
return diagnostics;
|
|
205
|
+
});
|
|
206
|
+
/** API request/response payloads that reference a model must reference a known one. */
|
|
207
|
+
export const apis = ir => ir.apis.flatMap(api => {
|
|
208
|
+
const diagnostics = [];
|
|
209
|
+
const loc = { file: api.sourceFile, path: `apis.${api.name}` };
|
|
210
|
+
const checkPayload = (payload, which) => {
|
|
211
|
+
if (payload.model && !findModel(ir, payload.model)) {
|
|
212
|
+
diagnostics.push(error('UNKNOWN_API_MODEL', `Api "${api.name}" ${which} references unknown model "${payload.model}"`, loc));
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
checkPayload(api.request, 'request');
|
|
216
|
+
checkPayload(api.response, 'response');
|
|
217
|
+
return diagnostics;
|
|
218
|
+
});
|
|
219
|
+
/** Firestore projections must derive from a known model with valid pick/omit fields. */
|
|
220
|
+
export const firestore = ir => ir.firestore.flatMap(doc => {
|
|
221
|
+
const diagnostics = [];
|
|
222
|
+
const loc = { file: doc.sourceFile, path: `firestore.${doc.name}` };
|
|
223
|
+
if (doc.pick.length > 0 && doc.omit.length > 0) {
|
|
224
|
+
diagnostics.push(warning('PICK_AND_OMIT', `Firestore doc "${doc.name}" sets both pick and omit; omit is ignored`, loc));
|
|
225
|
+
}
|
|
226
|
+
if (!doc.from) {
|
|
227
|
+
// A projection with no base model must declare its own fields.
|
|
228
|
+
if (doc.fields.length === 0) {
|
|
229
|
+
diagnostics.push(error('EMPTY_FIRESTORE_DOC', `Firestore doc "${doc.name}" has no "from" and no fields`, loc));
|
|
230
|
+
}
|
|
231
|
+
return diagnostics;
|
|
232
|
+
}
|
|
233
|
+
const model = findModel(ir, doc.from);
|
|
234
|
+
if (!model) {
|
|
235
|
+
diagnostics.push(error('UNKNOWN_FIRESTORE_MODEL', `Firestore doc "${doc.name}" derives from unknown model "${doc.from}"`, loc));
|
|
236
|
+
return diagnostics;
|
|
237
|
+
}
|
|
238
|
+
const fieldNames = new Set(model.fields.map(field => field.name));
|
|
239
|
+
for (const [kind, names] of [
|
|
240
|
+
['pick', doc.pick],
|
|
241
|
+
['omit', doc.omit],
|
|
242
|
+
]) {
|
|
243
|
+
for (const name of names) {
|
|
244
|
+
if (!fieldNames.has(name)) {
|
|
245
|
+
diagnostics.push(error('UNKNOWN_FIRESTORE_FIELD', `Firestore doc "${doc.name}" ${kind} references unknown field "${name}" on ${doc.from}`, loc));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return diagnostics;
|
|
250
|
+
});
|
|
251
|
+
/** Union variants must be known models, each carrying the discriminant field. */
|
|
252
|
+
export const unions = ir => ir.unions.flatMap(union => {
|
|
253
|
+
const diagnostics = [];
|
|
254
|
+
const loc = { file: union.sourceFile, path: `unions.${union.name}` };
|
|
255
|
+
if (union.variants.length === 0) {
|
|
256
|
+
diagnostics.push(error('EMPTY_UNION', `Union "${union.name}" has no variants`, loc));
|
|
257
|
+
}
|
|
258
|
+
for (const variant of union.variants) {
|
|
259
|
+
const model = findModel(ir, variant);
|
|
260
|
+
if (!model) {
|
|
261
|
+
diagnostics.push(error('UNKNOWN_UNION_VARIANT', `Union "${union.name}" references unknown model "${variant}"`, loc));
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (!model.fields.some(field => field.name === union.discriminant)) {
|
|
265
|
+
diagnostics.push(error('MISSING_DISCRIMINANT', `Union "${union.name}" variant "${variant}" lacks discriminant field "${union.discriminant}"`, loc));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return diagnostics;
|
|
269
|
+
});
|
|
270
|
+
export const DEFAULT_RULES = [
|
|
271
|
+
unresolvedTypes,
|
|
272
|
+
emptyEnums,
|
|
273
|
+
duplicateEnumValues,
|
|
274
|
+
validNames,
|
|
275
|
+
idFields,
|
|
276
|
+
modelKeysAndIndexes,
|
|
277
|
+
relationFields,
|
|
278
|
+
operations,
|
|
279
|
+
apis,
|
|
280
|
+
firestore,
|
|
281
|
+
unions,
|
|
282
|
+
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Diagnostic } from '../diagnostics.js';
|
|
2
|
+
import { Ir } from '../ir/ir.js';
|
|
3
|
+
import { ValidationRule } from './rules.js';
|
|
4
|
+
/**
|
|
5
|
+
* Run semantic validation over the IR. Callers may pass a custom rule set to
|
|
6
|
+
* add or replace checks; by default the built-in {@link DEFAULT_RULES} run.
|
|
7
|
+
*/
|
|
8
|
+
export declare const validateIr: (ir: Ir, rules?: ValidationRule[]) => Diagnostic[];
|
|
9
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/validation/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAEhC,OAAO,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,IAAI,EAAE,EAAE,QAAO,cAAc,EAAkB,KAAG,UAAU,EACtD,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DEFAULT_RULES } from './rules.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run semantic validation over the IR. Callers may pass a custom rule set to
|
|
4
|
+
* add or replace checks; by default the built-in {@link DEFAULT_RULES} run.
|
|
5
|
+
*/
|
|
6
|
+
export const validateIr = (ir, rules = DEFAULT_RULES) => rules.flatMap(rule => rule(ir));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../../src/lib/cli/commands/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAenC,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,KAAG,IAqEnD,CAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { generate, generateAll } from '../../_internal/compiler/compiler.js';
|
|
2
|
+
import { DEFAULT_HEADER, toHeaderComment } from '../../_internal/generators/support/header.js';
|
|
3
|
+
import { countBySeverity, formatDiagnostics } from '../../_internal/format-diagnostics.js';
|
|
4
|
+
import { createDefaultRegistry } from '../../_internal/generators/index.js';
|
|
5
|
+
import { loadConfig } from '../config.js';
|
|
6
|
+
export const registerGenerate = (program) => {
|
|
7
|
+
program
|
|
8
|
+
.command('generate')
|
|
9
|
+
.description('Generate outputs from a contract')
|
|
10
|
+
.argument('[entry]', 'entry contract file')
|
|
11
|
+
.option('-o, --out-dir <dir>', 'output directory')
|
|
12
|
+
.option('-g, --generators <list>', 'comma-separated generator names')
|
|
13
|
+
.option('--header [text]', 'prepend a comment header to generated files (bare --header = default banner; omitted = contract `header:` or none)')
|
|
14
|
+
.action((entryArg, flags) => {
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
const entry = entryArg ?? config.entry ?? 'contract.yml';
|
|
17
|
+
const outDir = flags.outDir ?? config.outDir ?? 'generated';
|
|
18
|
+
const generators = flags.generators?.split(',').map(name => name.trim()).filter(Boolean) ?? config.generators;
|
|
19
|
+
const header = flags.header === true ? DEFAULT_HEADER : typeof flags.header === 'string' ? toHeaderComment(flags.header) : undefined;
|
|
20
|
+
const registry = createDefaultRegistry();
|
|
21
|
+
const context = header !== undefined ? { header } : undefined;
|
|
22
|
+
// Without -o/-g, run every `generate:` target declared in the contract
|
|
23
|
+
// graph — the one-command flow. Flags switch back to single-target mode.
|
|
24
|
+
const explicit = flags.outDir !== undefined || flags.generators !== undefined || config.outDir !== undefined || config.generators !== undefined;
|
|
25
|
+
if (!explicit) {
|
|
26
|
+
const all = generateAll(entry, { registry, write: true, ...(context ? { context } : {}) });
|
|
27
|
+
if (all.targets.length > 0 || !all.ok) {
|
|
28
|
+
if (all.diagnostics.length > 0) {
|
|
29
|
+
console.log(formatDiagnostics(all.diagnostics));
|
|
30
|
+
}
|
|
31
|
+
if (!all.ok) {
|
|
32
|
+
const { errors } = countBySeverity(all.diagnostics);
|
|
33
|
+
console.error(`\n✖ generation aborted: ${errors} error(s)`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
let total = 0;
|
|
37
|
+
for (const target of all.targets) {
|
|
38
|
+
total += target.files.length;
|
|
39
|
+
console.log(`✔ ${target.files.length} file(s) → ${target.outDir} (${target.generators.join(', ')})`);
|
|
40
|
+
}
|
|
41
|
+
console.log(`\n✔ ${total} file(s) written from ${all.targets.length} target(s)`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// No declared targets anywhere: fall through to single-target mode.
|
|
45
|
+
}
|
|
46
|
+
const result = generate(entry, {
|
|
47
|
+
outDir,
|
|
48
|
+
generators,
|
|
49
|
+
registry,
|
|
50
|
+
write: true,
|
|
51
|
+
...(context ? { context } : {}),
|
|
52
|
+
});
|
|
53
|
+
if (result.diagnostics.length > 0) {
|
|
54
|
+
console.log(formatDiagnostics(result.diagnostics));
|
|
55
|
+
}
|
|
56
|
+
if (!result.ok) {
|
|
57
|
+
const { errors } = countBySeverity(result.diagnostics);
|
|
58
|
+
console.error(`\n✖ generation aborted: ${errors} error(s)`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
for (const file of result.files) {
|
|
62
|
+
console.log(`generated ${file.path}`);
|
|
63
|
+
}
|
|
64
|
+
console.log(`\n✔ ${result.files.length} file(s) written to ${outDir}`);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/cli/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOnC,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,KAAG,IAKnD,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerGenerate } from './generate.js';
|
|
2
|
+
import { registerInit } from './init.js';
|
|
3
|
+
import { registerInspect } from './inspect.js';
|
|
4
|
+
import { registerValidate } from './validate.js';
|
|
5
|
+
/** Register every CLI command on the program. */
|
|
6
|
+
export const registerCommands = (program) => {
|
|
7
|
+
registerInit(program);
|
|
8
|
+
registerValidate(program);
|
|
9
|
+
registerGenerate(program);
|
|
10
|
+
registerInspect(program);
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/lib/cli/commands/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAgEnC,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,IAgB/C,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { CONFIG_FILENAME } from '../config.js';
|
|
4
|
+
const EXAMPLE_CONTRACT = `version: 1
|
|
5
|
+
|
|
6
|
+
enums:
|
|
7
|
+
ProductStatus:
|
|
8
|
+
description: Lifecycle state of a product
|
|
9
|
+
values:
|
|
10
|
+
- todo
|
|
11
|
+
- in_progress
|
|
12
|
+
- done
|
|
13
|
+
|
|
14
|
+
models:
|
|
15
|
+
User:
|
|
16
|
+
description: An application user
|
|
17
|
+
fields:
|
|
18
|
+
id: { type: id, id: true }
|
|
19
|
+
name: string
|
|
20
|
+
email: string
|
|
21
|
+
|
|
22
|
+
Product:
|
|
23
|
+
description: A unit of work
|
|
24
|
+
fields:
|
|
25
|
+
id: { type: id, id: true }
|
|
26
|
+
title: string
|
|
27
|
+
status: ProductStatus
|
|
28
|
+
priority: { type: int, optional: true }
|
|
29
|
+
tags: { type: string, list: true }
|
|
30
|
+
assignee: { type: User, optional: true }
|
|
31
|
+
metadata: { type: json, optional: true }
|
|
32
|
+
createdAt: timestamp
|
|
33
|
+
`;
|
|
34
|
+
const EXAMPLE_CONFIG = JSON.stringify({
|
|
35
|
+
entry: 'contract.yml',
|
|
36
|
+
outDir: 'generated',
|
|
37
|
+
generators: [
|
|
38
|
+
'typescript',
|
|
39
|
+
'zod',
|
|
40
|
+
'data-connect-graphql',
|
|
41
|
+
'data-connect-operations',
|
|
42
|
+
'data-connect-adapter',
|
|
43
|
+
'firestore-types',
|
|
44
|
+
'firestore',
|
|
45
|
+
'api-types',
|
|
46
|
+
'api-validation',
|
|
47
|
+
],
|
|
48
|
+
}, null, 2) + '\n';
|
|
49
|
+
const writeIfAbsent = (path, content, force) => {
|
|
50
|
+
if (existsSync(path) && !force) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
writeFileSync(path, content, 'utf8');
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
export const registerInit = (program) => {
|
|
57
|
+
program
|
|
58
|
+
.command('init')
|
|
59
|
+
.description('Scaffold a starter contract.yml and firebase-contract.json')
|
|
60
|
+
.option('-f, --force', 'overwrite existing files', false)
|
|
61
|
+
.action((options) => {
|
|
62
|
+
const cwd = process.cwd();
|
|
63
|
+
const targets = [
|
|
64
|
+
[resolve(cwd, 'contract.yml'), EXAMPLE_CONTRACT],
|
|
65
|
+
[resolve(cwd, CONFIG_FILENAME), EXAMPLE_CONFIG],
|
|
66
|
+
];
|
|
67
|
+
for (const [path, content] of targets) {
|
|
68
|
+
const written = writeIfAbsent(path, content, Boolean(options.force));
|
|
69
|
+
console.log(written ? `created ${path}` : `skipped ${path} (already exists)`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../../../src/lib/cli/commands/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOnC,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAmBlD,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { compile } from '../../_internal/compiler/compiler.js';
|
|
2
|
+
import { formatDiagnostics } from '../../_internal/format-diagnostics.js';
|
|
3
|
+
import { hasErrors } from '../../_internal/diagnostics.js';
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
export const registerInspect = (program) => {
|
|
6
|
+
program
|
|
7
|
+
.command('inspect')
|
|
8
|
+
.description('Print the normalized IR for a contract (for debugging)')
|
|
9
|
+
.argument('[entry]', 'entry contract file')
|
|
10
|
+
.action((entryArg) => {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const entry = entryArg ?? config.entry ?? 'contract.yml';
|
|
13
|
+
const { ir, diagnostics } = compile(entry);
|
|
14
|
+
if (diagnostics.length > 0) {
|
|
15
|
+
console.error(formatDiagnostics(diagnostics));
|
|
16
|
+
console.error('');
|
|
17
|
+
}
|
|
18
|
+
console.log(JSON.stringify(ir, null, 2));
|
|
19
|
+
if (hasErrors(diagnostics)) {
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/lib/cli/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAMnC,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,KAAG,IAoBnD,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { compile } from '../../_internal/compiler/compiler.js';
|
|
2
|
+
import { countBySeverity, formatDiagnostics } from '../../_internal/format-diagnostics.js';
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
export const registerValidate = (program) => {
|
|
5
|
+
program
|
|
6
|
+
.command('validate')
|
|
7
|
+
.description('Parse, resolve imports, and semantically validate a contract')
|
|
8
|
+
.argument('[entry]', 'entry contract file')
|
|
9
|
+
.action((entryArg) => {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const entry = entryArg ?? config.entry ?? 'contract.yml';
|
|
12
|
+
const { diagnostics } = compile(entry);
|
|
13
|
+
if (diagnostics.length > 0) {
|
|
14
|
+
console.log(formatDiagnostics(diagnostics));
|
|
15
|
+
}
|
|
16
|
+
const { errors, warnings } = countBySeverity(diagnostics);
|
|
17
|
+
if (errors > 0) {
|
|
18
|
+
console.error(`\n✖ ${errors} error(s), ${warnings} warning(s)`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
console.log(`✔ ${entry} is valid${warnings > 0 ? ` (${warnings} warning(s))` : ''}`);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const CONFIG_FILENAME = "firebase-contract.json";
|
|
2
|
+
export interface FbcConfig {
|
|
3
|
+
entry?: string;
|
|
4
|
+
outDir?: string;
|
|
5
|
+
generators?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Load `firebase-contract.json` from `cwd` if present. CLI flags override these
|
|
9
|
+
* values; the config only supplies defaults so common invocations stay short.
|
|
10
|
+
*/
|
|
11
|
+
export declare const loadConfig: (cwd?: string) => FbcConfig;
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,2BAA2B,CAAA;AAEvD,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,MAAK,MAAsB,KAAG,SAWxD,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export const CONFIG_FILENAME = 'firebase-contract.json';
|
|
4
|
+
/**
|
|
5
|
+
* Load `firebase-contract.json` from `cwd` if present. CLI flags override these
|
|
6
|
+
* values; the config only supplies defaults so common invocations stay short.
|
|
7
|
+
*/
|
|
8
|
+
export const loadConfig = (cwd = process.cwd()) => {
|
|
9
|
+
const path = resolve(cwd, CONFIG_FILENAME);
|
|
10
|
+
if (!existsSync(path)) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
15
|
+
return parsed ?? {};
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { registerCommands } from './commands/index.js';
|
|
4
|
+
import { getVersion } from './version.js';
|
|
5
|
+
program
|
|
6
|
+
.name('firebase-contract')
|
|
7
|
+
.description('Generate types, validation, and Firebase artifacts from a YAML contract')
|
|
8
|
+
.version(getVersion());
|
|
9
|
+
registerCommands(program);
|
|
10
|
+
program.parseAsync(process.argv).catch((cause) => {
|
|
11
|
+
console.error(cause instanceof Error ? cause.message : String(cause));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/version.ts"],"names":[],"mappings":"AAIA,iFAAiF;AACjF,eAAO,MAAM,UAAU,QAAO,MAS7B,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/** Read the package version from package.json relative to the built CLI file. */
|
|
5
|
+
export const getVersion = () => {
|
|
6
|
+
try {
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
// dist/lib/cli/version.js -> package root is three levels up.
|
|
9
|
+
const pkg = JSON.parse(readFileSync(join(here, '../../../package.json'), 'utf8'));
|
|
10
|
+
return pkg.version ?? '0.0.0';
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return '0.0.0';
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/lib/modules/runtime.ts"],"names":[],"mappings":"AAEA,cAAc,4BAA4B,CAAA"}
|