instant-cli 0.22.96-experimental.add-posthog-frontend.20386914944.1 → 0.22.96
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/.turbo/turbo-build.log +1 -1
- package/__tests__/mergeSchema.test.ts +197 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/util/mergeSchema.d.ts +2 -0
- package/dist/util/mergeSchema.d.ts.map +1 -0
- package/dist/util/mergeSchema.js +334 -0
- package/dist/util/mergeSchema.js.map +1 -0
- package/package.json +6 -4
- package/src/index.js +28 -10
- package/src/util/mergeSchema.js +364 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import * as acorn from 'acorn';
|
|
2
|
+
import tsPlugin from 'acorn-typescript';
|
|
3
|
+
const node = acorn.Parser.extend(tsPlugin({ dts: false }));
|
|
4
|
+
// --- Import Handling Helpers ---
|
|
5
|
+
function collectImports(ast) {
|
|
6
|
+
const imports = new Map(); // localName -> { source, importedName, type, importKind }
|
|
7
|
+
for (const node of ast.body) {
|
|
8
|
+
if (node.type === 'ImportDeclaration') {
|
|
9
|
+
const source = node.source.value;
|
|
10
|
+
const declImportKind = node.importKind;
|
|
11
|
+
for (const specifier of node.specifiers) {
|
|
12
|
+
let kind = 'value';
|
|
13
|
+
if (declImportKind === 'type' || specifier.importKind === 'type') {
|
|
14
|
+
kind = 'type';
|
|
15
|
+
}
|
|
16
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
17
|
+
imports.set(specifier.local.name, {
|
|
18
|
+
source,
|
|
19
|
+
importedName: specifier.imported.name,
|
|
20
|
+
type: 'named',
|
|
21
|
+
importKind: kind,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (specifier.type === 'ImportDefaultSpecifier') {
|
|
25
|
+
imports.set(specifier.local.name, {
|
|
26
|
+
source,
|
|
27
|
+
type: 'default',
|
|
28
|
+
importKind: kind,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
32
|
+
imports.set(specifier.local.name, {
|
|
33
|
+
source,
|
|
34
|
+
type: 'namespace',
|
|
35
|
+
importKind: kind,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return imports;
|
|
42
|
+
}
|
|
43
|
+
function collectExistingImports(ast) {
|
|
44
|
+
const existing = new Map(); // source -> Set<localName>
|
|
45
|
+
for (const node of ast.body) {
|
|
46
|
+
if (node.type === 'ImportDeclaration') {
|
|
47
|
+
const source = node.source.value;
|
|
48
|
+
if (!existing.has(source))
|
|
49
|
+
existing.set(source, new Set());
|
|
50
|
+
for (const specifier of node.specifiers) {
|
|
51
|
+
existing.get(source).add(specifier.local.name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return existing;
|
|
56
|
+
}
|
|
57
|
+
function findIdentifiers(node, set = new Set()) {
|
|
58
|
+
if (!node)
|
|
59
|
+
return set;
|
|
60
|
+
// If it's a Type Reference, grab the name
|
|
61
|
+
if (node.type === 'TSTypeReference') {
|
|
62
|
+
if (node.typeName.type === 'Identifier') {
|
|
63
|
+
set.add(node.typeName.name);
|
|
64
|
+
}
|
|
65
|
+
else if (node.typeName.type === 'TSQualifiedName') {
|
|
66
|
+
// For A.B, we need A
|
|
67
|
+
let left = node.typeName.left;
|
|
68
|
+
while (left.type === 'TSQualifiedName') {
|
|
69
|
+
left = left.left;
|
|
70
|
+
}
|
|
71
|
+
if (left.type === 'Identifier') {
|
|
72
|
+
set.add(left.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Recursive walk
|
|
77
|
+
for (const key in node) {
|
|
78
|
+
if (key === 'loc' || key === 'start' || key === 'end')
|
|
79
|
+
continue;
|
|
80
|
+
if (typeof node[key] === 'object' && node[key] !== null) {
|
|
81
|
+
if (Array.isArray(node[key])) {
|
|
82
|
+
node[key].forEach((child) => findIdentifiers(child, set));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
findIdentifiers(node[key], set);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return set;
|
|
90
|
+
}
|
|
91
|
+
// --- Schema Traversal Helpers ---
|
|
92
|
+
function getPropName(prop) {
|
|
93
|
+
if (prop.key.type === 'Identifier')
|
|
94
|
+
return prop.key.name;
|
|
95
|
+
if (prop.key.type === 'Literal')
|
|
96
|
+
return prop.key.value;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function analyzeChain(node) {
|
|
100
|
+
let curr = node;
|
|
101
|
+
let typeParams = null;
|
|
102
|
+
let baseCall = null;
|
|
103
|
+
while (curr.type === 'CallExpression') {
|
|
104
|
+
if (curr.typeParameters) {
|
|
105
|
+
typeParams = curr.typeParameters;
|
|
106
|
+
}
|
|
107
|
+
if (curr.callee.type === 'MemberExpression' &&
|
|
108
|
+
curr.callee.object.type === 'Identifier' &&
|
|
109
|
+
curr.callee.object.name === 'i') {
|
|
110
|
+
baseCall = curr;
|
|
111
|
+
}
|
|
112
|
+
if (curr.callee.type === 'MemberExpression') {
|
|
113
|
+
curr = curr.callee.object;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { typeParams, baseCall };
|
|
120
|
+
}
|
|
121
|
+
function traverseSchema(node, path, callback) {
|
|
122
|
+
if (node.type === 'ObjectExpression') {
|
|
123
|
+
for (const prop of node.properties) {
|
|
124
|
+
const name = getPropName(prop);
|
|
125
|
+
if (!name)
|
|
126
|
+
continue;
|
|
127
|
+
const newPath = path ? `${path}.${name}` : name;
|
|
128
|
+
if (prop.value.type === 'ObjectExpression') {
|
|
129
|
+
traverseSchema(prop.value, newPath, callback);
|
|
130
|
+
}
|
|
131
|
+
else if (prop.value.type === 'CallExpression') {
|
|
132
|
+
let isEntity = false;
|
|
133
|
+
if (prop.value.callee.type === 'MemberExpression' &&
|
|
134
|
+
prop.value.callee.property.name === 'entity' &&
|
|
135
|
+
prop.value.callee.object.type === 'Identifier' &&
|
|
136
|
+
prop.value.callee.object.name === 'i') {
|
|
137
|
+
isEntity = true;
|
|
138
|
+
}
|
|
139
|
+
if (isEntity) {
|
|
140
|
+
callback(prop.value, newPath);
|
|
141
|
+
if (prop.value.arguments.length > 0) {
|
|
142
|
+
traverseSchema(prop.value.arguments[0], newPath, callback);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
callback(prop.value, newPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function findSchemaObject(ast) {
|
|
153
|
+
let schemaObj = null;
|
|
154
|
+
function walk(node) {
|
|
155
|
+
if (!node)
|
|
156
|
+
return;
|
|
157
|
+
if (schemaObj)
|
|
158
|
+
return;
|
|
159
|
+
if (node.type === 'CallExpression' &&
|
|
160
|
+
node.callee.type === 'MemberExpression' &&
|
|
161
|
+
node.callee.object.name === 'i' &&
|
|
162
|
+
node.callee.property.name === 'schema' &&
|
|
163
|
+
node.arguments.length > 0) {
|
|
164
|
+
schemaObj = node.arguments[0];
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
for (const key in node) {
|
|
168
|
+
if (key === 'loc' || key === 'start' || key === 'end')
|
|
169
|
+
continue;
|
|
170
|
+
if (node[key] && typeof node[key] === 'object') {
|
|
171
|
+
if (Array.isArray(node[key])) {
|
|
172
|
+
node[key].forEach(walk);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
walk(node[key]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
walk(ast);
|
|
181
|
+
return schemaObj;
|
|
182
|
+
}
|
|
183
|
+
export function mergeSchema(oldFile, newFile) {
|
|
184
|
+
const oldParsed = node.parse(oldFile, {
|
|
185
|
+
sourceType: 'module',
|
|
186
|
+
ecmaVersion: 'latest',
|
|
187
|
+
locations: true,
|
|
188
|
+
});
|
|
189
|
+
const newParsed = node.parse(newFile, {
|
|
190
|
+
sourceType: 'module',
|
|
191
|
+
ecmaVersion: 'latest',
|
|
192
|
+
locations: true,
|
|
193
|
+
});
|
|
194
|
+
const schemaMap = new Map(); // Path -> { src, ast }
|
|
195
|
+
// 1. Extract from old file
|
|
196
|
+
const oldSchemaObj = findSchemaObject(oldParsed);
|
|
197
|
+
if (oldSchemaObj) {
|
|
198
|
+
traverseSchema(oldSchemaObj, '', (node, path) => {
|
|
199
|
+
const { typeParams } = analyzeChain(node);
|
|
200
|
+
if (typeParams) {
|
|
201
|
+
const src = oldFile.slice(typeParams.start, typeParams.end);
|
|
202
|
+
schemaMap.set(path, { src, ast: typeParams });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// 2. Collect Imports
|
|
207
|
+
const oldImports = collectImports(oldParsed);
|
|
208
|
+
const newExistingImports = collectExistingImports(newParsed);
|
|
209
|
+
const neededIdentifiers = new Set();
|
|
210
|
+
// 3. Apply to new file & Collect needed identifiers
|
|
211
|
+
const edits = [];
|
|
212
|
+
const newSchemaObj = findSchemaObject(newParsed);
|
|
213
|
+
if (newSchemaObj) {
|
|
214
|
+
traverseSchema(newSchemaObj, '', (node, path) => {
|
|
215
|
+
const { typeParams, baseCall } = analyzeChain(node);
|
|
216
|
+
const stored = schemaMap.get(path);
|
|
217
|
+
if (stored) {
|
|
218
|
+
// Collect identifiers from the type params we are about to inject
|
|
219
|
+
findIdentifiers(stored.ast, neededIdentifiers);
|
|
220
|
+
if (typeParams) {
|
|
221
|
+
edits.push({
|
|
222
|
+
start: typeParams.start,
|
|
223
|
+
end: typeParams.end,
|
|
224
|
+
text: stored.src,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else if (baseCall) {
|
|
228
|
+
edits.push({
|
|
229
|
+
start: baseCall.callee.end,
|
|
230
|
+
end: baseCall.callee.end,
|
|
231
|
+
text: stored.src,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// 4. Generate Import Statements
|
|
238
|
+
const importsToAdd = new Map(); // source -> { named: Map<string, {str, isType}>, default: {str, isType}, namespace: {str, isType} }
|
|
239
|
+
for (const id of neededIdentifiers) {
|
|
240
|
+
const importInfo = oldImports.get(id);
|
|
241
|
+
if (importInfo) {
|
|
242
|
+
// Check if already imported in new file
|
|
243
|
+
const existing = newExistingImports.get(importInfo.source);
|
|
244
|
+
if (existing && existing.has(id)) {
|
|
245
|
+
continue; // Already imported
|
|
246
|
+
}
|
|
247
|
+
if (!importsToAdd.has(importInfo.source)) {
|
|
248
|
+
importsToAdd.set(importInfo.source, {
|
|
249
|
+
named: new Map(),
|
|
250
|
+
default: null,
|
|
251
|
+
namespace: null,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const group = importsToAdd.get(importInfo.source);
|
|
255
|
+
const isType = importInfo.importKind === 'type';
|
|
256
|
+
if (importInfo.type === 'named') {
|
|
257
|
+
let importStr = id;
|
|
258
|
+
if (importInfo.importedName !== id) {
|
|
259
|
+
importStr = `${importInfo.importedName} as ${id}`;
|
|
260
|
+
}
|
|
261
|
+
group.named.set(importStr, { str: importStr, isType });
|
|
262
|
+
}
|
|
263
|
+
else if (importInfo.type === 'default') {
|
|
264
|
+
group.default = { str: id, isType };
|
|
265
|
+
}
|
|
266
|
+
else if (importInfo.type === 'namespace') {
|
|
267
|
+
group.namespace = { str: id, isType };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const importBlocks = [];
|
|
272
|
+
for (const [source, info] of importsToAdd) {
|
|
273
|
+
// Check if source exists in new file to merge?
|
|
274
|
+
// For simplicity, we append new import lines.
|
|
275
|
+
// But we can try to be smart.
|
|
276
|
+
// If we have named imports
|
|
277
|
+
if (info.named.size > 0) {
|
|
278
|
+
const namedImports = Array.from(info.named.values());
|
|
279
|
+
const allTypes = namedImports.every((x) => x.isType);
|
|
280
|
+
if (allTypes) {
|
|
281
|
+
const names = namedImports.map((x) => x.str).join(', ');
|
|
282
|
+
importBlocks.push(`import type { ${names} } from '${source}';`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const names = namedImports
|
|
286
|
+
.map((x) => (x.isType ? `type ${x.str}` : x.str))
|
|
287
|
+
.join(', ');
|
|
288
|
+
importBlocks.push(`import { ${names} } from '${source}';`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (info.default) {
|
|
292
|
+
if (info.default.isType) {
|
|
293
|
+
importBlocks.push(`import type ${info.default.str} from '${source}';`);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
importBlocks.push(`import ${info.default.str} from '${source}';`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (info.namespace) {
|
|
300
|
+
if (info.namespace.isType) {
|
|
301
|
+
importBlocks.push(`import type * as ${info.namespace.str} from '${source}';`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
importBlocks.push(`import * as ${info.namespace.str} from '${source}';`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// 5. Apply edits
|
|
309
|
+
edits.sort((a, b) => b.start - a.start);
|
|
310
|
+
let output = newFile;
|
|
311
|
+
for (const edit of edits) {
|
|
312
|
+
output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
|
|
313
|
+
}
|
|
314
|
+
// Prepend imports
|
|
315
|
+
if (importBlocks.length > 0) {
|
|
316
|
+
// Check for leading comments (e.g. // Docs: ...)
|
|
317
|
+
// We want to insert imports AFTER the leading comments but BEFORE the first code/import
|
|
318
|
+
// Simple heuristic: match consecutive lines at start that begin with //
|
|
319
|
+
const commentMatch = output.match(/^(\s*\/\/.*(\r?\n|$))+/);
|
|
320
|
+
if (commentMatch) {
|
|
321
|
+
const commentEnd = commentMatch[0].length;
|
|
322
|
+
output =
|
|
323
|
+
output.slice(0, commentEnd) +
|
|
324
|
+
'\n' +
|
|
325
|
+
importBlocks.join('\n') +
|
|
326
|
+
output.slice(commentEnd);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
output = importBlocks.join('\n') + '\n' + output;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return output;
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=mergeSchema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeSchema.js","sourceRoot":"","sources":["../../src/util/mergeSchema.js"],"names":[],"mappings":"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","sourcesContent":["import * as acorn from 'acorn';\nimport tsPlugin from 'acorn-typescript';\n\nconst node = acorn.Parser.extend(tsPlugin({ dts: false }));\n\n// --- Import Handling Helpers ---\n\nfunction collectImports(ast) {\n const imports = new Map(); // localName -> { source, importedName, type, importKind }\n\n for (const node of ast.body) {\n if (node.type === 'ImportDeclaration') {\n const source = node.source.value;\n const declImportKind = node.importKind;\n\n for (const specifier of node.specifiers) {\n let kind = 'value';\n if (declImportKind === 'type' || specifier.importKind === 'type') {\n kind = 'type';\n }\n\n if (specifier.type === 'ImportSpecifier') {\n imports.set(specifier.local.name, {\n source,\n importedName: specifier.imported.name,\n type: 'named',\n importKind: kind,\n });\n } else if (specifier.type === 'ImportDefaultSpecifier') {\n imports.set(specifier.local.name, {\n source,\n type: 'default',\n importKind: kind,\n });\n } else if (specifier.type === 'ImportNamespaceSpecifier') {\n imports.set(specifier.local.name, {\n source,\n type: 'namespace',\n importKind: kind,\n });\n }\n }\n }\n }\n return imports;\n}\n\nfunction collectExistingImports(ast) {\n const existing = new Map(); // source -> Set<localName>\n\n for (const node of ast.body) {\n if (node.type === 'ImportDeclaration') {\n const source = node.source.value;\n if (!existing.has(source)) existing.set(source, new Set());\n\n for (const specifier of node.specifiers) {\n existing.get(source).add(specifier.local.name);\n }\n }\n }\n return existing;\n}\n\nfunction findIdentifiers(node, set = new Set()) {\n if (!node) return set;\n\n // If it's a Type Reference, grab the name\n if (node.type === 'TSTypeReference') {\n if (node.typeName.type === 'Identifier') {\n set.add(node.typeName.name);\n } else if (node.typeName.type === 'TSQualifiedName') {\n // For A.B, we need A\n let left = node.typeName.left;\n while (left.type === 'TSQualifiedName') {\n left = left.left;\n }\n if (left.type === 'Identifier') {\n set.add(left.name);\n }\n }\n }\n\n // Recursive walk\n for (const key in node) {\n if (key === 'loc' || key === 'start' || key === 'end') continue;\n if (typeof node[key] === 'object' && node[key] !== null) {\n if (Array.isArray(node[key])) {\n node[key].forEach((child) => findIdentifiers(child, set));\n } else {\n findIdentifiers(node[key], set);\n }\n }\n }\n return set;\n}\n\n// --- Schema Traversal Helpers ---\n\nfunction getPropName(prop) {\n if (prop.key.type === 'Identifier') return prop.key.name;\n if (prop.key.type === 'Literal') return prop.key.value;\n return null;\n}\n\nfunction analyzeChain(node) {\n let curr = node;\n let typeParams = null;\n let baseCall = null;\n\n while (curr.type === 'CallExpression') {\n if (curr.typeParameters) {\n typeParams = curr.typeParameters;\n }\n\n if (\n curr.callee.type === 'MemberExpression' &&\n curr.callee.object.type === 'Identifier' &&\n curr.callee.object.name === 'i'\n ) {\n baseCall = curr;\n }\n\n if (curr.callee.type === 'MemberExpression') {\n curr = curr.callee.object;\n } else {\n break;\n }\n }\n return { typeParams, baseCall };\n}\n\nfunction traverseSchema(node, path, callback) {\n if (node.type === 'ObjectExpression') {\n for (const prop of node.properties) {\n const name = getPropName(prop);\n if (!name) continue;\n const newPath = path ? `${path}.${name}` : name;\n\n if (prop.value.type === 'ObjectExpression') {\n traverseSchema(prop.value, newPath, callback);\n } else if (prop.value.type === 'CallExpression') {\n let isEntity = false;\n if (\n prop.value.callee.type === 'MemberExpression' &&\n prop.value.callee.property.name === 'entity' &&\n prop.value.callee.object.type === 'Identifier' &&\n prop.value.callee.object.name === 'i'\n ) {\n isEntity = true;\n }\n\n if (isEntity) {\n callback(prop.value, newPath);\n if (prop.value.arguments.length > 0) {\n traverseSchema(prop.value.arguments[0], newPath, callback);\n }\n } else {\n callback(prop.value, newPath);\n }\n }\n }\n }\n}\n\nfunction findSchemaObject(ast) {\n let schemaObj = null;\n function walk(node) {\n if (!node) return;\n if (schemaObj) return;\n if (\n node.type === 'CallExpression' &&\n node.callee.type === 'MemberExpression' &&\n node.callee.object.name === 'i' &&\n node.callee.property.name === 'schema' &&\n node.arguments.length > 0\n ) {\n schemaObj = node.arguments[0];\n return;\n }\n for (const key in node) {\n if (key === 'loc' || key === 'start' || key === 'end') continue;\n if (node[key] && typeof node[key] === 'object') {\n if (Array.isArray(node[key])) {\n node[key].forEach(walk);\n } else {\n walk(node[key]);\n }\n }\n }\n }\n walk(ast);\n return schemaObj;\n}\n\nexport function mergeSchema(oldFile, newFile) {\n const oldParsed = node.parse(oldFile, {\n sourceType: 'module',\n ecmaVersion: 'latest',\n locations: true,\n });\n\n const newParsed = node.parse(newFile, {\n sourceType: 'module',\n ecmaVersion: 'latest',\n locations: true,\n });\n\n const schemaMap = new Map(); // Path -> { src, ast }\n\n // 1. Extract from old file\n const oldSchemaObj = findSchemaObject(oldParsed);\n if (oldSchemaObj) {\n traverseSchema(oldSchemaObj, '', (node, path) => {\n const { typeParams } = analyzeChain(node);\n if (typeParams) {\n const src = oldFile.slice(typeParams.start, typeParams.end);\n schemaMap.set(path, { src, ast: typeParams });\n }\n });\n }\n\n // 2. Collect Imports\n const oldImports = collectImports(oldParsed);\n const newExistingImports = collectExistingImports(newParsed);\n const neededIdentifiers = new Set();\n\n // 3. Apply to new file & Collect needed identifiers\n const edits = [];\n const newSchemaObj = findSchemaObject(newParsed);\n\n if (newSchemaObj) {\n traverseSchema(newSchemaObj, '', (node, path) => {\n const { typeParams, baseCall } = analyzeChain(node);\n const stored = schemaMap.get(path);\n\n if (stored) {\n // Collect identifiers from the type params we are about to inject\n findIdentifiers(stored.ast, neededIdentifiers);\n\n if (typeParams) {\n edits.push({\n start: typeParams.start,\n end: typeParams.end,\n text: stored.src,\n });\n } else if (baseCall) {\n edits.push({\n start: baseCall.callee.end,\n end: baseCall.callee.end,\n text: stored.src,\n });\n }\n }\n });\n }\n\n // 4. Generate Import Statements\n const importsToAdd = new Map(); // source -> { named: Map<string, {str, isType}>, default: {str, isType}, namespace: {str, isType} }\n\n for (const id of neededIdentifiers) {\n const importInfo = oldImports.get(id);\n if (importInfo) {\n // Check if already imported in new file\n const existing = newExistingImports.get(importInfo.source);\n if (existing && existing.has(id)) {\n continue; // Already imported\n }\n\n if (!importsToAdd.has(importInfo.source)) {\n importsToAdd.set(importInfo.source, {\n named: new Map(),\n default: null,\n namespace: null,\n });\n }\n const group = importsToAdd.get(importInfo.source);\n const isType = importInfo.importKind === 'type';\n\n if (importInfo.type === 'named') {\n let importStr = id;\n if (importInfo.importedName !== id) {\n importStr = `${importInfo.importedName} as ${id}`;\n }\n group.named.set(importStr, { str: importStr, isType });\n } else if (importInfo.type === 'default') {\n group.default = { str: id, isType };\n } else if (importInfo.type === 'namespace') {\n group.namespace = { str: id, isType };\n }\n }\n }\n\n const importBlocks = [];\n\n for (const [source, info] of importsToAdd) {\n // Check if source exists in new file to merge?\n // For simplicity, we append new import lines.\n // But we can try to be smart.\n\n // If we have named imports\n if (info.named.size > 0) {\n const namedImports = Array.from(info.named.values());\n const allTypes = namedImports.every((x) => x.isType);\n\n if (allTypes) {\n const names = namedImports.map((x) => x.str).join(', ');\n importBlocks.push(`import type { ${names} } from '${source}';`);\n } else {\n const names = namedImports\n .map((x) => (x.isType ? `type ${x.str}` : x.str))\n .join(', ');\n importBlocks.push(`import { ${names} } from '${source}';`);\n }\n }\n if (info.default) {\n if (info.default.isType) {\n importBlocks.push(`import type ${info.default.str} from '${source}';`);\n } else {\n importBlocks.push(`import ${info.default.str} from '${source}';`);\n }\n }\n if (info.namespace) {\n if (info.namespace.isType) {\n importBlocks.push(\n `import type * as ${info.namespace.str} from '${source}';`,\n );\n } else {\n importBlocks.push(\n `import * as ${info.namespace.str} from '${source}';`,\n );\n }\n }\n }\n\n // 5. Apply edits\n edits.sort((a, b) => b.start - a.start);\n\n let output = newFile;\n for (const edit of edits) {\n output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);\n }\n\n // Prepend imports\n if (importBlocks.length > 0) {\n // Check for leading comments (e.g. // Docs: ...)\n // We want to insert imports AFTER the leading comments but BEFORE the first code/import\n\n // Simple heuristic: match consecutive lines at start that begin with //\n const commentMatch = output.match(/^(\\s*\\/\\/.*(\\r?\\n|$))+/);\n\n if (commentMatch) {\n const commentEnd = commentMatch[0].length;\n output =\n output.slice(0, commentEnd) +\n '\\n' +\n importBlocks.join('\\n') +\n output.slice(commentEnd);\n } else {\n output = importBlocks.join('\\n') + '\\n' + output;\n }\n }\n\n return output;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instant-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.22.96
|
|
4
|
+
"version": "0.22.96",
|
|
5
5
|
"description": "Instant's CLI",
|
|
6
6
|
"homepage": "https://github.com/instantdb/instant/tree/main/client/packages/cli",
|
|
7
7
|
"repository": {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"instant-cli": "bin/index.js"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"acorn": "^8.15.0",
|
|
25
|
+
"acorn-typescript": "^1.4.13",
|
|
24
26
|
"ansi-escapes": "4.3.2",
|
|
25
27
|
"boxen": "^8.0.1",
|
|
26
28
|
"chalk": "^5.3.0",
|
|
@@ -39,9 +41,9 @@
|
|
|
39
41
|
"strip-ansi": "^7.1.2",
|
|
40
42
|
"terminal-link": "^3.0.0",
|
|
41
43
|
"unconfig": "^0.5.5",
|
|
42
|
-
"@instantdb/core": "0.22.96
|
|
43
|
-
"@instantdb/
|
|
44
|
-
"@instantdb/
|
|
44
|
+
"@instantdb/core": "0.22.96",
|
|
45
|
+
"@instantdb/platform": "0.22.96",
|
|
46
|
+
"@instantdb/version": "0.22.96"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@babel/core": "^7.17.9",
|
package/src/index.js
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
getSchemaPathToWrite,
|
|
51
51
|
getPermsPathToWrite,
|
|
52
52
|
} from './util/findConfigCandidates.js';
|
|
53
|
+
import { mergeSchema } from './util/mergeSchema.js';
|
|
53
54
|
|
|
54
55
|
const execAsync = promisify(exec);
|
|
55
56
|
|
|
@@ -478,6 +479,10 @@ program
|
|
|
478
479
|
'-p --package <react|react-native|core|admin>',
|
|
479
480
|
'Which package to automatically install if there is not one installed already.',
|
|
480
481
|
)
|
|
482
|
+
.option(
|
|
483
|
+
'--experimental-type-preservation',
|
|
484
|
+
"[Experimental] Preserve manual type changes like `status: i.json<'online' | 'offline'>()` when doing `instant-cli pull schema`",
|
|
485
|
+
)
|
|
481
486
|
.description('Pull schema and perm files from production.')
|
|
482
487
|
.addHelpText(
|
|
483
488
|
'after',
|
|
@@ -655,7 +660,7 @@ async function handlePull(bag, opts) {
|
|
|
655
660
|
);
|
|
656
661
|
return;
|
|
657
662
|
}
|
|
658
|
-
await pull(bag, appId, pkgAndAuthInfo);
|
|
663
|
+
await pull(bag, appId, { ...pkgAndAuthInfo, ...opts });
|
|
659
664
|
}
|
|
660
665
|
|
|
661
666
|
async function push(bag, appId, opts) {
|
|
@@ -1189,7 +1194,10 @@ async function getOrPromptPackageAndAuthInfoWithErrorLogging(opts) {
|
|
|
1189
1194
|
return { pkgDir, projectType, instantModuleName, authToken };
|
|
1190
1195
|
}
|
|
1191
1196
|
|
|
1192
|
-
async function pullSchema(
|
|
1197
|
+
async function pullSchema(
|
|
1198
|
+
appId,
|
|
1199
|
+
{ pkgDir, instantModuleName, experimentalTypePreservation },
|
|
1200
|
+
) {
|
|
1193
1201
|
console.log('Pulling schema...');
|
|
1194
1202
|
|
|
1195
1203
|
const pullRes = await fetchJson({
|
|
@@ -1228,16 +1236,26 @@ async function pullSchema(appId, { pkgDir, instantModuleName }) {
|
|
|
1228
1236
|
const shortSchemaPath = getSchemaPathToWrite(prev?.path);
|
|
1229
1237
|
const schemaPath = join(pkgDir, shortSchemaPath);
|
|
1230
1238
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
apiSchemaToInstantSchemaDef(pullRes.data.schema),
|
|
1236
|
-
instantModuleName,
|
|
1237
|
-
),
|
|
1238
|
-
'utf-8',
|
|
1239
|
+
let newSchemaContent = generateSchemaTypescriptFile(
|
|
1240
|
+
prev?.schema,
|
|
1241
|
+
apiSchemaToInstantSchemaDef(pullRes.data.schema),
|
|
1242
|
+
instantModuleName,
|
|
1239
1243
|
);
|
|
1240
1244
|
|
|
1245
|
+
if (prev && experimentalTypePreservation) {
|
|
1246
|
+
try {
|
|
1247
|
+
const oldSchemaContent = await readFile(prev.path, 'utf-8');
|
|
1248
|
+
newSchemaContent = mergeSchema(oldSchemaContent, newSchemaContent);
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
warn(
|
|
1251
|
+
'Failed to merge schema with existing file. Overwriting instead.',
|
|
1252
|
+
e,
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
await writeTypescript(schemaPath, newSchemaContent, 'utf-8');
|
|
1258
|
+
|
|
1241
1259
|
console.log('✅ Wrote schema to ' + shortSchemaPath);
|
|
1242
1260
|
|
|
1243
1261
|
return { ok: true };
|