instant-cli 0.22.96-experimental.surgical.20765817844.1 → 0.22.97-experimental.add-user-perm-rules.20788245875.1
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 +6 -9
- 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 +4 -4
- package/src/index.js +10 -19
- package/src/util/mergeSchema.js +364 -0
- package/__tests__/__snapshots__/updateSchemaFile.test.ts.snap +0 -248
- package/__tests__/updateSchemaFile.test.ts +0 -557
- package/dist/util/updateSchemaFile.d.ts +0 -3
- package/dist/util/updateSchemaFile.d.ts.map +0 -1
- package/dist/util/updateSchemaFile.js +0 -610
- package/dist/util/updateSchemaFile.js.map +0 -1
- package/src/util/updateSchemaFile.ts +0 -760
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
> instant-cli@0.22.
|
|
2
|
+
> instant-cli@0.22.97-experimental.add-user-perm-rules.20788245875.1 build /home/runner/work/instant/instant/client/packages/cli
|
|
3
3
|
> rm -rf dist; tsc -p tsconfig.json
|
|
4
4
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { mergeSchema } from '../src/util/mergeSchema';
|
|
3
|
+
|
|
4
|
+
test('preserves type annotations', () => {
|
|
5
|
+
const oldFile = `
|
|
6
|
+
import { i } from '@instantdb/core';
|
|
7
|
+
import { Label } from './types';
|
|
8
|
+
|
|
9
|
+
const _schema = i.schema({
|
|
10
|
+
entities: {
|
|
11
|
+
$users: i.entity({
|
|
12
|
+
email: i.string().unique().indexed(),
|
|
13
|
+
}),
|
|
14
|
+
todos: i.entity({
|
|
15
|
+
title: i.string(),
|
|
16
|
+
status: i.string<'todo' | 'in_progress' | 'done'>(),
|
|
17
|
+
priority: i.number<1 | 2 | 3>(),
|
|
18
|
+
labels: i.json<Label[]>().optional(),
|
|
19
|
+
}),
|
|
20
|
+
projects: i.entity({
|
|
21
|
+
name: i.string(),
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
links: {
|
|
25
|
+
todoProject: {
|
|
26
|
+
forward: { on: 'todos', has: 'one', label: 'project' },
|
|
27
|
+
reverse: { on: 'projects', has: 'many', label: 'todos' },
|
|
28
|
+
},
|
|
29
|
+
projectOwner: {
|
|
30
|
+
forward: { on: 'projects', has: 'one', label: 'owner' },
|
|
31
|
+
reverse: { on: '$users', has: 'many', label: 'projects' },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
rooms: {
|
|
35
|
+
projectRoom: {
|
|
36
|
+
presence: i.entity({
|
|
37
|
+
cursor: i.json<{ x: number; y: number }>(),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export default _schema;
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const newFile = `
|
|
47
|
+
import { i } from '@instantdb/core';
|
|
48
|
+
|
|
49
|
+
const _schema = i.schema({
|
|
50
|
+
entities: {
|
|
51
|
+
$users: i.entity({
|
|
52
|
+
email: i.string().unique().indexed(),
|
|
53
|
+
}),
|
|
54
|
+
todos: i.entity({
|
|
55
|
+
title: i.string(),
|
|
56
|
+
status: i.string(),
|
|
57
|
+
priority: i.number(),
|
|
58
|
+
labels: i.json().optional(),
|
|
59
|
+
}),
|
|
60
|
+
projects: i.entity({
|
|
61
|
+
name: i.string(),
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
links: {
|
|
65
|
+
todoProject: {
|
|
66
|
+
forward: { on: 'todos', has: 'one', label: 'project' },
|
|
67
|
+
reverse: { on: 'projects', has: 'many', label: 'todos' },
|
|
68
|
+
},
|
|
69
|
+
projectOwner: {
|
|
70
|
+
forward: { on: 'projects', has: 'one', label: 'owner' },
|
|
71
|
+
reverse: { on: '$users', has: 'many', label: 'projects' },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
rooms: {
|
|
75
|
+
projectRoom: {
|
|
76
|
+
presence: i.entity({
|
|
77
|
+
cursor: i.json(),
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export default _schema;
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const result = mergeSchema(oldFile, newFile);
|
|
87
|
+
|
|
88
|
+
// Type annotations preserved
|
|
89
|
+
expect(result).toContain("i.string<'todo' | 'in_progress' | 'done'>()");
|
|
90
|
+
expect(result).toContain('i.number<1 | 2 | 3>()');
|
|
91
|
+
expect(result).toContain('i.json<Label[]>()');
|
|
92
|
+
expect(result).toContain('i.json<{ x: number; y: number }>()');
|
|
93
|
+
|
|
94
|
+
// Import preserved
|
|
95
|
+
expect(result).toContain("import { Label } from './types';");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('preserves different import styles', () => {
|
|
99
|
+
const oldFile = `
|
|
100
|
+
import { i } from '@instantdb/core';
|
|
101
|
+
import { Label } from './types';
|
|
102
|
+
import { Tag as MyTag } from './types';
|
|
103
|
+
import Priority from './Priority';
|
|
104
|
+
import * as Models from './models';
|
|
105
|
+
import type { Meta } from './meta';
|
|
106
|
+
|
|
107
|
+
const _schema = i.schema({
|
|
108
|
+
entities: {
|
|
109
|
+
a: i.entity({ f: i.json<Label>() }),
|
|
110
|
+
b: i.entity({ f: i.json<MyTag>() }),
|
|
111
|
+
c: i.entity({ f: i.json<Priority>() }),
|
|
112
|
+
d: i.entity({ f: i.json<Models.Status>() }),
|
|
113
|
+
e: i.entity({ f: i.json<Meta>() }),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const newFile = `
|
|
119
|
+
import { i } from '@instantdb/core';
|
|
120
|
+
|
|
121
|
+
const _schema = i.schema({
|
|
122
|
+
entities: {
|
|
123
|
+
a: i.entity({ f: i.json() }),
|
|
124
|
+
b: i.entity({ f: i.json() }),
|
|
125
|
+
c: i.entity({ f: i.json() }),
|
|
126
|
+
d: i.entity({ f: i.json() }),
|
|
127
|
+
e: i.entity({ f: i.json() }),
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
const result = mergeSchema(oldFile, newFile);
|
|
133
|
+
|
|
134
|
+
// All import styles preserved (named imports from same module are combined)
|
|
135
|
+
expect(result).toContain('Label');
|
|
136
|
+
expect(result).toContain('Tag as MyTag');
|
|
137
|
+
expect(result).toContain("import Priority from './Priority';");
|
|
138
|
+
expect(result).toContain("import * as Models from './models';");
|
|
139
|
+
expect(result).toContain("import type { Meta } from './meta';");
|
|
140
|
+
|
|
141
|
+
// Type annotations preserved
|
|
142
|
+
expect(result).toContain('i.json<Label>()');
|
|
143
|
+
expect(result).toContain('i.json<MyTag>()');
|
|
144
|
+
expect(result).toContain('i.json<Priority>()');
|
|
145
|
+
expect(result).toContain('i.json<Models.Status>()');
|
|
146
|
+
expect(result).toContain('i.json<Meta>()');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('handles entity additions and removals', () => {
|
|
150
|
+
const oldFile = `
|
|
151
|
+
import { i } from '@instantdb/core';
|
|
152
|
+
import { Label } from './types';
|
|
153
|
+
|
|
154
|
+
const _schema = i.schema({
|
|
155
|
+
entities: {
|
|
156
|
+
todos: i.entity({
|
|
157
|
+
labels: i.json<Label[]>(),
|
|
158
|
+
}),
|
|
159
|
+
oldEntity: i.entity({
|
|
160
|
+
data: i.json<{ removed: true }>(),
|
|
161
|
+
}),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const newFile = `
|
|
167
|
+
import { i } from '@instantdb/core';
|
|
168
|
+
import { Label } from './types';
|
|
169
|
+
|
|
170
|
+
const _schema = i.schema({
|
|
171
|
+
entities: {
|
|
172
|
+
todos: i.entity({
|
|
173
|
+
labels: i.json(),
|
|
174
|
+
}),
|
|
175
|
+
newEntity: i.entity({
|
|
176
|
+
data: i.json(),
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
`;
|
|
181
|
+
|
|
182
|
+
const result = mergeSchema(oldFile, newFile);
|
|
183
|
+
|
|
184
|
+
// Existing entity type preserved
|
|
185
|
+
expect(result).toContain('i.json<Label[]>()');
|
|
186
|
+
|
|
187
|
+
// Removed entity is gone
|
|
188
|
+
expect(result).not.toContain('oldEntity');
|
|
189
|
+
|
|
190
|
+
// New entity has no type annotation
|
|
191
|
+
expect(result).toContain('newEntity: i.entity({');
|
|
192
|
+
expect(result).not.toContain('i.json<{ removed: true }>()');
|
|
193
|
+
|
|
194
|
+
// Import not duplicated (was already in newFile)
|
|
195
|
+
const importMatches = result.match(/import { Label } from '\.\/types';/g);
|
|
196
|
+
expect(importMatches?.length).toBe(1);
|
|
197
|
+
});
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAu9DA,8EAQC;AA+ED;;;;;EAKE"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
// @ts-check
|
|
11
11
|
import { generatePermsTypescriptFile, apiSchemaToInstantSchemaDef, generateSchemaTypescriptFile, diffSchemas, convertTxSteps, validateSchema, SchemaValidationError, PlatformApi, } from '@instantdb/platform';
|
|
12
12
|
import version from './version.js';
|
|
13
|
+
import { existsSync } from 'fs';
|
|
13
14
|
import { mkdir, writeFile, readFile, unlink } from 'fs/promises';
|
|
14
15
|
import path, { join } from 'path';
|
|
15
16
|
import { randomUUID } from 'crypto';
|
|
@@ -37,7 +38,7 @@ import { buildAutoRenameSelector } from './rename.js';
|
|
|
37
38
|
import { loadEnv } from './util/loadEnv.js';
|
|
38
39
|
import { isHeadlessEnvironment } from './util/isHeadlessEnvironment.js';
|
|
39
40
|
import { getSchemaReadCandidates, getPermsReadCandidates, getSchemaPathToWrite, getPermsPathToWrite, } from './util/findConfigCandidates.js';
|
|
40
|
-
import {
|
|
41
|
+
import { mergeSchema } from './util/mergeSchema.js';
|
|
41
42
|
const execAsync = promisify(exec);
|
|
42
43
|
loadEnv();
|
|
43
44
|
const dev = Boolean(process.env.INSTANT_CLI_DEV);
|
|
@@ -353,7 +354,7 @@ program
|
|
|
353
354
|
.argument('[schema|perms|all]', 'Which configuration to push. Defaults to `all`')
|
|
354
355
|
.option('-a --app <app-id>', 'App ID to push to. Defaults to *_INSTANT_APP_ID in .env')
|
|
355
356
|
.option('-p --package <react|react-native|core|admin>', 'Which package to automatically install if there is not one installed already.')
|
|
356
|
-
.option('--experimental-type-preservation',
|
|
357
|
+
.option('--experimental-type-preservation', "[Experimental] Preserve manual type changes like `status: i.json<'online' | 'offline'>()` when doing `instant-cli pull schema`")
|
|
357
358
|
.description('Pull schema and perm files from production.')
|
|
358
359
|
.addHelpText('after', `
|
|
359
360
|
Environment Variables:
|
|
@@ -1023,20 +1024,16 @@ function pullSchema(appId_1, _a) {
|
|
|
1023
1024
|
}
|
|
1024
1025
|
const shortSchemaPath = getSchemaPathToWrite(prev === null || prev === void 0 ? void 0 : prev.path);
|
|
1025
1026
|
const schemaPath = join(pkgDir, shortSchemaPath);
|
|
1026
|
-
|
|
1027
|
-
let newSchemaContent;
|
|
1027
|
+
let newSchemaContent = generateSchemaTypescriptFile(prev === null || prev === void 0 ? void 0 : prev.schema, apiSchemaToInstantSchemaDef(pullRes.data.schema), instantModuleName);
|
|
1028
1028
|
if (prev && experimentalTypePreservation) {
|
|
1029
1029
|
try {
|
|
1030
1030
|
const oldSchemaContent = yield readFile(prev.path, 'utf-8');
|
|
1031
|
-
newSchemaContent =
|
|
1031
|
+
newSchemaContent = mergeSchema(oldSchemaContent, newSchemaContent);
|
|
1032
1032
|
}
|
|
1033
1033
|
catch (e) {
|
|
1034
|
-
warn('Failed to
|
|
1034
|
+
warn('Failed to merge schema with existing file. Overwriting instead.', e);
|
|
1035
1035
|
}
|
|
1036
1036
|
}
|
|
1037
|
-
if (!newSchemaContent) {
|
|
1038
|
-
newSchemaContent = generateSchemaTypescriptFile(prev === null || prev === void 0 ? void 0 : prev.schema, serverSchema, instantModuleName);
|
|
1039
|
-
}
|
|
1040
1037
|
yield writeTypescript(schemaPath, newSchemaContent, 'utf-8');
|
|
1041
1038
|
console.log('✅ Wrote schema to ' + shortSchemaPath);
|
|
1042
1039
|
return { ok: true };
|