vovk-cli 0.0.1-draft.4 → 0.0.1-draft.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{watcher → dev}/diffSchema.d.mts +2 -2
- package/dist/{watcher → dev}/diffSchema.mjs +1 -1
- package/dist/dev/ensureClient.d.mts +5 -0
- package/dist/dev/ensureClient.mjs +31 -0
- package/dist/{watcher → dev}/ensureSchemaFiles.mjs +20 -8
- package/dist/dev/index.d.mts +4 -0
- package/dist/{watcher → dev}/index.mjs +92 -55
- package/dist/{watcher → dev}/logDiffResult.d.mts +2 -2
- package/dist/{watcher → dev}/logDiffResult.mjs +13 -9
- package/dist/{watcher → dev}/writeOneSchemaFile.d.mts +1 -1
- package/dist/{watcher → dev}/writeOneSchemaFile.mjs +4 -3
- package/dist/generateClient.d.mts +1 -1
- package/dist/generateClient.mjs +6 -6
- package/dist/getProjectInfo/getConfig.mjs +1 -1
- package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +2 -2
- package/dist/getProjectInfo/getRelativeSrcRoot.mjs +4 -4
- package/dist/getProjectInfo/getUserConfig.mjs +4 -2
- package/dist/getProjectInfo/importUncachedModule.mjs +0 -1
- package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -1
- package/dist/getProjectInfo/index.mjs +2 -2
- package/dist/index.d.mts +1 -23
- package/dist/index.mjs +19 -43
- package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
- package/dist/init/createConfig.d.mts +3 -4
- package/dist/init/createConfig.mjs +5 -5
- package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
- package/dist/init/getTemplateFilesFromPackage.mjs +4 -5
- package/dist/init/index.d.mts +1 -2
- package/dist/init/index.mjs +46 -93
- package/dist/init/installDependencies.d.mts +4 -1
- package/dist/init/installDependencies.mjs +2 -2
- package/dist/init/logUpdateDependenciesError.d.mts +11 -0
- package/dist/init/logUpdateDependenciesError.mjs +45 -0
- package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
- package/dist/init/updateDependenciesWithoutInstalling.mjs +12 -7
- package/dist/init/updateNPMScripts.d.mts +3 -1
- package/dist/init/updateNPMScripts.mjs +10 -6
- package/dist/init/updateTypeScriptConfig.mjs +2 -2
- package/dist/initProgram.d.mts +2 -0
- package/dist/initProgram.mjs +21 -0
- package/dist/locateSegments.mjs +3 -3
- package/dist/new/addClassToSegmentCode.mjs +6 -2
- package/dist/new/addCommonTerms.mjs +1 -0
- package/dist/new/index.d.mts +2 -2
- package/dist/new/index.mjs +13 -3
- package/dist/new/newModule.d.mts +6 -2
- package/dist/new/newModule.mjs +50 -26
- package/dist/new/newSegment.d.mts +3 -2
- package/dist/new/newSegment.mjs +5 -5
- package/dist/new/render.d.mts +3 -7
- package/dist/new/render.mjs +11 -7
- package/dist/postinstall.mjs +5 -3
- package/dist/types.d.mts +36 -1
- package/dist/utils/debounceWithArgs.d.mts +1 -1
- package/dist/utils/debounceWithArgs.mjs +24 -9
- package/dist/utils/formatLoggedSegmentName.mjs +1 -1
- package/dist/utils/getAvailablePort.mjs +3 -2
- package/dist/utils/getFileSystemEntryType.mjs +1 -1
- package/package.json +14 -11
- package/templates/controller.ejs +20 -18
- package/templates/service.ejs +24 -4
- package/templates/worker.ejs +24 -1
- package/dist/getProjectInfo/directoryExists.d.mts +0 -1
- package/dist/getProjectInfo/directoryExists.mjs +0 -10
- package/dist/watcher/index.d.mts +0 -6
- package/templates_old/MyThingController.c.only.template.ts +0 -32
- package/templates_old/MyThingController.c.template.ts +0 -34
- package/templates_old/MyThingService.s.template.ts +0 -18
- package/templates_old/controller.ejs +0 -85
- package/templates_old/service.ejs +0 -9
- package/templates_old/worker.ejs +0 -9
- package/templates_old/zod/MyThingController.c.only.template.ts +0 -32
- package/templates_old/zod/MyThingController.c.template.ts +0 -39
- package/templates_old/zod/MyThingService.s.template.ts +0 -18
- /package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +0 -0
- /package/dist/{watcher → dev}/isMetadataEmpty.d.mts +0 -0
- /package/dist/{watcher → dev}/isMetadataEmpty.mjs +0 -0
package/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Description is coming soon.
|
|
1
|
+
Description is coming soon.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
|
|
2
|
+
import type { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
|
|
3
3
|
interface HandlersDiff {
|
|
4
4
|
nameOfClass: string;
|
|
5
5
|
added: string[];
|
|
@@ -15,7 +15,7 @@ export interface DiffResult {
|
|
|
15
15
|
workers: WorkersOrControllersDiff;
|
|
16
16
|
controllers: WorkersOrControllersDiff;
|
|
17
17
|
}
|
|
18
|
-
export declare function diffHandlers<T extends _VovkWorkerSchema['
|
|
18
|
+
export declare function diffHandlers<T extends _VovkWorkerSchema['handlers'] | _VovkControllerSchema['handlers']>(oldHandlers: T, newHandlers: T, nameOfClass: string): HandlersDiff;
|
|
19
19
|
export declare function diffWorkersOrControllers<T extends VovkSchema['controllers'] | VovkSchema['workers']>(oldItems: T, newItems: T): WorkersOrControllersDiff;
|
|
20
20
|
/**
|
|
21
21
|
example output:
|
|
@@ -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].
|
|
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
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
export default async function ensureClient(projectInfo) {
|
|
4
|
+
const { config, cwd, log } = projectInfo;
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
|
|
7
|
+
const dts = `// auto-generated
|
|
8
|
+
// This is a temporary placeholder to avoid errors if client is imported before it's generated.
|
|
9
|
+
// If you still see this text, the client is not generated yet because of an unknown problem.
|
|
10
|
+
// Feel free to report an issue at https://github.com/finom/vovk/issues`;
|
|
11
|
+
const js = dts;
|
|
12
|
+
const ts = dts;
|
|
13
|
+
const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.js');
|
|
14
|
+
const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.d.ts');
|
|
15
|
+
const localTsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'index.ts');
|
|
16
|
+
const existingJs = await fs.readFile(localJsAbsolutePath, 'utf-8').catch(() => null);
|
|
17
|
+
const existingDts = await fs.readFile(localDtsAbsolutePath, 'utf-8').catch(() => null);
|
|
18
|
+
const existingTs = await fs.readFile(localTsAbsolutePath, 'utf-8').catch(() => null);
|
|
19
|
+
if (existingJs && existingDts && existingTs) {
|
|
20
|
+
return { written: false, path: clientoOutDirAbsolutePath };
|
|
21
|
+
}
|
|
22
|
+
await fs.mkdir(clientoOutDirAbsolutePath, { recursive: true });
|
|
23
|
+
if (!existingJs)
|
|
24
|
+
await fs.writeFile(localJsAbsolutePath, js);
|
|
25
|
+
if (!existingDts)
|
|
26
|
+
await fs.writeFile(localDtsAbsolutePath, dts);
|
|
27
|
+
if (!existingTs)
|
|
28
|
+
await fs.writeFile(localTsAbsolutePath, ts);
|
|
29
|
+
log.info(`Empty client files are generated in ${Date.now() - now}ms`);
|
|
30
|
+
return { written: true, path: clientoOutDirAbsolutePath };
|
|
31
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
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';
|
|
@@ -7,17 +7,29 @@ export default async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePa
|
|
|
7
7
|
const now = Date.now();
|
|
8
8
|
let hasChanged = false;
|
|
9
9
|
// Create index.js file
|
|
10
|
-
const indexContent =
|
|
10
|
+
const indexContent = `// auto-generated
|
|
11
|
+
${segmentNames
|
|
11
12
|
.map((segmentName) => {
|
|
12
13
|
return `module.exports['${segmentName}'] = require('./${segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json');`;
|
|
13
14
|
})
|
|
14
|
-
.join('\n')
|
|
15
|
-
const dTsContent =
|
|
16
|
-
|
|
15
|
+
.join('\n')}`;
|
|
16
|
+
const dTsContent = `// auto-generated
|
|
17
|
+
import type { VovkSchema } from 'vovk';
|
|
18
|
+
declare const segmentSchema: {
|
|
19
|
+
${segmentNames.map((segmentName) => ` '${segmentName}': VovkSchema;`).join('\n')}
|
|
20
|
+
};
|
|
17
21
|
export default segmentSchema;`;
|
|
22
|
+
const jsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.js');
|
|
23
|
+
const dTsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.d.ts');
|
|
24
|
+
const existingJs = await fs.readFile(jsAbsolutePath, 'utf-8').catch(() => null);
|
|
25
|
+
const existingDTs = await fs.readFile(dTsAbsolutePath, 'utf-8').catch(() => null);
|
|
18
26
|
await fs.mkdir(schemaOutAbsolutePath, { recursive: true });
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
if (existingJs !== indexContent) {
|
|
28
|
+
await fs.writeFile(jsAbsolutePath, indexContent);
|
|
29
|
+
}
|
|
30
|
+
if (existingDTs !== dTsContent) {
|
|
31
|
+
await fs.writeFile(dTsAbsolutePath, dTsContent);
|
|
32
|
+
}
|
|
21
33
|
// Create JSON files (if not exist) with name [segmentName].json (where segmentName can include /, which means the folder structure can be nested)
|
|
22
34
|
await Promise.all(segmentNames.map(async (segmentName) => {
|
|
23
35
|
const { isCreated } = await writeOneSchemaFile({
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import * as chokidar from 'chokidar';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
4
|
+
import { Agent, setGlobalDispatcher } from 'undici';
|
|
5
|
+
import keyBy from 'lodash/keyBy.js';
|
|
6
|
+
import capitalize from 'lodash/capitalize.js';
|
|
7
|
+
import debounce from 'lodash/debounce.js';
|
|
8
|
+
import isEmpty from 'lodash/isEmpty.js';
|
|
5
9
|
import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
|
|
6
10
|
import writeOneSchemaFile from './writeOneSchemaFile.mjs';
|
|
7
11
|
import logDiffResult from './logDiffResult.mjs';
|
|
12
|
+
import ensureClient from './ensureClient.mjs';
|
|
13
|
+
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
8
14
|
import generateClient from '../generateClient.mjs';
|
|
9
15
|
import locateSegments from '../locateSegments.mjs';
|
|
10
16
|
import debounceWithArgs from '../utils/debounceWithArgs.mjs';
|
|
11
|
-
import debounce from 'lodash/debounce.js';
|
|
12
|
-
import isEmpty from 'lodash/isEmpty.js';
|
|
13
17
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
14
|
-
|
|
15
|
-
import capitalize from 'lodash/capitalize.js';
|
|
16
|
-
import { Agent, setGlobalDispatcher } from 'undici';
|
|
17
|
-
export class VovkCLIWatcher {
|
|
18
|
+
export class VovkDev {
|
|
18
19
|
#projectInfo;
|
|
19
20
|
#segments = [];
|
|
20
21
|
#schemas = {};
|
|
21
22
|
#isWatching = false;
|
|
22
23
|
#modulesWatcher = null;
|
|
23
24
|
#segmentWatcher = null;
|
|
24
|
-
#watchSegments = () => {
|
|
25
|
+
#watchSegments = (callback) => {
|
|
25
26
|
const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
|
|
26
27
|
const { cwd, log, config, apiDir } = this.#projectInfo;
|
|
27
28
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
28
29
|
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
29
30
|
const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
|
|
30
|
-
log.debug(`Watching segments
|
|
31
|
+
log.debug(`Watching segments at ${apiDirAbsolutePath}`);
|
|
31
32
|
this.#segmentWatcher = chokidar
|
|
32
33
|
.watch(apiDirAbsolutePath, {
|
|
33
34
|
persistent: true,
|
|
@@ -51,7 +52,6 @@ export class VovkCLIWatcher {
|
|
|
51
52
|
void this.#requestSchema(getSegmentName(filePath));
|
|
52
53
|
}
|
|
53
54
|
})
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
55
55
|
.on('addDir', async (dirPath) => {
|
|
56
56
|
log.debug(`Directory ${dirPath} has been added to segments folder`);
|
|
57
57
|
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
@@ -59,7 +59,6 @@ export class VovkCLIWatcher {
|
|
|
59
59
|
void this.#requestSchema(segmentName);
|
|
60
60
|
}
|
|
61
61
|
})
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
63
62
|
.on('unlinkDir', async (dirPath) => {
|
|
64
63
|
log.debug(`Directory ${dirPath} has been removed from segments folder`);
|
|
65
64
|
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
@@ -78,16 +77,17 @@ export class VovkCLIWatcher {
|
|
|
78
77
|
}
|
|
79
78
|
})
|
|
80
79
|
.on('ready', () => {
|
|
80
|
+
callback();
|
|
81
81
|
log.debug('Segments watcher is ready');
|
|
82
82
|
})
|
|
83
83
|
.on('error', (error) => {
|
|
84
|
-
log.error(`Error watching segments folder: ${error
|
|
84
|
+
log.error(`Error watching segments folder: ${error?.message ?? 'Unknown error'}`);
|
|
85
85
|
});
|
|
86
86
|
};
|
|
87
|
-
#watchModules = () => {
|
|
87
|
+
#watchModules = (callback) => {
|
|
88
88
|
const { config, cwd, log } = this.#projectInfo;
|
|
89
89
|
const modulesDirAbsolutePath = path.join(cwd, config.modulesDir);
|
|
90
|
-
log.debug(`Watching modules
|
|
90
|
+
log.debug(`Watching modules at ${modulesDirAbsolutePath}`);
|
|
91
91
|
const processControllerChange = debounceWithArgs(this.#processControllerChange, 500);
|
|
92
92
|
this.#modulesWatcher = chokidar
|
|
93
93
|
.watch(modulesDirAbsolutePath, {
|
|
@@ -116,30 +116,37 @@ export class VovkCLIWatcher {
|
|
|
116
116
|
}
|
|
117
117
|
})
|
|
118
118
|
.on('ready', () => {
|
|
119
|
+
callback();
|
|
119
120
|
log.debug('Modules watcher is ready');
|
|
120
121
|
})
|
|
121
122
|
.on('error', (error) => {
|
|
122
|
-
log.error(`Error watching modules folder: ${error
|
|
123
|
+
log.error(`Error watching modules folder: ${error?.message ?? 'Unknown error'}`);
|
|
123
124
|
});
|
|
124
125
|
};
|
|
125
|
-
#watchConfig = () => {
|
|
126
|
+
#watchConfig = (callback) => {
|
|
126
127
|
const { log, cwd } = this.#projectInfo;
|
|
127
128
|
log.debug(`Watching config files`);
|
|
128
129
|
let isInitial = true;
|
|
130
|
+
let isReady = false;
|
|
129
131
|
const handle = debounce(async () => {
|
|
130
132
|
this.#projectInfo = await getProjectInfo();
|
|
131
|
-
if (!isInitial) {
|
|
132
|
-
log.info('Config file has been updated');
|
|
133
|
-
isInitial = false;
|
|
134
|
-
}
|
|
135
133
|
await this.#modulesWatcher?.close();
|
|
136
134
|
await this.#segmentWatcher?.close();
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
await Promise.all([
|
|
136
|
+
new Promise((resolve) => this.#watchModules(() => resolve(0))),
|
|
137
|
+
new Promise((resolve) => this.#watchSegments(() => resolve(0)))
|
|
138
|
+
]);
|
|
139
|
+
if (isInitial) {
|
|
140
|
+
callback();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
log.info('Config file has been updated');
|
|
144
|
+
}
|
|
145
|
+
isInitial = false;
|
|
139
146
|
}, 1000);
|
|
140
|
-
let ready = false;
|
|
141
147
|
chokidar
|
|
142
|
-
.watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
148
|
+
// .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
149
|
+
.watch(['vovk.config.js', 'vovk.config.mjs', 'vovk.config.cjs', '.config/vovk.config.js', '.config/vovk.config.mjs', '.config/vovk.config.cjs'], {
|
|
143
150
|
persistent: true,
|
|
144
151
|
cwd,
|
|
145
152
|
ignoreInitial: false,
|
|
@@ -149,24 +156,23 @@ export class VovkCLIWatcher {
|
|
|
149
156
|
.on('change', () => void handle())
|
|
150
157
|
.on('unlink', () => void handle())
|
|
151
158
|
.on('ready', () => {
|
|
152
|
-
if (
|
|
159
|
+
if (isReady)
|
|
153
160
|
return;
|
|
154
161
|
// for some reason this watcher triggers ready event twice
|
|
155
162
|
log.debug('Config files watcher is ready');
|
|
156
|
-
|
|
163
|
+
isReady = true;
|
|
157
164
|
})
|
|
158
|
-
.on('error', (error) => {
|
|
159
|
-
log.error(`Error watching config files: ${error.message}`);
|
|
160
|
-
});
|
|
165
|
+
.on('error', (error) => log.error(`Error watching config files: ${error?.message ?? 'Unknown error'}`));
|
|
161
166
|
void handle();
|
|
162
167
|
};
|
|
163
|
-
#watch() {
|
|
168
|
+
async #watch(callback) {
|
|
164
169
|
if (this.#isWatching)
|
|
165
170
|
throw new Error('Already watching');
|
|
166
171
|
const { log } = this.#projectInfo;
|
|
167
172
|
log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
|
|
173
|
+
await ensureClient(this.#projectInfo);
|
|
168
174
|
// automatically watches segments and modules
|
|
169
|
-
this.#watchConfig();
|
|
175
|
+
this.#watchConfig(callback);
|
|
170
176
|
}
|
|
171
177
|
#processControllerChange = async (filePath) => {
|
|
172
178
|
const { log } = this.#projectInfo;
|
|
@@ -183,8 +189,8 @@ export class VovkCLIWatcher {
|
|
|
183
189
|
const schema = this.#schemas[s.segmentName];
|
|
184
190
|
if (!schema)
|
|
185
191
|
return false;
|
|
186
|
-
const controllersByOriginalName = keyBy(schema.controllers, '
|
|
187
|
-
const workersByOriginalName = keyBy(schema.workers, '
|
|
192
|
+
const controllersByOriginalName = keyBy(schema.controllers, 'originalControllerName');
|
|
193
|
+
const workersByOriginalName = keyBy(schema.workers, 'originalWorkerName');
|
|
188
194
|
return namesOfClasses.some((name) => schema.controllers[name] ||
|
|
189
195
|
schema.workers[name] ||
|
|
190
196
|
controllersByOriginalName[name] ||
|
|
@@ -196,6 +202,9 @@ export class VovkCLIWatcher {
|
|
|
196
202
|
await this.#requestSchema(segment.segmentName);
|
|
197
203
|
}
|
|
198
204
|
}
|
|
205
|
+
else {
|
|
206
|
+
log.debug(`The class ${namesOfClasses.join(', ')} does not belong to any segment`);
|
|
207
|
+
}
|
|
199
208
|
}
|
|
200
209
|
else {
|
|
201
210
|
log.debug(`The file does not contain any controller or worker`);
|
|
@@ -206,23 +215,29 @@ export class VovkCLIWatcher {
|
|
|
206
215
|
const { devHttps } = config;
|
|
207
216
|
const endpoint = `${apiEntryPoint.startsWith(`http${devHttps ? 's' : ''}://`) ? apiEntryPoint : `http${devHttps ? 's' : ''}://localhost:${port}${apiEntryPoint}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
|
|
208
217
|
log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
|
|
209
|
-
const resp = await fetch(endpoint);
|
|
210
|
-
if (resp.status !== 200) {
|
|
211
|
-
const probableCause = {
|
|
212
|
-
404: 'The segment is not compiled.',
|
|
213
|
-
500: 'Syntax error in one of controllers.',
|
|
214
|
-
}[resp.status];
|
|
215
|
-
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
let schema = null;
|
|
219
218
|
try {
|
|
220
|
-
|
|
219
|
+
const resp = await fetch(endpoint);
|
|
220
|
+
if (resp.status !== 200) {
|
|
221
|
+
const probableCause = {
|
|
222
|
+
404: 'The segment did not compile or config.origin is wrong.',
|
|
223
|
+
}[resp.status];
|
|
224
|
+
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
|
|
225
|
+
return { isError: true };
|
|
226
|
+
}
|
|
227
|
+
let schema = null;
|
|
228
|
+
try {
|
|
229
|
+
({ schema } = (await resp.json()));
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
233
|
+
}
|
|
234
|
+
await this.#handleSchema(schema);
|
|
221
235
|
}
|
|
222
236
|
catch (error) {
|
|
223
|
-
log.error(`Error
|
|
237
|
+
log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
238
|
+
return { isError: true };
|
|
224
239
|
}
|
|
225
|
-
|
|
240
|
+
return { isError: false };
|
|
226
241
|
}, 500);
|
|
227
242
|
async #handleSchema(schema) {
|
|
228
243
|
const { log, config, cwd } = this.#projectInfo;
|
|
@@ -259,9 +274,11 @@ export class VovkCLIWatcher {
|
|
|
259
274
|
await generateClient(this.#projectInfo, this.#segments, this.#schemas);
|
|
260
275
|
}
|
|
261
276
|
}
|
|
262
|
-
async start(
|
|
263
|
-
|
|
277
|
+
async start() {
|
|
278
|
+
const now = Date.now();
|
|
279
|
+
this.#projectInfo = await getProjectInfo();
|
|
264
280
|
const { log, config, cwd, apiDir } = this.#projectInfo;
|
|
281
|
+
log.info('Starting...');
|
|
265
282
|
if (config.devHttps) {
|
|
266
283
|
const agent = new Agent({
|
|
267
284
|
connect: {
|
|
@@ -280,16 +297,36 @@ export class VovkCLIWatcher {
|
|
|
280
297
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
281
298
|
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
282
299
|
await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
283
|
-
// Request schema every segment in
|
|
300
|
+
// Request schema every segment in 5 seconds in order to update schema and start watching
|
|
284
301
|
setTimeout(() => {
|
|
285
302
|
for (const { segmentName } of this.#segments) {
|
|
286
|
-
|
|
303
|
+
const MAX_ATTEMPTS = 3;
|
|
304
|
+
let attempts = 0;
|
|
305
|
+
void this.#requestSchema(segmentName).then(({ isError }) => {
|
|
306
|
+
if (isError) {
|
|
307
|
+
const interval = setInterval(() => {
|
|
308
|
+
attempts++;
|
|
309
|
+
if (attempts >= MAX_ATTEMPTS) {
|
|
310
|
+
clearInterval(interval);
|
|
311
|
+
log.error(`Failed to request schema for ${formatLoggedSegmentName(segmentName)} after ${MAX_ATTEMPTS} attempts`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
|
|
315
|
+
if (!isError2) {
|
|
316
|
+
clearInterval(interval);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}, 5000);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
287
322
|
}
|
|
288
|
-
|
|
289
|
-
|
|
323
|
+
}, 5000);
|
|
324
|
+
this.#watch(() => {
|
|
325
|
+
log.info(`Ready in ${Date.now() - now}ms`);
|
|
326
|
+
});
|
|
290
327
|
}
|
|
291
328
|
}
|
|
292
329
|
const env = process.env;
|
|
293
330
|
if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
|
|
294
|
-
void new
|
|
331
|
+
void new VovkDev().start();
|
|
295
332
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { DiffResult } from './diffSchema.mjs';
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
3
|
export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
2
3
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
3
4
|
export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
@@ -37,48 +38,51 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
|
37
38
|
});
|
|
38
39
|
});
|
|
39
40
|
const LIMIT = diffNormalized.length < 12 ? diffNormalized.length : 10;
|
|
41
|
+
const addedText = chalk.green('added');
|
|
42
|
+
const removedText = chalk.red('removed');
|
|
43
|
+
const changedText = chalk.cyan('changed');
|
|
40
44
|
for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
|
|
41
45
|
switch (diffNormalizedItem.what) {
|
|
42
46
|
case 'worker':
|
|
43
47
|
switch (diffNormalizedItem.type) {
|
|
44
48
|
case 'added':
|
|
45
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
49
|
+
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
46
50
|
break;
|
|
47
51
|
case 'removed':
|
|
48
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
52
|
+
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
49
53
|
break;
|
|
50
54
|
}
|
|
51
55
|
break;
|
|
52
56
|
case 'controller':
|
|
53
57
|
switch (diffNormalizedItem.type) {
|
|
54
58
|
case 'added':
|
|
55
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
59
|
+
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
56
60
|
break;
|
|
57
61
|
case 'removed':
|
|
58
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
62
|
+
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
59
63
|
break;
|
|
60
64
|
}
|
|
61
65
|
break;
|
|
62
66
|
case 'workerHandler':
|
|
63
67
|
switch (diffNormalizedItem.type) {
|
|
64
68
|
case 'added':
|
|
65
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
69
|
+
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
66
70
|
break;
|
|
67
71
|
case 'removed':
|
|
68
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
72
|
+
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
69
73
|
break;
|
|
70
74
|
}
|
|
71
75
|
break;
|
|
72
76
|
case 'controllerHandler':
|
|
73
77
|
switch (diffNormalizedItem.type) {
|
|
74
78
|
case 'added':
|
|
75
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
79
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
76
80
|
break;
|
|
77
81
|
case 'removed':
|
|
78
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
82
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
79
83
|
break;
|
|
80
84
|
case 'changed':
|
|
81
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
85
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
82
86
|
break;
|
|
83
87
|
}
|
|
84
88
|
break;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import { DiffResult } from './diffSchema.mjs';
|
|
2
|
+
import { type DiffResult } from './diffSchema.mjs';
|
|
3
3
|
export declare const ROOT_SEGMENT_SCHEMA_NAME = "_root";
|
|
4
4
|
export default function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists, }: {
|
|
5
5
|
schemaOutAbsolutePath: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs/promises';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
3
|
import diffSchema from './diffSchema.mjs';
|
|
4
|
+
import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
4
5
|
export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
|
|
5
6
|
export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists = false, }) {
|
|
6
7
|
const segmentPath = path.join(schemaOutAbsolutePath, `${schema.segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json`);
|
|
7
|
-
if (skipIfExists) {
|
|
8
|
+
if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
|
|
8
9
|
try {
|
|
9
10
|
await fs.stat(segmentPath);
|
|
10
11
|
return { isCreated: false, diffResult: null };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { VovkSchema } from 'vovk';
|
|
1
2
|
import type { ProjectInfo } from './getProjectInfo/index.mjs';
|
|
2
3
|
import type { Segment } from './locateSegments.mjs';
|
|
3
|
-
import type { VovkSchema } from 'vovk';
|
|
4
4
|
export default function generateClient(projectInfo: ProjectInfo, segments: Segment[], segmentsSchema: Record<string, VovkSchema>): Promise<{
|
|
5
5
|
written: boolean;
|
|
6
6
|
path: string;
|
package/dist/generateClient.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs/promises';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
3
|
import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
|
|
4
4
|
import prettify from './utils/prettify.mjs';
|
|
5
5
|
export default async function generateClient(projectInfo, segments, segmentsSchema) {
|
|
@@ -48,11 +48,11 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
|
48
48
|
ts += `
|
|
49
49
|
${validateOnClientImportPath ? `import validateOnClient from '${validateOnClientImportPath}';\n` : '\nconst validateOnClient = undefined;'}
|
|
50
50
|
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
51
|
-
const
|
|
51
|
+
const apiRoot = '${apiEntryPoint}';
|
|
52
52
|
`;
|
|
53
53
|
js += `
|
|
54
54
|
const { default: validateOnClient = null } = ${validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'};
|
|
55
|
-
const
|
|
55
|
+
const apiRoot = '${apiEntryPoint}';
|
|
56
56
|
`;
|
|
57
57
|
for (let i = 0; i < segments.length; i++) {
|
|
58
58
|
const { segmentName } = segments[i];
|
|
@@ -64,8 +64,8 @@ const prefix = '${apiEntryPoint}';
|
|
|
64
64
|
continue;
|
|
65
65
|
for (const key of Object.keys(schema.controllers)) {
|
|
66
66
|
dts += `export const ${key}: ReturnType<typeof clientizeController<Controllers${i}["${key}"], Options>>;\n`;
|
|
67
|
-
js += `exports.${key} = clientizeController(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: {
|
|
68
|
-
ts += `export const ${key} = clientizeController<Controllers${i}["${key}"], Options>(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: {
|
|
67
|
+
js += `exports.${key} = clientizeController(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { apiRoot } });\n`;
|
|
68
|
+
ts += `export const ${key} = clientizeController<Controllers${i}["${key}"], Options>(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { apiRoot } });\n`;
|
|
69
69
|
}
|
|
70
70
|
for (const key of Object.keys(schema.workers)) {
|
|
71
71
|
dts += `export const ${key}: ReturnType<typeof promisifyWorker<Workers${i}["${key}"]>>;\n`;
|
|
@@ -15,7 +15,7 @@ export default async function getConfig({ clientOutDir, cwd }) {
|
|
|
15
15
|
origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
16
16
|
rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
|
|
17
17
|
rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? conf.rootSegmentModulesDirName ?? '',
|
|
18
|
-
logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? '
|
|
18
|
+
logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
|
|
19
19
|
prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
|
|
20
20
|
devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
|
|
21
21
|
templates: {
|
|
@@ -1,5 +1,5 @@
|
|
|
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
|
export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
|
|
4
4
|
const rootDir = path.resolve(cwd, relativePath || '');
|
|
5
5
|
const baseName = 'vovk.config';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
|
|
3
3
|
export default async function getRelativeSrcRoot({ cwd }) {
|
|
4
4
|
// Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
|
|
5
|
-
if (await
|
|
5
|
+
if ((await getFileSystemEntryType(path.join(cwd, 'app'))) === FileSystemEntryType.DIRECTORY) {
|
|
6
6
|
return '.';
|
|
7
7
|
}
|
|
8
|
-
else if (await
|
|
8
|
+
else if ((await getFileSystemEntryType(path.join(cwd, 'src/app'))) === FileSystemEntryType.DIRECTORY) {
|
|
9
9
|
return './src';
|
|
10
10
|
}
|
|
11
11
|
throw new Error(`${cwd} Could not find app router directory. Check Next.js docs for more info.`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
1
2
|
import getConfigAbsolutePaths from './getConfigAbsolutePaths.mjs';
|
|
2
3
|
import importUncachedModule from './importUncachedModule.mjs';
|
|
3
4
|
async function getUserConfig({ cwd, }) {
|
|
@@ -11,10 +12,11 @@ async function getUserConfig({ cwd, }) {
|
|
|
11
12
|
try {
|
|
12
13
|
userConfig = await importUncachedModule(configPath);
|
|
13
14
|
}
|
|
14
|
-
catch
|
|
15
|
+
catch {
|
|
15
16
|
try {
|
|
16
17
|
const cacheBuster = Date.now();
|
|
17
|
-
|
|
18
|
+
const configPathUrl = pathToFileURL(configPath).href;
|
|
19
|
+
({ default: userConfig } = (await import(`${configPathUrl}?cache=${cacheBuster}`)));
|
|
18
20
|
}
|
|
19
21
|
catch (e) {
|
|
20
22
|
return { userConfig: null, configAbsolutePaths, error: e };
|
|
@@ -3,7 +3,6 @@ import { Worker } from 'node:worker_threads';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import './importUncachedModuleWorker.mjs'; // required for TS compilation
|
|
6
|
-
// TODO comments
|
|
7
6
|
function importUncachedModule(modulePath) {
|
|
8
7
|
return new Promise((resolve, reject) => {
|
|
9
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import path from 'path';
|
|
1
|
+
import path from 'node:path';
|
|
2
2
|
import getConfig from './getConfig.mjs';
|
|
3
3
|
import getLogger from '../utils/getLogger.mjs';
|
|
4
4
|
export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
|
|
@@ -8,7 +8,7 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, cw
|
|
|
8
8
|
const { config, srcRoot, configAbsolutePaths, userConfig, error } = await getConfig({ clientOutDir, cwd });
|
|
9
9
|
const apiEntryPoint = `${config.origin ?? ''}/${config.rootEntry}`;
|
|
10
10
|
const apiDir = path.join(srcRoot, 'app', config.rootEntry);
|
|
11
|
-
const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir);
|
|
11
|
+
const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir).replace(/\\/g, '/'); // windows fix
|
|
12
12
|
const fetcherClientImportPath = config.fetcher.startsWith('.')
|
|
13
13
|
? path.relative(config.clientOutDir, config.fetcher)
|
|
14
14
|
: config.fetcher;
|