vovk-cli 0.0.1-draft.8 → 0.0.1-draft.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +29 -1
  2. package/client-templates/main/main.cjs.ejs +15 -0
  3. package/client-templates/main/main.d.cts.ejs +14 -0
  4. package/client-templates/module/module.d.mts.ejs +14 -0
  5. package/client-templates/module/module.mjs.ejs +24 -0
  6. package/client-templates/python/__init__.py +276 -0
  7. package/client-templates/ts/index.ts.ejs +25 -0
  8. package/dist/dev/diffSchema.d.mts +36 -0
  9. package/dist/{watcher → dev}/diffSchema.mjs +3 -11
  10. package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +3 -0
  11. package/dist/{watcher → dev}/ensureSchemaFiles.mjs +28 -14
  12. package/dist/dev/index.d.mts +6 -0
  13. package/dist/{watcher → dev}/index.mjs +129 -62
  14. package/dist/{watcher → dev}/isMetadataEmpty.mjs +1 -1
  15. package/dist/{watcher → dev}/logDiffResult.d.mts +2 -2
  16. package/dist/dev/logDiffResult.mjs +57 -0
  17. package/dist/{watcher → dev}/writeOneSchemaFile.d.mts +1 -1
  18. package/dist/{watcher → dev}/writeOneSchemaFile.mjs +2 -2
  19. package/dist/generate/ensureClient.d.mts +5 -0
  20. package/dist/generate/ensureClient.mjs +27 -0
  21. package/dist/generate/getClientTemplates.d.mts +15 -0
  22. package/dist/generate/getClientTemplates.mjs +30 -0
  23. package/dist/generate/index.d.mts +13 -0
  24. package/dist/generate/index.mjs +84 -0
  25. package/dist/getProjectInfo/getConfig.d.mts +3 -3
  26. package/dist/getProjectInfo/getConfig.mjs +8 -5
  27. package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +2 -2
  28. package/dist/getProjectInfo/getRelativeSrcRoot.mjs +1 -1
  29. package/dist/getProjectInfo/getUserConfig.mjs +3 -1
  30. package/dist/getProjectInfo/importUncachedModule.mjs +0 -1
  31. package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -1
  32. package/dist/getProjectInfo/index.d.mts +14 -5
  33. package/dist/getProjectInfo/index.mjs +21 -13
  34. package/dist/index.d.mts +1 -27
  35. package/dist/index.mjs +59 -64
  36. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  37. package/dist/init/createConfig.d.mts +3 -4
  38. package/dist/init/createConfig.mjs +9 -8
  39. package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
  40. package/dist/init/getTemplateFilesFromPackage.mjs +4 -5
  41. package/dist/init/index.d.mts +2 -3
  42. package/dist/init/index.mjs +61 -97
  43. package/dist/init/installDependencies.d.mts +4 -1
  44. package/dist/init/installDependencies.mjs +2 -2
  45. package/dist/init/logUpdateDependenciesError.d.mts +11 -0
  46. package/dist/init/logUpdateDependenciesError.mjs +45 -0
  47. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
  48. package/dist/init/updateDependenciesWithoutInstalling.mjs +13 -8
  49. package/dist/init/updateNPMScripts.d.mts +3 -1
  50. package/dist/init/updateNPMScripts.mjs +10 -6
  51. package/dist/init/updateTypeScriptConfig.mjs +2 -2
  52. package/dist/initProgram.d.mts +2 -0
  53. package/dist/initProgram.mjs +22 -0
  54. package/dist/locateSegments.d.mts +7 -1
  55. package/dist/locateSegments.mjs +9 -6
  56. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  57. package/dist/new/addClassToSegmentCode.mjs +5 -5
  58. package/dist/new/addCommonTerms.mjs +1 -0
  59. package/dist/new/index.d.mts +2 -2
  60. package/dist/new/index.mjs +3 -3
  61. package/dist/new/newModule.d.mts +3 -3
  62. package/dist/new/newModule.mjs +37 -28
  63. package/dist/new/newSegment.mjs +8 -6
  64. package/dist/new/render.mjs +2 -5
  65. package/dist/postinstall.mjs +16 -19
  66. package/dist/types.d.mts +48 -9
  67. package/dist/utils/debounceWithArgs.d.mts +1 -1
  68. package/dist/utils/debounceWithArgs.mjs +24 -9
  69. package/dist/utils/formatLoggedSegmentName.mjs +1 -1
  70. package/dist/utils/getAvailablePort.mjs +3 -2
  71. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  72. package/package.json +21 -18
  73. package/templates/controller.ejs +10 -9
  74. package/templates/service.ejs +6 -6
  75. package/dist/generateClient.d.mts +0 -7
  76. package/dist/generateClient.mjs +0 -97
  77. package/dist/watcher/diffSchema.d.mts +0 -43
  78. package/dist/watcher/index.d.mts +0 -6
  79. package/dist/watcher/logDiffResult.mjs +0 -90
  80. package/templates/worker.ejs +0 -1
  81. /package/dist/{watcher → dev}/isMetadataEmpty.d.mts +0 -0
package/README.md CHANGED
@@ -1 +1,29 @@
1
- Description is coming soon.
1
+ <p align="center">
2
+ <picture>
3
+ <source width="300" media="(prefers-color-scheme: dark)" srcset="https://vovk.dev/vovk-logo-white.svg">
4
+ <source width="300" media="(prefers-color-scheme: light)" srcset="https://vovk.dev/vovk-logo.svg">
5
+ <img width="300" alt="vovk" src="https://vovk.dev/vovk-logo.svg">
6
+ </picture><br>
7
+ <strong>REST + RPC = ♥️</strong>
8
+ </p>
9
+
10
+ <p align="center">
11
+ Back-end meta-framework for <a href="https://nextjs.org/docs/app">Next.js</a>
12
+ </p>
13
+
14
+ ---
15
+
16
+ ## vovk-cli [![npm version](https://badge.fury.io/js/vovk-cli.svg)](https://www.npmjs.com/package/vovk-cli)
17
+
18
+ The Vovk.ts CLI that generates schema and client but also provides some useful utilities for the development process.
19
+
20
+ ```sh
21
+ npm install -D vovk-cli
22
+ ```
23
+
24
+ - [vovk dev](https://vovk.dev/cli/vovk-dev) - starts the development script that watches the changes in controllers and regenerates the schema and client.
25
+ - [vovk generate](https://vovk.dev/cli/vovk-generate) - generates the client based on the schema.
26
+ - [vovk init](https://vovk.dev/cli/vovk-init) - initializes the Vovk.ts project.
27
+ - [vovk new](https://vovk.dev/cli/vovk-new) - generates a new controller, service or a custom module.
28
+
29
+ For more information, please visit the [CLI documentation](https://vovk.dev/cli) or use `npx vovk-cli --help` to get the list of available commands and options.
@@ -0,0 +1,15 @@
1
+ <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
2
+ const { default: fetcher } = require('<%= template.imports.fetcher %>');
3
+ const { default: createRPC } = require('<%= template.imports.createRPC %>');
4
+ const schema = require('<%= template.imports.schema %>');
5
+ const { default: validateOnClient = null } = <%- template.imports.validateOnClient ? `require('${template.imports.validateOnClient}')` : '{}'%>;
6
+ const apiRoot = '<%= template.apiRoot %>';
7
+ <% template.segments.forEach((segment) => {
8
+ Object.keys(template.fullSchema[segment.segmentName].controllers).forEach((key) => { %>
9
+ exports.<%= key %> = createRPC(
10
+ schema['<%= segment.segmentName %>'].controllers.<%= key %>,
11
+ '<%= segment.segmentName %>',
12
+ { fetcher, validateOnClient, defaultOptions: { apiRoot } }
13
+ );
14
+ <% })
15
+ }) %>
@@ -0,0 +1,14 @@
1
+
2
+ <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
3
+ import type { VovkClientFetcher } from 'vovk';
4
+ import type fetcher from '<%= template.imports.fetcher %>';
5
+ import type createRPC from '<%= template.imports.createRPC %>';
6
+ <% template.segments.forEach((segment, i) => { if(Object.keys(template.fullSchema[segment.segmentName].controllers).length) { %>
7
+ import type { Controllers as Controllers<%= i %> } from "<%= segment.segmentImportPath %>";
8
+ <% }}) %>
9
+ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
10
+ <% template.segments.forEach((segment, i) => {
11
+ Object.keys(template.fullSchema[segment.segmentName].controllers).forEach((key) => { %>
12
+ export const <%= key %>: ReturnType<typeof createRPC<Controllers<%= i %>["<%= key %>"], Options>>;
13
+ <% })
14
+ }) %>
@@ -0,0 +1,14 @@
1
+
2
+ <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
3
+ import type { VovkClientFetcher } from 'vovk';
4
+ import type fetcher from '<%= template.imports.module.fetcher %>';
5
+ import type createRPC from '<%= template.imports.module.createRPC %>';
6
+ <% template.segments.forEach((segment, i) => { if(Object.keys(template.fullSchema[segment.segmentName].controllers).length) { %>
7
+ import type { Controllers as Controllers<%= i %> } from "<%= segment.segmentImportPath %>";
8
+ <% }}) %>
9
+ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
10
+ <% template.segments.forEach((segment, i) => {
11
+ Object.keys(template.fullSchema[segment.segmentName].controllers).forEach((key) => { %>
12
+ export const <%= key %>: ReturnType<typeof createRPC<Controllers<%= i %>["<%= key %>"], Options>>;
13
+ <% })
14
+ }) %>
@@ -0,0 +1,24 @@
1
+ <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
2
+ import fetcherImport from '<%= template.imports.module.fetcher %>';
3
+ import createRPCImport from '<%= template.imports.module.createRPC %>';
4
+ import schema from '<%= template.imports.module.schema %>';
5
+ <% if (template.imports.module.validateOnClient) { %>
6
+ import validateOnClientImport from '<%= template.imports.module.validateOnClient %>';
7
+ const validateOnClient = validateOnClientImport.default || validateOnClientImport;
8
+ <% } else { %>
9
+ const validateOnClient = undefined;
10
+ <% } %>
11
+ const apiRoot = '<%= template.apiRoot %>';
12
+ const fetcher = fetcherImport.default || fetcherImport;
13
+ const createRPC = createRPCImport.default || createRPCImport;
14
+ <% template.segments.forEach((segment, i) => {
15
+ Object.keys(template.fullSchema[segment.segmentName].controllers).forEach((key) => { %>
16
+ export const <%= key %> = createRPC(
17
+ schema['<%= segment.segmentName %>'].controllers.<%= key %>,
18
+ '<%= segment.segmentName %>',
19
+ { fetcher, validateOnClient, defaultOptions: { apiRoot } }
20
+ );
21
+ <%
22
+ });
23
+ });
24
+ %>
@@ -0,0 +1,276 @@
1
+ import os
2
+ import json
3
+ import requests
4
+ from jsonschema import validate
5
+ from jsonschema.exceptions import ValidationError
6
+ from typing import Optional
7
+
8
+ __all__ = [] # We'll populate this dynamically
9
+
10
+ class ServerError(Exception):
11
+ """Custom exception for server errors that include statusCode and/or message."""
12
+ def __init__(self, status_code: int, message: str):
13
+ super().__init__(f"[{status_code}] {message}")
14
+ self.status_code = status_code
15
+ self.server_message = message
16
+
17
+ def _load_full_schema() -> dict:
18
+ """
19
+ Loads the 'full-schema.json' file (which must sit in the same folder as this __init__.py).
20
+ Returns it as a Python dictionary.
21
+ """
22
+ current_dir = os.path.dirname(__file__)
23
+ schema_path = os.path.join(current_dir, "full-schema.json")
24
+ with open(schema_path, "r", encoding="utf-8") as f:
25
+ return json.load(f)
26
+
27
+ class _RPCBase:
28
+ """
29
+ Base class that provides a validated HTTP request mechanism.
30
+ All dynamic RPC classes will subclass this.
31
+ """
32
+ def __init__(self, base_url: str):
33
+ self.base_url = base_url.rstrip("/")
34
+
35
+ def _handle_stream_response(self, resp: requests.Response):
36
+ """
37
+ Returns a generator that yields JSON objects from a newline-delimited stream.
38
+ It attempts to parse each line as valid JSON.
39
+ If we encounter an 'isError' structure, we raise a ServerError immediately.
40
+ """
41
+ buffer = ""
42
+
43
+ # We'll use resp.iter_content(...) to handle partial chunks
44
+ # decode_unicode=True gives us str chunks in Python 3.
45
+ for chunk in resp.iter_content(chunk_size=None, decode_unicode=True):
46
+ buffer += chunk
47
+ lines = buffer.split("\n")
48
+ # We'll parse every line except the last, which might still be partial
49
+ for line in lines[:-1]:
50
+ line = line.strip()
51
+ if not line:
52
+ continue # skip empty lines
53
+
54
+ try:
55
+ data = json.loads(line)
56
+ except json.JSONDecodeError:
57
+ # Could happen if line is incomplete, but we got a newline anyway
58
+ continue
59
+
60
+ # If the server signals an error in-stream
61
+ if data.get("isError") and "reason" in data:
62
+ resp.close()
63
+ raise ServerError(resp.status_code, str(data["reason"]))
64
+
65
+ yield data
66
+
67
+ # The last piece (lines[-1]) may be incomplete
68
+ buffer = lines[-1]
69
+
70
+ # If there's leftover data in the buffer (no trailing newline at end):
71
+ leftover = buffer.strip()
72
+ if leftover:
73
+ try:
74
+ data = json.loads(leftover)
75
+ if data.get("isError") and "reason" in data:
76
+ resp.close()
77
+ raise ServerError(resp.status_code, str(data["reason"]))
78
+ yield data
79
+ except json.JSONDecodeError:
80
+ # Not valid JSON or partial leftover
81
+ pass
82
+
83
+ # End of stream; close the connection
84
+ resp.close()
85
+
86
+ def _request(
87
+ self,
88
+ method: str,
89
+ endpoint_path: str,
90
+ query: Optional[dict] = None,
91
+ body: Optional[dict] = None,
92
+ query_schema: Optional[dict] = None,
93
+ body_schema: Optional[dict] = None,
94
+ disable_client_validation: bool = False
95
+ ):
96
+ """
97
+ 1. If disable_client_validation is False, validates `query` & `body` (if schemas).
98
+ 2. Makes an HTTP request (using `requests` with stream=True).
99
+ 3. If the response is not 2xx:
100
+ - parse JSON for a possible error structure
101
+ - or raise requests.HTTPError if not available
102
+ 4. If 'x-vovk-stream' == 'true', return a generator that yields JSON objects.
103
+ 5. Otherwise, parse and return the actual response (JSON -> dict or fallback to text).
104
+ """
105
+ # Validate query and body if schemas are provided AND validation not disabled
106
+ if not disable_client_validation:
107
+ if query_schema:
108
+ validate(instance=query or {}, schema=query_schema)
109
+ if body_schema:
110
+ validate(instance=body or {}, schema=body_schema)
111
+
112
+ # Build the final URL
113
+ url = f"{self.base_url}/{endpoint_path}"
114
+
115
+ # Make the request (stream=True to handle streaming)
116
+ resp = requests.request(
117
+ method=method,
118
+ url=url,
119
+ params=query,
120
+ json=body,
121
+ stream=True
122
+ )
123
+
124
+ # Check if status is not 2xx
125
+ if not resp.ok:
126
+ try:
127
+ # Attempt to parse JSON error
128
+ data = resp.json()
129
+ # Example: { "statusCode": 400, "message": "Zod validation failed...", "isError": true }
130
+ if data.get("isError"):
131
+ status_code = data.get("statusCode", resp.status_code)
132
+ message = data.get("message", resp.text)
133
+ resp.close()
134
+ raise ServerError(status_code, message)
135
+ else:
136
+ # Not the structured error we expect - fallback
137
+ resp.raise_for_status()
138
+ except ValueError:
139
+ # If parsing fails, fallback
140
+ resp.raise_for_status()
141
+
142
+ # If we get here, resp is 2xx. Check if streaming is requested.
143
+ if resp.headers.get("x-vovk-stream", "").lower() == "true":
144
+ return self._handle_stream_response(resp)
145
+
146
+ # Non-streaming: parse JSON or return text
147
+ content_type = resp.headers.get("Content-Type", "").lower()
148
+ try:
149
+ if "application/json" in content_type:
150
+ result = resp.json() # parse the body as JSON
151
+ else:
152
+ result = resp.text # fallback if not JSON
153
+ finally:
154
+ # In either case, we can close the connection now since we're reading full body
155
+ resp.close()
156
+
157
+ return result
158
+
159
+
160
+ def _build_controller_class(
161
+ controller_name: str,
162
+ controller_spec: dict,
163
+ segment_name: str
164
+ ):
165
+ """
166
+ Builds a dynamic class (subclass of _RPCBase) for a single controller.
167
+ Instead of instance methods, we create class methods so we can call
168
+ them directly on the class (passing base_url, query, body, etc.).
169
+
170
+ The endpoints will be constructed as: `segmentName/prefix/path`.
171
+ If `prefix` or `path` contain placeholder segments like `:id`,
172
+ they can be replaced by passing a `params` dict, e.g. { "id": 123 }
173
+ which would convert "/foo/:id/bar" --> "/foo/123/bar"
174
+ """
175
+ prefix = controller_spec.get("prefix", "").strip("/")
176
+ handlers = controller_spec.get("handlers", {})
177
+
178
+ class_attrs = {}
179
+
180
+ for handler_name, handler_data in handlers.items():
181
+ # HTTP method (e.g., "GET", "POST", etc.)
182
+ http_method = handler_data["httpMethod"]
183
+
184
+ # Path defined in the schema (may contain ":id", etc.)
185
+ path = handler_data["path"].strip("/")
186
+
187
+ # Combine "segmentName/prefix/path" into a single path
188
+ endpoint_path = f"{segment_name}/{prefix}/{path}".strip("/")
189
+
190
+ # Optional JSON schemas (for query/body)
191
+ validation = handler_data.get("validation", {})
192
+ query_schema = validation.get("query")
193
+ body_schema = validation.get("body")
194
+
195
+ def make_class_method(
196
+ m=http_method,
197
+ ep=endpoint_path,
198
+ q_schema=query_schema,
199
+ b_schema=body_schema,
200
+ name=handler_name
201
+ ):
202
+ @classmethod
203
+ def handler(cls, base_url, *, query=None, body=None, params=None, disable_client_validation=False):
204
+ """
205
+ Class method that instantiates the RPC class (with base_url)
206
+ and immediately calls _request on that instance.
207
+
208
+ :param base_url: The base URL of your API.
209
+ :param query: An optional dict for query parameters.
210
+ :param body: An optional dict for the request JSON body.
211
+ :param params: A dict for path substitutions, e.g. {"id": 42}
212
+ which will replace ":id" in the endpoint path.
213
+ :param disable_client_validation: If True, skip schema validation.
214
+ """
215
+ final_endpoint_path = ep
216
+
217
+ # Perform path param substitution if needed
218
+ for param_key, param_val in (params or {}).items():
219
+ final_endpoint_path = final_endpoint_path.replace(
220
+ f":{param_key}",
221
+ str(param_val)
222
+ )
223
+
224
+ # Instantiate and make the request
225
+ temp_instance = cls(base_url)
226
+ return temp_instance._request(
227
+ method=m,
228
+ endpoint_path=final_endpoint_path,
229
+ query=query,
230
+ body=body,
231
+ query_schema=q_schema,
232
+ body_schema=b_schema,
233
+ disable_client_validation=disable_client_validation
234
+ )
235
+
236
+ handler.__name__ = name
237
+ return handler
238
+
239
+ # Attach the generated class method for this handler
240
+ class_attrs[handler_name] = make_class_method()
241
+
242
+ # Dynamically create a new subclass of _RPCBase with those methods
243
+ return type(controller_name, (_RPCBase,), class_attrs)
244
+
245
+ def _load_controllers():
246
+ """
247
+ Reads the entire 'full-schema.json',
248
+ iterates over each top-level segment (like 'xxx', 'yyy'),
249
+ extracts the segmentName + controllers,
250
+ and dynamically builds classes for each controller.
251
+ """
252
+ data = _load_full_schema()
253
+ all_controllers = {}
254
+
255
+ for segment_key, segment_obj in data.items():
256
+ segment_name = segment_obj.get("segmentName", "").strip("/")
257
+ controllers = segment_obj.get("controllers", {})
258
+
259
+ for ctrl_name, ctrl_spec in controllers.items():
260
+ dynamic_class = _build_controller_class(
261
+ controller_name=ctrl_name,
262
+ controller_spec=ctrl_spec,
263
+ segment_name=segment_name
264
+ )
265
+ all_controllers[ctrl_name] = dynamic_class
266
+
267
+ return all_controllers
268
+
269
+ # Build all controllers at import time
270
+ _controllers_dict = _load_controllers()
271
+
272
+ # Export them at the top level
273
+ for ctrl_name, ctrl_class in _controllers_dict.items():
274
+ globals()[ctrl_name] = ctrl_class
275
+ __all__.append(ctrl_name)
276
+
@@ -0,0 +1,25 @@
1
+ <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
2
+ import type { VovkClientFetcher } from 'vovk';
3
+ import fetcher from '<%= template.imports.fetcher %>';
4
+ import createRPC from '<%= template.imports.createRPC %>';
5
+ import schema from '<%= template.imports.schema %>';
6
+ <% template.segments.forEach((segment, i) => { if(Object.keys(template.fullSchema[segment.segmentName].controllers).length) { %>
7
+ import type { Controllers as Controllers<%= i %> } from "<%= segment.segmentImportPath %>";
8
+ <% }}) %>
9
+ <% if (template.imports.validateOnClient) { %>
10
+ import validateOnClient from '<%= template.imports.validateOnClient %>';
11
+ <% } else { %>
12
+ const validateOnClient = undefined;
13
+ <% } %>
14
+ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
15
+ const apiRoot = '<%= template.apiRoot %>';
16
+
17
+ <% template.segments.forEach((segment, i) => { %>
18
+ <% Object.keys(template.fullSchema[segment.segmentName].controllers).forEach((key) => { %>
19
+ export const <%= key %> = createRPC<Controllers<%= i %>["<%= key %>"], Options>(
20
+ schema['<%= segment.segmentName %>'].controllers.<%= key %>,
21
+ '<%= segment.segmentName %>',
22
+ { fetcher, validateOnClient, defaultOptions: { apiRoot } }
23
+ );
24
+ <% }) %>
25
+ <% }) %>
@@ -0,0 +1,36 @@
1
+ import type { VovkControllerSchema, VovkSchema } from 'vovk';
2
+ interface HandlersDiff {
3
+ nameOfClass: string;
4
+ added: string[];
5
+ removed: string[];
6
+ changed: string[];
7
+ }
8
+ interface ControllersDiff {
9
+ added: string[];
10
+ removed: string[];
11
+ handlers: HandlersDiff[];
12
+ }
13
+ export interface DiffResult {
14
+ controllers: ControllersDiff;
15
+ }
16
+ export declare function diffHandlers<T extends VovkControllerSchema['handlers']>(oldHandlers: T, newHandlers: T, nameOfClass: string): HandlersDiff;
17
+ export declare function diffControllers<T extends VovkSchema['controllers']>(oldItems: T, newItems: T): ControllersDiff;
18
+ /**
19
+ example output:
20
+ {
21
+ controllers: {
22
+ added: ["ControllerC"],
23
+ removed: ["ControllerB"],
24
+ handlers: [
25
+ {
26
+ nameOfClass: "ControllerA",
27
+ added: ["handlerF"],
28
+ removed: [],
29
+ changed: ["handlerD"]
30
+ }
31
+ ]
32
+ }
33
+ }
34
+ */
35
+ export default function diffSchema(oldJson: VovkSchema, newJson: VovkSchema): DiffResult;
36
+ export {};
@@ -18,7 +18,7 @@ export function diffHandlers(oldHandlers, newHandlers, nameOfClass) {
18
18
  }
19
19
  return { nameOfClass, added, removed, changed };
20
20
  }
21
- export function diffWorkersOrControllers(oldItems, newItems) {
21
+ export function diffControllers(oldItems, newItems) {
22
22
  const added = [];
23
23
  const removed = [];
24
24
  const handlersDiff = [];
@@ -27,7 +27,7 @@ export function diffWorkersOrControllers(oldItems, newItems) {
27
27
  added.push(item);
28
28
  }
29
29
  else {
30
- const handlers = diffHandlers(oldItems[item]._handlers, newItem._handlers, item);
30
+ const handlers = diffHandlers(oldItems[item].handlers, newItem.handlers, item);
31
31
  if (handlers.added.length || handlers.removed.length || handlers.changed.length) {
32
32
  handlersDiff.push(handlers);
33
33
  }
@@ -43,11 +43,6 @@ export function diffWorkersOrControllers(oldItems, newItems) {
43
43
  /**
44
44
  example output:
45
45
  {
46
- workers: {
47
- added: ["WorkerC"],
48
- removed: ["WorkerA"],
49
- handlers: []
50
- },
51
46
  controllers: {
52
47
  added: ["ControllerC"],
53
48
  removed: ["ControllerB"],
@@ -63,10 +58,7 @@ example output:
63
58
  }
64
59
  */
65
60
  export default function diffSchema(oldJson, newJson) {
66
- const workersDiff = diffWorkersOrControllers(oldJson.workers ?? {}, newJson.workers ?? {});
67
- const controllersDiff = diffWorkersOrControllers(oldJson.controllers ?? {}, newJson.controllers ?? {});
68
61
  return {
69
- workers: workersDiff,
70
- controllers: controllersDiff,
62
+ controllers: diffControllers(oldJson.controllers ?? {}, newJson.controllers ?? {}),
71
63
  };
72
64
  }
@@ -1,3 +1,6 @@
1
1
  import { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ /**
3
+ * Ensure that the schema files are created to avoid any import errors.
4
+ */
2
5
  export default function ensureSchemaFiles(projectInfo: ProjectInfo | null, schemaOutAbsolutePath: string, segmentNames: string[]): Promise<void>;
3
6
  export declare const debouncedEnsureSchemaFiles: import("lodash").DebouncedFunc<typeof ensureSchemaFiles>;
@@ -1,35 +1,50 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  import debounce from 'lodash/debounce.js';
4
4
  import writeOneSchemaFile, { ROOT_SEGMENT_SCHEMA_NAME } from './writeOneSchemaFile.mjs';
5
5
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
6
+ /**
7
+ * Ensure that the schema files are created to avoid any import errors.
8
+ */
6
9
  export default async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePath, segmentNames) {
7
10
  const now = Date.now();
8
11
  let hasChanged = false;
9
- // Create index.js file
10
- const indexContent = `// auto-generated
12
+ const jsContent = `// auto-generated ${new Date().toISOString()}
11
13
  ${segmentNames
12
14
  .map((segmentName) => {
13
15
  return `module.exports['${segmentName}'] = require('./${segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json');`;
14
16
  })
15
17
  .join('\n')}`;
16
- const dTsContent = `// auto-generated
18
+ const dTsContent = `// auto-generated ${new Date().toISOString()}
17
19
  import type { VovkSchema } from 'vovk';
18
- declare const segmentSchema: {
20
+ declare const fullSchema: {
19
21
  ${segmentNames.map((segmentName) => ` '${segmentName}': VovkSchema;`).join('\n')}
20
22
  };
21
- export default segmentSchema;`;
22
- const jsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.js');
23
- const dTsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.d.ts');
23
+ export default fullSchema;`;
24
+ const tsContent = `// auto-generated ${new Date().toISOString()}
25
+ import type { VovkSchema } from 'vovk';
26
+ ${segmentNames.map((segmentName, i) => `import segment${i} from './${segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json';`).join('\n')}
27
+ const fullSchema = {
28
+ ${segmentNames.map((segmentName, i) => ` '${segmentName}': segment${i} as VovkSchema,`).join('\n')}
29
+ };
30
+ export default fullSchema;`;
31
+ const jsAbsolutePath = path.join(schemaOutAbsolutePath, 'main.cjs');
32
+ const dTsAbsolutePath = path.join(schemaOutAbsolutePath, 'main.d.cts');
33
+ const tsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.ts');
24
34
  const existingJs = await fs.readFile(jsAbsolutePath, 'utf-8').catch(() => null);
25
35
  const existingDTs = await fs.readFile(dTsAbsolutePath, 'utf-8').catch(() => null);
36
+ const existingTs = await fs.readFile(tsAbsolutePath, 'utf-8').catch(() => null);
26
37
  await fs.mkdir(schemaOutAbsolutePath, { recursive: true });
27
- if (existingJs !== indexContent) {
28
- await fs.writeFile(jsAbsolutePath, indexContent);
38
+ // ignore 1st lines at the files
39
+ if (existingJs?.split('\n').slice(1).join('\n') !== jsContent.split('\n').slice(1).join('\n')) {
40
+ await fs.writeFile(jsAbsolutePath, jsContent);
29
41
  }
30
- if (existingDTs !== dTsContent) {
42
+ if (existingDTs?.split('\n').slice(1).join('\n') !== dTsContent.split('\n').slice(1).join('\n')) {
31
43
  await fs.writeFile(dTsAbsolutePath, dTsContent);
32
44
  }
45
+ if (existingTs?.split('\n').slice(1).join('\n') !== tsContent.split('\n').slice(1).join('\n')) {
46
+ await fs.writeFile(tsAbsolutePath, tsContent);
47
+ }
33
48
  // Create JSON files (if not exist) with name [segmentName].json (where segmentName can include /, which means the folder structure can be nested)
34
49
  await Promise.all(segmentNames.map(async (segmentName) => {
35
50
  const { isCreated } = await writeOneSchemaFile({
@@ -38,7 +53,6 @@ export default segmentSchema;`;
38
53
  emitSchema: false,
39
54
  segmentName,
40
55
  controllers: {},
41
- workers: {},
42
56
  },
43
57
  skipIfExists: true,
44
58
  });
@@ -78,6 +92,6 @@ export default segmentSchema;`;
78
92
  // Start the recursive deletion from the root directory
79
93
  await deleteUnnecessaryJsonFiles(schemaOutAbsolutePath);
80
94
  if (hasChanged)
81
- projectInfo?.log.info(`Schema files updated in ${Date.now() - now}ms`);
95
+ projectInfo?.log.info(`Created empty schema files in ${Date.now() - now}ms`);
82
96
  }
83
97
  export const debouncedEnsureSchemaFiles = debounce(ensureSchemaFiles, 1000);
@@ -0,0 +1,6 @@
1
+ export declare class VovkDev {
2
+ #private;
3
+ start({ exit }: {
4
+ exit: boolean;
5
+ }): Promise<void>;
6
+ }