@stoker-platform/cli 0.5.82 → 0.5.86

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/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/cli",
3
- "version": "0.5.81",
3
+ "version": "0.5.85",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./lib/src/main.js",
@@ -24,9 +24,9 @@
24
24
  "@google-cloud/secret-manager": "^6.1.1",
25
25
  "@google-cloud/storage": "^7.19.0",
26
26
  "@inquirer/prompts": "^8.4.2",
27
- "@stoker-platform/node-client": "0.5.58",
28
- "@stoker-platform/types": "0.5.37",
29
- "@stoker-platform/utils": "0.5.49",
27
+ "@stoker-platform/node-client": "0.5.59",
28
+ "@stoker-platform/types": "0.5.38",
29
+ "@stoker-platform/utils": "0.5.50",
30
30
  "algoliasearch": "^5.51.0",
31
31
  "commander": "^14.0.0",
32
32
  "cross-spawn": "^7.0.6",
@@ -25,6 +25,7 @@ export const deployProject = async (options) => {
25
25
  throw new Error("The schema for this project has changed. Maintenance mode cannot be disabled.");
26
26
  }
27
27
  }
28
+ await runChildProcess("npx", ["stoker", "generate-types"]);
28
29
  await runChildProcess("npm", ["run", "format"]);
29
30
  await runChildProcess("npm", ["run", "lint"]);
30
31
  await runChildProcess("npm", ["run", "build"]);
@@ -1,184 +1,216 @@
1
1
  import { tryPromise } from "@stoker-platform/node-client";
2
2
  import { getRelationLists, roleHasOperationAccess } from "@stoker-platform/utils";
3
3
  import { ServerValue } from "firebase-admin/database";
4
- import { readdir, readFile } from "fs/promises";
4
+ import { spawnSync } from "child_process";
5
+ import { existsSync } from "fs";
6
+ import { readdir, readFile, rm } from "fs/promises";
5
7
  import { join } from "path";
6
8
  import { pathToFileURL } from "url";
7
- export const generateSchema = async (includeComputedFields = false) => {
9
+ const ensureSchemaBuildOutput = (isTypeGen) => {
10
+ if (!isTypeGen)
11
+ return;
12
+ const mainPath = join(process.cwd(), "lib", "main.js");
13
+ const collectionsPath = join(process.cwd(), "lib", "collections");
14
+ if (existsSync(mainPath) && existsSync(collectionsPath))
15
+ return;
16
+ const buildResult = spawnSync("npx", ["tsc", "--pretty", "false", "--noEmitOnError", "false"], {
17
+ cwd: process.cwd(),
18
+ stdio: "ignore",
19
+ });
20
+ if (buildResult.error) {
21
+ throw new Error("Failed to run TypeScript compile step for schema generation", { cause: buildResult.error });
22
+ }
23
+ if (!existsSync(mainPath) || !existsSync(collectionsPath)) {
24
+ throw new Error("No lib output found. Run 'npm run build' to generate lib artifacts.");
25
+ }
26
+ return true;
27
+ };
28
+ export const generateSchema = async (includeComputedFields = false, isTypeGen = false) => {
8
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
30
  const newSchema = {};
10
- const path = join(process.cwd(), "lib", "main.js");
11
- const url = pathToFileURL(path).href;
12
- const globalConfigFile = await import(url);
13
- const globalConfig = globalConfigFile.default({ sdk: "node" });
14
- const projectData = await readFile(join(process.cwd(), "project-data.json"), "utf8");
15
- const projectDataJson = JSON.parse(projectData);
16
- const version = projectDataJson.version || 1;
17
- newSchema.version = version;
18
- newSchema.published_time = ServerValue.TIMESTAMP;
19
- const collections = await readdir(join(process.cwd(), "lib", "collections"));
20
- const { roles, firebase } = globalConfig;
21
- const configSchema = {
22
- roles,
23
- permissionsIndexExemption: false,
24
- };
25
- if (firebase?.permissionsIndexExemption)
26
- configSchema.permissionsIndexExemption = true;
27
- firebase?.writeLogIndexExemption
28
- ? (configSchema.writeLogIndexExemption = firebase?.writeLogIndexExemption || [])
29
- : null;
30
- firebase?.writeLogTTL ? (configSchema.writeLogTTL = firebase?.writeLogTTL) : null;
31
- newSchema.config = configSchema;
32
- const fullSchema = [];
33
- for (const collection of collections) {
34
- if (globalConfig.disabledCollections?.includes(collection))
35
- continue;
36
- const path = join(process.cwd(), "lib", "collections", collection);
31
+ const tempSchemaGenerated = ensureSchemaBuildOutput(isTypeGen);
32
+ try {
33
+ const path = join(process.cwd(), "lib", "main.js");
37
34
  const url = pathToFileURL(path).href;
38
- const schema = await import(url);
39
- fullSchema.push(schema.default({ sdk: "node" }));
40
- }
41
- newSchema.collections = {};
42
- for (const collection of collections) {
43
- if (globalConfig.disabledCollections?.includes(collection))
44
- continue;
45
- const path = join(process.cwd(), "lib", "collections", collection);
46
- const url = pathToFileURL(path).href;
47
- const schema = await import(url);
48
- const persistSchema = schema.default({ sdk: "node" });
49
- const { labels, access, preloadCache, admin } = persistSchema;
50
- const { serverReadOnly } = access;
51
- if (!includeComputedFields) {
52
- persistSchema.fields = persistSchema.fields.filter((field) => field.type !== "Computed");
35
+ const globalConfigFile = await import(url);
36
+ const globalConfig = globalConfigFile.default({ sdk: "node" });
37
+ const projectData = await readFile(join(process.cwd(), "project-data.json"), "utf8");
38
+ const projectDataJson = JSON.parse(projectData);
39
+ const version = projectDataJson.version || 1;
40
+ newSchema.version = version;
41
+ newSchema.published_time = ServerValue.TIMESTAMP;
42
+ const collections = await readdir(join(process.cwd(), "lib", "collections"));
43
+ const { roles, firebase } = globalConfig;
44
+ const configSchema = {
45
+ roles,
46
+ permissionsIndexExemption: false,
47
+ };
48
+ if (firebase?.permissionsIndexExemption)
49
+ configSchema.permissionsIndexExemption = true;
50
+ firebase?.writeLogIndexExemption
51
+ ? (configSchema.writeLogIndexExemption = firebase?.writeLogIndexExemption || [])
52
+ : null;
53
+ firebase?.writeLogTTL ? (configSchema.writeLogTTL = firebase?.writeLogTTL) : null;
54
+ newSchema.config = configSchema;
55
+ const fullSchema = [];
56
+ for (const collection of collections) {
57
+ if (globalConfig.disabledCollections?.includes(collection))
58
+ continue;
59
+ const path = join(process.cwd(), "lib", "collections", collection);
60
+ const url = pathToFileURL(path).href;
61
+ const schema = await import(url);
62
+ fullSchema.push(schema.default({ sdk: "node" }));
53
63
  }
54
- else {
55
- for (const field of persistSchema.fields) {
56
- if (field.type === "Computed") {
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- delete field.formula;
59
- }
64
+ newSchema.collections = {};
65
+ for (const collection of collections) {
66
+ if (globalConfig.disabledCollections?.includes(collection))
67
+ continue;
68
+ const path = join(process.cwd(), "lib", "collections", collection);
69
+ const url = pathToFileURL(path).href;
70
+ const schema = await import(url);
71
+ const persistSchema = schema.default({ sdk: "node" });
72
+ const { labels, access, preloadCache, admin } = persistSchema;
73
+ const { serverReadOnly } = access;
74
+ if (!includeComputedFields) {
75
+ persistSchema.fields = persistSchema.fields.filter((field) => field.type !== "Computed");
60
76
  }
61
- }
62
- if (admin) {
63
- const filters = ((await tryPromise(admin.filters)) || []);
64
- const cards = (await tryPromise(admin.cards));
65
- const calendar = (await tryPromise(admin.calendar));
66
- const statusField = (await tryPromise(admin.statusField));
67
- const readRoles = roles.filter((role) => roleHasOperationAccess(persistSchema, role, "read"));
68
- const nonStandardRoles = (serverReadOnly || []).concat(preloadCache?.roles || []);
69
- const standardRoles = readRoles.filter((role) => !nonStandardRoles.includes(role));
70
- const noPreloadCacheRoles = readRoles.filter((role) => !preloadCache?.roles.includes(role));
71
- const addUnscheduledRoles = calendar?.unscheduled?.roles && !noPreloadCacheRoles.length;
72
- if (calendar?.unscheduled) {
73
- filters.push({
74
- type: "select",
75
- field: calendar.startField,
76
- roles: calendar?.unscheduled.roles,
77
- });
77
+ else {
78
+ for (const field of persistSchema.fields) {
79
+ if (field.type === "Computed") {
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ delete field.formula;
82
+ }
83
+ }
78
84
  }
79
- const relationListFields = getRelationLists(labels.collection, fullSchema);
80
- if (relationListFields.size > 0) {
81
- for (const relationListField of relationListFields.values()) {
82
- const existingRelationFilter = filters.find((filter) => filter.type === "relation" && filter.field === relationListField.field);
83
- if (existingRelationFilter && existingRelationFilter.type === "relation") {
84
- if (existingRelationFilter.roles) {
85
- const newRoles = new Set(existingRelationFilter.roles);
86
- for (const role of relationListField.roles || noPreloadCacheRoles) {
87
- if (noPreloadCacheRoles.includes(role)) {
88
- newRoles.add(role);
85
+ if (admin) {
86
+ const filters = ((await tryPromise(admin.filters)) || []);
87
+ const cards = (await tryPromise(admin.cards));
88
+ const calendar = (await tryPromise(admin.calendar));
89
+ const statusField = (await tryPromise(admin.statusField));
90
+ const readRoles = roles.filter((role) => roleHasOperationAccess(persistSchema, role, "read"));
91
+ const nonStandardRoles = (serverReadOnly || []).concat(preloadCache?.roles || []);
92
+ const standardRoles = readRoles.filter((role) => !nonStandardRoles.includes(role));
93
+ const noPreloadCacheRoles = readRoles.filter((role) => !preloadCache?.roles.includes(role));
94
+ const addUnscheduledRoles = calendar?.unscheduled?.roles && !noPreloadCacheRoles.length;
95
+ if (calendar?.unscheduled) {
96
+ filters.push({
97
+ type: "select",
98
+ field: calendar.startField,
99
+ roles: calendar?.unscheduled.roles,
100
+ });
101
+ }
102
+ const relationListFields = getRelationLists(labels.collection, fullSchema);
103
+ if (relationListFields.size > 0) {
104
+ for (const relationListField of relationListFields.values()) {
105
+ const existingRelationFilter = filters.find((filter) => filter.type === "relation" && filter.field === relationListField.field);
106
+ if (existingRelationFilter && existingRelationFilter.type === "relation") {
107
+ if (existingRelationFilter.roles) {
108
+ const newRoles = new Set(existingRelationFilter.roles);
109
+ for (const role of relationListField.roles || noPreloadCacheRoles) {
110
+ if (noPreloadCacheRoles.includes(role)) {
111
+ newRoles.add(role);
112
+ }
89
113
  }
114
+ existingRelationFilter.roles = Array.from(newRoles);
90
115
  }
91
- existingRelationFilter.roles = Array.from(newRoles);
116
+ }
117
+ else {
118
+ filters.push({
119
+ type: "relation",
120
+ field: relationListField.field,
121
+ roles: relationListField.roles?.filter((role) => noPreloadCacheRoles.includes(role)) ||
122
+ noPreloadCacheRoles,
123
+ });
92
124
  }
93
125
  }
94
- else {
95
- filters.push({
96
- type: "relation",
97
- field: relationListField.field,
98
- roles: relationListField.roles?.filter((role) => noPreloadCacheRoles.includes(role)) ||
99
- noPreloadCacheRoles,
100
- });
101
- }
102
- }
103
- }
104
- if (statusField || cards?.statusField) {
105
- const existingStatusFieldIndex = filters.findIndex((filter) => "field" in filter && filter.field === (statusField?.field || cards?.statusField));
106
- const newFilter = {
107
- type: "status",
108
- };
109
- if (addUnscheduledRoles) {
110
- newFilter.roles = calendar?.unscheduled?.roles;
111
126
  }
112
- filters.push(newFilter);
113
- if (existingStatusFieldIndex !== -1) {
114
- filters.splice(existingStatusFieldIndex, 1);
115
- }
116
- }
117
- if (calendar) {
118
- const existingRangeIndex = filters.findIndex((filter) => filter.type === "range");
119
- if (existingRangeIndex === -1) {
120
- if (noPreloadCacheRoles.length) {
121
- filters.push({
122
- type: "range",
123
- field: calendar.startField,
124
- });
127
+ if (statusField || cards?.statusField) {
128
+ const existingStatusFieldIndex = filters.findIndex((filter) => "field" in filter && filter.field === (statusField?.field || cards?.statusField));
129
+ const newFilter = {
130
+ type: "status",
131
+ };
132
+ if (addUnscheduledRoles) {
133
+ newFilter.roles = calendar?.unscheduled?.roles;
125
134
  }
126
- }
127
- else {
128
- if (noPreloadCacheRoles.length === 0) {
129
- filters.splice(existingRangeIndex, 1);
135
+ filters.push(newFilter);
136
+ if (existingStatusFieldIndex !== -1) {
137
+ filters.splice(existingStatusFieldIndex, 1);
130
138
  }
131
139
  }
132
- }
133
- if ((cards || admin.images) && standardRoles.length) {
134
- filters.push({
135
- type: "select",
136
- field: "Last_Save_At",
137
- roles: standardRoles,
138
- });
139
- }
140
- const persistFilters = [];
141
- filters.forEach((filter) => {
142
- const persistFilter = {};
143
- if (filter.type === "status") {
144
- if (statusField || cards?.statusField) {
145
- const field = statusField?.field || cards?.statusField;
146
- if (field) {
147
- persistFilter.field = field;
140
+ if (calendar) {
141
+ const existingRangeIndex = filters.findIndex((filter) => filter.type === "range");
142
+ if (existingRangeIndex === -1) {
143
+ if (noPreloadCacheRoles.length) {
144
+ filters.push({
145
+ type: "range",
146
+ field: calendar.startField,
147
+ });
148
+ }
149
+ }
150
+ else {
151
+ if (noPreloadCacheRoles.length === 0) {
152
+ filters.splice(existingRangeIndex, 1);
148
153
  }
149
154
  }
150
155
  }
151
- else {
152
- persistFilter.field = filter.field;
153
- }
154
- if ("roles" in filter && filter.roles?.length) {
155
- persistFilter.roles = filter.roles;
156
+ if ((cards || admin.images) && standardRoles.length) {
157
+ filters.push({
158
+ type: "select",
159
+ field: "Last_Save_At",
160
+ roles: standardRoles,
161
+ });
156
162
  }
157
- if (addUnscheduledRoles) {
158
- if (persistFilter.roles?.length) {
159
- persistFilter.roles = persistFilter.roles?.filter((role) => calendar.unscheduled?.roles?.includes(role));
163
+ const persistFilters = [];
164
+ filters.forEach((filter) => {
165
+ const persistFilter = {};
166
+ if (filter.type === "status") {
167
+ if (statusField || cards?.statusField) {
168
+ const field = statusField?.field || cards?.statusField;
169
+ if (field) {
170
+ persistFilter.field = field;
171
+ }
172
+ }
160
173
  }
161
174
  else {
162
- persistFilter.roles = calendar.unscheduled?.roles;
175
+ persistFilter.field = filter.field;
163
176
  }
164
- }
165
- if (filter.type === "range") {
166
- persistFilter.range = true;
167
- }
168
- if (filter.type !== "status" && filter.field === "Last_Save_At" && filter.type === "select") {
169
- persistFilter.standalone = true;
170
- }
171
- persistFilters.push(persistFilter);
172
- });
173
- persistSchema.queries = persistFilters;
177
+ if ("roles" in filter && filter.roles?.length) {
178
+ persistFilter.roles = filter.roles;
179
+ }
180
+ if (addUnscheduledRoles) {
181
+ if (persistFilter.roles?.length) {
182
+ persistFilter.roles = persistFilter.roles?.filter((role) => calendar.unscheduled?.roles?.includes(role));
183
+ }
184
+ else {
185
+ persistFilter.roles = calendar.unscheduled?.roles;
186
+ }
187
+ }
188
+ if (filter.type === "range") {
189
+ persistFilter.range = true;
190
+ }
191
+ if (filter.type !== "status" && filter.field === "Last_Save_At" && filter.type === "select") {
192
+ persistFilter.standalone = true;
193
+ }
194
+ persistFilters.push(persistFilter);
195
+ });
196
+ persistSchema.queries = persistFilters;
197
+ }
198
+ delete persistSchema.custom;
199
+ delete persistSchema.admin;
200
+ for (const field of persistSchema.fields) {
201
+ delete field.custom;
202
+ delete field.admin;
203
+ }
204
+ newSchema.collections[collection.split(".")[0]] = persistSchema;
174
205
  }
175
- delete persistSchema.custom;
176
- delete persistSchema.admin;
177
- for (const field of persistSchema.fields) {
178
- delete field.custom;
179
- delete field.admin;
206
+ return newSchema;
207
+ }
208
+ finally {
209
+ if (tempSchemaGenerated) {
210
+ const libPath = join(process.cwd(), "lib");
211
+ if (existsSync(libPath)) {
212
+ await rm(libPath, { recursive: true, force: true });
213
+ }
180
214
  }
181
- newSchema.collections[collection.split(".")[0]] = persistSchema;
182
215
  }
183
- return newSchema;
184
216
  };
@@ -126,25 +126,22 @@ export const lintSchema = async (noLog = false) => {
126
126
  }
127
127
  }
128
128
  for (const [collectionName, collectionSchema] of collectionSchemas) {
129
- const { auth, fields, access, ttl, parentCollection, recordTitleField, softDelete, roleSystemFields, preloadCache, relationLists, fullTextSearch, ai, } = collectionSchema;
129
+ const { labels, auth, fields, access, ttl, parentCollection, recordTitleField, softDelete, roleSystemFields, preloadCache, relationLists, fullTextSearch, ai, } = collectionSchema;
130
130
  const { auth: authAccess, operations, attributeRestrictions, entityRestrictions, permissionWriteRestrictions, serverReadOnly, serverWriteOnly, files, } = access;
131
131
  // eslint-disable-next-line security/detect-object-injection
132
132
  const customization = customizationModules[collectionName];
133
133
  const readRoles = roles.filter((role) => roleHasOperationAccess(collectionSchema, role, "read"));
134
134
  const fieldNames = fields.map((field) => field.name);
135
- const regex = /^(?!\/)(?!.*\/)(?!\.$)(?!\.\.$)(?!__.*__)[^/\s]{1,1500}$/;
136
- if (!regex.test(collectionName)) {
135
+ const firestoreRegex = /^(?!\/)(?!.*\/)(?!\.$)(?!\.\.$)(?!__.*__)[^/\s]{1,1500}$/;
136
+ if (!firestoreRegex.test(collectionName)) {
137
137
  errors.push(`Invalid collection name: ${collectionName}. Must be a valid Firestore collection ID.`);
138
138
  }
139
- if (collectionName.includes("?")) {
140
- errors.push(`Invalid collection name: ${collectionName}. Collection names cannot contain question marks.`);
139
+ const tsIdentifierRegex = /^[A-Z][A-Za-z0-9_]*$/;
140
+ if (!tsIdentifierRegex.test(collectionName)) {
141
+ errors.push(`Invalid collection name: ${collectionName}. Must start with a capital letter and contain only letters, digits, and underscores.`);
141
142
  }
142
- const formattedCollectionName = collectionName.replace(/\s+/g, "").replace(/^\w/, (c) => c.toUpperCase());
143
- if (formattedCollectionName !== collectionName) {
144
- errors.push(`Invalid collection name: ${collectionName}. Collection names should not have spaces and should start with a capital letter.`);
145
- }
146
- if (collectionName.includes("-")) {
147
- errors.push(`Invalid collection name: ${collectionName}. Collection names cannot have dashes. Use underscores instead.`);
143
+ if (labels.collection !== collectionName) {
144
+ errors.push(`Invalid collection label: collection label "${labels.collection}" must match collection file name "${collectionName}".`);
148
145
  }
149
146
  fieldNames.forEach((fieldName) => {
150
147
  const formattedFieldName = fieldName.replace(/\s+/g, "").replace(/^\w/, (c) => c.toUpperCase());
package/lib/src/main.js CHANGED
@@ -18,6 +18,7 @@ if (!process.env.GCP_PROJECT &&
18
18
  "generate-firestore-indexes",
19
19
  "generate-firestore-rules",
20
20
  "generate-storage-rules",
21
+ "generate-types",
21
22
  "add-project",
22
23
  "list-projects",
23
24
  ].includes(process.argv[2])) {
@@ -84,6 +85,7 @@ import { applySchema } from "./deploy/schema/applySchema.js";
84
85
  import { addTenant } from "./project/addTenant.js";
85
86
  import { deleteTenant } from "./project/deleteTenant.js";
86
87
  import { updateLiveSchema } from "./deploy/schema/updateLiveSchema.js";
88
+ import { generateTypes } from "./types/generateTypes.js";
87
89
  const program = new Command();
88
90
  program.name("stoker").description("Stoker Platform CLI").version(pkg.version);
89
91
  program
@@ -196,6 +198,12 @@ program
196
198
  .action(() => {
197
199
  generateStorageRules();
198
200
  });
201
+ program
202
+ .command("generate-types")
203
+ .description("generate TypeScript collection types")
204
+ .action(() => {
205
+ generateTypes();
206
+ });
199
207
  program
200
208
  .command("deploy")
201
209
  .description("deploy project")
@@ -0,0 +1,107 @@
1
+ import { writeFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { isRelationField } from "@stoker-platform/utils";
4
+ import { generateSchema } from "../deploy/schema/generateSchema.js";
5
+ const INDENT = " ";
6
+ const tsLiteral = (value) => JSON.stringify(value);
7
+ const literalUnion = (values) => values.map(tsLiteral).join(" | ");
8
+ const fieldType = (field, mode) => {
9
+ switch (field.type) {
10
+ case "String":
11
+ return field.values?.length ? literalUnion(field.values) : "string";
12
+ case "Boolean":
13
+ return "boolean";
14
+ case "Number":
15
+ if (field.values?.length)
16
+ return literalUnion(field.values);
17
+ if (field.autoIncrement) {
18
+ return mode === "record" ? `number | ${tsLiteral("Pending")}` : "number";
19
+ }
20
+ return "number";
21
+ case "Timestamp":
22
+ return "FirestoreTimestamp";
23
+ case "Array":
24
+ return field.values?.length ? `(${literalUnion(field.values)})[]` : "unknown[]";
25
+ case "Map":
26
+ return "Record<string, unknown>";
27
+ case "Embedding":
28
+ return "unknown";
29
+ case "Computed":
30
+ return mode === "record" ? "string | number" : "never";
31
+ default:
32
+ return "unknown";
33
+ }
34
+ };
35
+ const buildField = (field, mode) => {
36
+ if (field.type === "Embedding")
37
+ return [];
38
+ if (field.type === "Computed" && mode === "input")
39
+ return [];
40
+ if (isRelationField(field)) {
41
+ const required = Boolean(field.required);
42
+ const optional = required ? "" : "?";
43
+ if (mode === "input") {
44
+ return [`${INDENT}${tsLiteral(field.name)}${optional}: StokerRelationObject`];
45
+ }
46
+ return [
47
+ `${INDENT}${tsLiteral(field.name)}${optional}: StokerRelationObject`,
48
+ `${INDENT}${tsLiteral(`${field.name}_Array`)}${optional}: StokerRelationArray`,
49
+ ];
50
+ }
51
+ const isAutoIncrement = field.type === "Number" && field.autoIncrement;
52
+ const required = Boolean(field.required) && !(mode === "input" && isAutoIncrement);
53
+ const optional = required ? "" : "?";
54
+ const nullable = "nullable" in field && field.nullable ? " | null" : "";
55
+ return [`${INDENT}${tsLiteral(field.name)}${optional}: ${fieldType(field, mode)}${nullable}`];
56
+ };
57
+ const buildRecordType = (collection) => {
58
+ const typeName = `${collection.labels.collection}Record`;
59
+ const lines = collection.fields.flatMap((field) => buildField(field, "record"));
60
+ return [`export type ${typeName} = SystemFields & {`, ...lines, "}"].join("\n");
61
+ };
62
+ const buildCreateInputType = (collection) => {
63
+ const typeName = `${collection.labels.collection}CreateInput`;
64
+ const lines = collection.fields.flatMap((field) => buildField(field, "input"));
65
+ return [`export type ${typeName} = {`, ...lines, "}"].join("\n");
66
+ };
67
+ const buildUpdateInputType = (collection) => {
68
+ const typeName = collection.labels.collection;
69
+ return `export type ${typeName}UpdateInput = Partial<${typeName}CreateInput>`;
70
+ };
71
+ const buildMap = (mapName, collections, suffix) => {
72
+ const entries = collections.map((collection) => `${INDENT}${tsLiteral(collection.labels.collection)}: ${collection.labels.collection}${suffix}`);
73
+ return [`export type ${mapName} = {`, ...entries, "}"].join("\n");
74
+ };
75
+ const buildOutput = (collections) => {
76
+ const sections = [
77
+ "import type {",
78
+ `${INDENT}FirestoreTimestamp,`,
79
+ `${INDENT}SystemFields,`,
80
+ `${INDENT}StokerRelationArray,`,
81
+ `${INDENT}StokerRelationObject,`,
82
+ '} from "@stoker-platform/types"',
83
+ "",
84
+ [
85
+ "export type CollectionName =",
86
+ ...collections.map((collection) => `${INDENT}| ${tsLiteral(collection.labels.collection)}`),
87
+ ].join("\n"),
88
+ "",
89
+ ];
90
+ for (const collection of collections) {
91
+ sections.push(buildRecordType(collection), "", buildCreateInputType(collection), "", buildUpdateInputType(collection), "");
92
+ }
93
+ sections.push(buildMap("CollectionRecordMap", collections, "Record"), "", buildMap("CollectionCreateInputMap", collections, "CreateInput"), "", buildMap("CollectionUpdateInputMap", collections, "UpdateInput"), "", "export type CollectionRecord<C extends CollectionName> = CollectionRecordMap[C]", "export type CollectionCreateInput<C extends CollectionName> = CollectionCreateInputMap[C]", "export type CollectionUpdateInput<C extends CollectionName> = CollectionUpdateInputMap[C]", "");
94
+ return sections.join("\n");
95
+ };
96
+ export const generateTypes = async () => {
97
+ const schema = await generateSchema(true, true);
98
+ const collections = Object.values(schema.collections).sort((a, b) => a.labels.collection.localeCompare(b.labels.collection));
99
+ const outPath = join(process.cwd(), "src", "types.ts");
100
+ await writeFile(outPath, buildOutput(collections), "utf8");
101
+ if (collections.length === 1) {
102
+ console.log("Generated types for 1 collection");
103
+ }
104
+ else {
105
+ console.log(`Generated types for ${collections.length} collections`);
106
+ }
107
+ };
@@ -1 +1 @@
1
- {"root":["../src/main.ts","../src/data/exporttobigquery.ts","../src/data/seeddata.ts","../src/deploy/deployproject.ts","../src/deploy/cloud-functions/getfunctionsdata.ts","../src/deploy/firestore-export/exportfirestoredata.ts","../src/deploy/firestore-ttl/deployttls.ts","../src/deploy/live-update/liveupdate.ts","../src/deploy/maintenance/activatemaintenancemode.ts","../src/deploy/maintenance/disablemaintenancemode.ts","../src/deploy/maintenance/setdeploymentstatus.ts","../src/deploy/rules-indexes/generatefirestoreindexes.ts","../src/deploy/rules-indexes/generatefirestorerules.ts","../src/deploy/rules-indexes/generatestoragerules.ts","../src/deploy/schema/applyschema.ts","../src/deploy/schema/generateschema.ts","../src/deploy/schema/persistschema.ts","../src/deploy/schema/updateliveschema.ts","../src/lint/lintschema.ts","../src/lint/securityreport.ts","../src/migration/migrateall.ts","../src/migration/firestore/migratefirestore.ts","../src/migration/firestore/operations/deletefield.ts","../src/ops/auditdenormalized.ts","../src/ops/auditpermissions.ts","../src/ops/auditrelations.ts","../src/ops/explainpreloadqueries.ts","../src/ops/getuser.ts","../src/ops/getuserpermissions.ts","../src/ops/getuserrecord.ts","../src/ops/listprojects.ts","../src/ops/setusercollection.ts","../src/ops/setuserdocument.ts","../src/ops/setuserrole.ts","../src/project/addproject.ts","../src/project/addrecord.ts","../src/project/addrecordprompt.ts","../src/project/addtenant.ts","../src/project/buildwebapp.ts","../src/project/customdomain.ts","../src/project/deleteproject.ts","../src/project/deleterecord.ts","../src/project/deletetenant.ts","../src/project/getone.ts","../src/project/getsome.ts","../src/project/initproject.ts","../src/project/prepareemulatordata.ts","../src/project/setproject.ts","../src/project/startemulators.ts","../src/project/updaterecord.ts"],"version":"6.0.3"}
1
+ {"root":["../src/main.ts","../src/data/exporttobigquery.ts","../src/data/seeddata.ts","../src/deploy/deployproject.ts","../src/deploy/cloud-functions/getfunctionsdata.ts","../src/deploy/firestore-export/exportfirestoredata.ts","../src/deploy/firestore-ttl/deployttls.ts","../src/deploy/live-update/liveupdate.ts","../src/deploy/maintenance/activatemaintenancemode.ts","../src/deploy/maintenance/disablemaintenancemode.ts","../src/deploy/maintenance/setdeploymentstatus.ts","../src/deploy/rules-indexes/generatefirestoreindexes.ts","../src/deploy/rules-indexes/generatefirestorerules.ts","../src/deploy/rules-indexes/generatestoragerules.ts","../src/deploy/schema/applyschema.ts","../src/deploy/schema/generateschema.ts","../src/deploy/schema/persistschema.ts","../src/deploy/schema/updateliveschema.ts","../src/lint/lintschema.ts","../src/lint/securityreport.ts","../src/migration/migrateall.ts","../src/migration/firestore/migratefirestore.ts","../src/migration/firestore/operations/deletefield.ts","../src/ops/auditdenormalized.ts","../src/ops/auditpermissions.ts","../src/ops/auditrelations.ts","../src/ops/explainpreloadqueries.ts","../src/ops/getuser.ts","../src/ops/getuserpermissions.ts","../src/ops/getuserrecord.ts","../src/ops/listprojects.ts","../src/ops/setusercollection.ts","../src/ops/setuserdocument.ts","../src/ops/setuserrole.ts","../src/project/addproject.ts","../src/project/addrecord.ts","../src/project/addrecordprompt.ts","../src/project/addtenant.ts","../src/project/buildwebapp.ts","../src/project/customdomain.ts","../src/project/deleteproject.ts","../src/project/deleterecord.ts","../src/project/deletetenant.ts","../src/project/getone.ts","../src/project/getsome.ts","../src/project/initproject.ts","../src/project/prepareemulatordata.ts","../src/project/setproject.ts","../src/project/startemulators.ts","../src/project/updaterecord.ts","../src/types/generatetypes.ts"],"version":"6.0.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/cli",
3
- "version": "0.5.82",
3
+ "version": "0.5.86",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./lib/src/main.js",
@@ -24,9 +24,9 @@
24
24
  "@google-cloud/secret-manager": "^6.1.1",
25
25
  "@google-cloud/storage": "^7.19.0",
26
26
  "@inquirer/prompts": "^8.4.2",
27
- "@stoker-platform/node-client": "0.5.58",
28
- "@stoker-platform/types": "0.5.37",
29
- "@stoker-platform/utils": "0.5.49",
27
+ "@stoker-platform/node-client": "0.5.60",
28
+ "@stoker-platform/types": "0.5.39",
29
+ "@stoker-platform/utils": "0.5.51",
30
30
  "algoliasearch": "^5.51.0",
31
31
  "commander": "^14.0.0",
32
32
  "cross-spawn": "^7.0.6",