vovk-cli 0.2.0 → 0.3.0-beta.0
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/CHANGELOG.md +38 -0
- package/client-templates/schema-ts/schema.ts.ejs +1 -1
- package/client-templates/ts-base/index.ts.ejs +1 -1
- package/dist/bundle/index.mjs +64 -42
- package/dist/dev/diff-segment-schema.d.mts +0 -17
- package/dist/dev/diff-segment-schema.mjs +1 -17
- package/dist/dev/ensure-schema-files.mjs +28 -10
- package/dist/dev/index.mjs +19 -13
- package/dist/dev/log-diff-result.d.mts +1 -1
- package/dist/dev/log-diff-result.mjs +1 -1
- package/dist/dev/write-meta-json.mjs +2 -2
- package/dist/dev/write-one-segment-schema-file.mjs +7 -2
- package/dist/generate/ensure-client.mjs +1 -1
- package/dist/generate/generate.mjs +38 -25
- package/dist/generate/get-client-template-files.mjs +8 -4
- package/dist/generate/get-project-full-schema.d.mts +1 -1
- package/dist/generate/get-project-full-schema.mjs +2 -2
- package/dist/generate/index.d.mts +2 -2
- package/dist/generate/index.mjs +46 -34
- package/dist/generate/write-one-client-file.d.mts +3 -3
- package/dist/generate/write-one-client-file.mjs +8 -6
- package/dist/get-project-info/get-config/get-relative-src-root.mjs +1 -1
- package/dist/get-project-info/get-config/get-template-defs.d.mts +20 -23
- package/dist/get-project-info/get-config/get-template-defs.mjs +21 -47
- package/dist/get-project-info/get-config/get-user-config.mjs +1 -1
- package/dist/get-project-info/get-config/index.d.mts +1 -1
- package/dist/get-project-info/get-config/index.mjs +15 -10
- package/dist/get-project-info/get-meta-schema.d.mts +1 -1
- package/dist/get-project-info/get-meta-schema.mjs +1 -1
- package/dist/get-project-info/index.mjs +2 -2
- package/dist/index.mjs +25 -16
- package/dist/init/check-tsconfig-for-experimental-decorators.mjs +1 -1
- package/dist/init/create-config.d.mts +1 -1
- package/dist/init/create-config.mjs +5 -5
- package/dist/init/index.d.mts +1 -1
- package/dist/init/index.mjs +49 -17
- package/dist/init/install-dependencies.d.mts +2 -1
- package/dist/init/install-dependencies.mjs +10 -2
- package/dist/init/update-dependencies-without-installing.d.mts +1 -1
- package/dist/init/update-dependencies-without-installing.mjs +0 -1
- package/dist/init/update-gitignore.d.mts +2 -0
- package/dist/init/update-gitignore.mjs +20 -0
- package/dist/init/update-typescript-config.mjs +1 -1
- package/dist/new/index.d.mts +1 -1
- package/dist/new/new-module.mjs +11 -16
- package/dist/new/new-segment.mjs +9 -4
- package/dist/new/render.mjs +1 -1
- package/dist/types.d.mts +1 -1
- package/dist/utils/compile-json-schema-to-typescript-type.d.mts +1 -1
- package/dist/utils/compile-ts.mjs +1 -1
- package/dist/utils/generate-fn-name.d.mts +0 -9
- package/dist/utils/generate-fn-name.mjs +2 -9
- package/dist/utils/generated-banner.d.mts +1 -0
- package/dist/utils/generated-banner.mjs +3 -0
- package/dist/utils/get-available-port.d.mts +0 -8
- package/dist/utils/get-available-port.mjs +2 -14
- package/dist/utils/get-file-system-entry-type.d.mts +5 -4
- package/dist/utils/get-file-system-entry-type.mjs +5 -5
- package/dist/utils/normalize-openapi-mixin.d.mts +1 -1
- package/dist/utils/normalize-openapi-mixin.mjs +18 -3
- package/dist/utils/prettify.d.mts +4 -0
- package/dist/utils/prettify.mjs +39 -3
- package/dist/utils/remove-unlisted-directories.d.mts +1 -9
- package/dist/utils/remove-unlisted-directories.mjs +60 -21
- package/dist/utils/resolve-absolute-module-path.mjs +1 -2
- package/dist/utils/update-config-property.mjs +1 -1
- package/package.json +23 -30
- package/client-templates/js-base/index.d.ts.ejs +0 -21
- package/client-templates/js-base/index.js.ejs +0 -18
- package/client-templates/openapi-js/openapi.d.ts.ejs +0 -4
- package/client-templates/openapi-js/openapi.js.ejs +0 -4
- package/client-templates/schema-js/schema.d.ts.ejs +0 -10
- package/client-templates/schema-js/schema.js.ejs +0 -29
|
@@ -1,28 +1,62 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @param allowedDirs - Array of relative directory paths to keep
|
|
10
|
-
* @returns Promise that resolves when all operations are complete
|
|
11
|
-
*/
|
|
12
|
-
export async function removeUnlistedDirectories(folderPath, allowedDirs) {
|
|
3
|
+
import { GENERATED_BANNER_PREFIX } from './generated-banner.mjs';
|
|
4
|
+
import { FileSystemEntryType, getFileSystemEntryType } from './get-file-system-entry-type.mjs';
|
|
5
|
+
// removes all dirs in folderPath that aren't in allowedDirs, supports nested paths like 'foo/bar/baz'
|
|
6
|
+
// generatedRelPaths guards user files: a dir holding anything the generator wouldn't write is kept,
|
|
7
|
+
// returns the dirs that were kept for that reason
|
|
8
|
+
export async function removeUnlistedDirectories(folderPath, allowedDirs, generatedRelPaths) {
|
|
13
9
|
// Normalize all allowed paths to use the system-specific separator
|
|
14
10
|
const normalizedAllowedDirs = allowedDirs.map((dir) => dir.split('/').join(path.sep));
|
|
11
|
+
const skipped = [];
|
|
15
12
|
// Process the directory tree recursively
|
|
16
|
-
await processDirectory(folderPath, '', normalizedAllowedDirs);
|
|
13
|
+
await processDirectory(folderPath, '', normalizedAllowedDirs, generatedRelPaths, skipped);
|
|
14
|
+
return skipped;
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
// "[package_name]" is substituted at write time, so treat it as a wildcard segment
|
|
17
|
+
function matchesGeneratedPath(relPath, generatedRelPaths) {
|
|
18
|
+
const segments = relPath.split(path.sep);
|
|
19
|
+
return generatedRelPaths.some((generated) => {
|
|
20
|
+
const generatedSegments = generated.split(path.sep);
|
|
21
|
+
if (generatedSegments.length > segments.length)
|
|
22
|
+
return false;
|
|
23
|
+
// a nested segment such as bar/baz adds leading directories, so match the tail
|
|
24
|
+
const offset = segments.length - generatedSegments.length;
|
|
25
|
+
return generatedSegments.every((segment, i) => segment === '[package_name]' || segment === segments[offset + i]);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// a matching name is not enough, a user file may be named like ours, so require our banner too.
|
|
29
|
+
// json holds no comment and therefore no banner, so there the name stays the only signal
|
|
30
|
+
async function isGeneratedFile(absolutePath, relPath, generatedRelPaths) {
|
|
31
|
+
if (!matchesGeneratedPath(relPath, generatedRelPaths))
|
|
32
|
+
return false;
|
|
33
|
+
if (path.extname(absolutePath) === '.json')
|
|
34
|
+
return true;
|
|
35
|
+
try {
|
|
36
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
37
|
+
return content.slice(0, content.indexOf('\n') + 1 || undefined).includes(GENERATED_BANNER_PREFIX);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// true when every file below dirPath is something the generator wrote
|
|
44
|
+
async function containsOnlyGenerated(dirPath, generatedRelPaths, relativePath = '') {
|
|
45
|
+
const entries = await fs.readdir(path.join(dirPath, relativePath), { withFileTypes: true });
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const entryRelPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
if (!(await containsOnlyGenerated(dirPath, generatedRelPaths, entryRelPath)))
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
else if (!(await isGeneratedFile(path.join(dirPath, entryRelPath), entryRelPath, generatedRelPaths))) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// recursively decides which dirs to keep or remove
|
|
59
|
+
async function processDirectory(basePath, relativePath, allowedDirs, generatedRelPaths, skipped) {
|
|
26
60
|
const currentDirPath = path.join(basePath, relativePath);
|
|
27
61
|
// check if the current path is a directory
|
|
28
62
|
const type = await getFileSystemEntryType(currentDirPath);
|
|
@@ -49,11 +83,16 @@ async function processDirectory(basePath, relativePath, allowedDirs) {
|
|
|
49
83
|
});
|
|
50
84
|
if (shouldKeep) {
|
|
51
85
|
// Recursively process this directory's contents
|
|
52
|
-
await processDirectory(basePath, newRelativePath, allowedDirs);
|
|
86
|
+
await processDirectory(basePath, newRelativePath, allowedDirs, generatedRelPaths, skipped);
|
|
53
87
|
}
|
|
54
88
|
else {
|
|
55
|
-
// Remove this directory since it's not in the allowed list
|
|
56
89
|
const fullPath = path.join(basePath, newRelativePath);
|
|
90
|
+
// never delete a directory that holds files we did not generate
|
|
91
|
+
if (generatedRelPaths && !(await containsOnlyGenerated(fullPath, generatedRelPaths))) {
|
|
92
|
+
skipped.push(fullPath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Remove this directory since it's not in the allowed list
|
|
57
96
|
await fs.rm(fullPath, { recursive: true, force: true });
|
|
58
97
|
}
|
|
59
98
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { getPublicModuleNameFromPath } from './get-public-module-name-from-path.mjs';
|
|
4
4
|
// Returns the path up to and including the last occurrence of the given module name
|
|
5
5
|
export function getPathUpToModule(moduleName, fullPath) {
|
|
@@ -24,7 +24,6 @@ export function resolveAbsoluteModulePath(modulePath, cwd) {
|
|
|
24
24
|
return path.resolve(getPathUpToModule(moduleName, path.dirname(resolved)), restPath);
|
|
25
25
|
}
|
|
26
26
|
catch (e) {
|
|
27
|
-
// eslint-disable-next-line no-console
|
|
28
27
|
console.error(`Error resolving module path: ${modulePath}`, e);
|
|
29
28
|
// If resolution fails, fall back to the original behavior
|
|
30
29
|
return path.resolve(cwd, './node_modules', modulePath);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IndentationText, NewLineKind, Node, Project, QuoteKind, SyntaxKind, } from 'ts-morph';
|
|
2
2
|
export function updateConfigProperty(sourceCode, pathToProperty, newValue) {
|
|
3
3
|
const project = createProject();
|
|
4
4
|
const sourceFile = project.createSourceFile('config-temp.mts', sourceCode, { overwrite: true });
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vovk-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.0",
|
|
4
4
|
"description": "CLI tool for managing Vovk.ts projects",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"client-templates",
|
|
8
|
-
"module-templates"
|
|
8
|
+
"module-templates",
|
|
9
|
+
"CHANGELOG.md"
|
|
9
10
|
],
|
|
10
11
|
"bin": {
|
|
11
12
|
"vovk": "./dist/index.mjs"
|
|
@@ -18,10 +19,10 @@
|
|
|
18
19
|
"build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -P tsconfig.build.json",
|
|
19
20
|
"postbuild": "chmod +x ./dist/index.mjs",
|
|
20
21
|
"pre-test": "npm run build && npm run clear-test-cache",
|
|
21
|
-
"test-only": "npm run pre-test && NODE_ENV=test node --experimental-
|
|
22
|
-
"test": "npm run pre-test && NODE_ENV=test node --experimental-
|
|
22
|
+
"test-only": "npm run pre-test && NODE_ENV=test node --experimental-strip-types --experimental-vm-modules --test --test-only test/spec/**/*.mts",
|
|
23
|
+
"test": "npm run pre-test && NODE_ENV=test node --experimental-strip-types --experimental-vm-modules --test --test-concurrency=1 test/spec/**/*.mts",
|
|
23
24
|
"tsc": "tsc --noEmit",
|
|
24
|
-
"ncu": "npm-check-updates -u -x commander,
|
|
25
|
+
"ncu": "npm-check-updates -u -x commander,tsdown",
|
|
25
26
|
"npm-publish": "npm publish",
|
|
26
27
|
"clear-test-cache": "rm -rf ./tmp_*"
|
|
27
28
|
},
|
|
@@ -44,36 +45,29 @@
|
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"vovk": ">=3.0.0"
|
|
46
47
|
},
|
|
47
|
-
"optionalDependencies": {
|
|
48
|
-
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13"
|
|
49
|
-
},
|
|
50
48
|
"dependencies": {
|
|
51
49
|
"@iarna/toml": "^2.2.5",
|
|
52
|
-
"@inquirer/prompts": "^8.
|
|
53
|
-
"@npmcli/package-json": "^
|
|
50
|
+
"@inquirer/prompts": "^8.5.2",
|
|
51
|
+
"@npmcli/package-json": "^8.0.0",
|
|
54
52
|
"@types/json-schema": "^7.0.15",
|
|
55
|
-
"@types/lodash": "^4.17.
|
|
56
|
-
"chalk": "^
|
|
53
|
+
"@types/lodash": "^4.17.25",
|
|
54
|
+
"chalk": "^6.0.0",
|
|
57
55
|
"chokidar": "^5.0.0",
|
|
58
|
-
"clone-deep": "^4.0.1",
|
|
59
56
|
"commander": "^13.1.0",
|
|
60
|
-
"concurrently": "^
|
|
61
|
-
"dotenv": "^17.4.
|
|
62
|
-
"ejs": "^
|
|
63
|
-
"get-tsconfig": "^4.
|
|
57
|
+
"concurrently": "^10.0.4",
|
|
58
|
+
"dotenv": "^17.4.2",
|
|
59
|
+
"ejs": "^6.0.1",
|
|
60
|
+
"get-tsconfig": "^4.14.1",
|
|
64
61
|
"glob": "^13.0.6",
|
|
65
62
|
"gray-matter": "^4.0.3",
|
|
66
|
-
"inflection": "^3.0.2",
|
|
67
63
|
"jsonc-parser": "^3.3.1",
|
|
68
64
|
"lodash": "^4.18.1",
|
|
69
65
|
"loglevel": "^1.9.2",
|
|
70
66
|
"pluralize": "^8.0.0",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"undici": "^8.0.2",
|
|
76
|
-
"yaml": "^2.8.3"
|
|
67
|
+
"ts-morph": "^28.0.0",
|
|
68
|
+
"type-fest": "^5.8.0",
|
|
69
|
+
"undici": "^8.9.0",
|
|
70
|
+
"yaml": "^2.9.0"
|
|
77
71
|
},
|
|
78
72
|
"devDependencies": {
|
|
79
73
|
"@types/concat-stream": "^2.0.3",
|
|
@@ -81,13 +75,12 @@
|
|
|
81
75
|
"@types/js-yaml": "^4.0.9",
|
|
82
76
|
"@types/npmcli__package-json": "^4.0.4",
|
|
83
77
|
"@types/pluralize": "^0.0.33",
|
|
84
|
-
"@types/tar-stream": "^3.1.4",
|
|
85
78
|
"concat-stream": "^2.0.0",
|
|
86
|
-
"create-next-app": "^16.2.
|
|
79
|
+
"create-next-app": "^16.2.12",
|
|
87
80
|
"http-server": "^14.1.1",
|
|
88
|
-
"node-pty": "
|
|
89
|
-
"openapi3-ts": "^4.
|
|
90
|
-
"tsdown": "
|
|
91
|
-
"zod": "^4.3
|
|
81
|
+
"node-pty": "1.1.0",
|
|
82
|
+
"openapi3-ts": "^4.6.1",
|
|
83
|
+
"tsdown": "0.22.14",
|
|
84
|
+
"zod": "^4.4.3"
|
|
92
85
|
}
|
|
93
86
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<%- t.getFirstLineBanner() %>
|
|
2
|
-
import type { VovkFetcher } from 'vovk/fetcher';
|
|
3
|
-
import type { createRPC } from '<%= t.commonImports.createRPC %>';
|
|
4
|
-
<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { if(segment.segmentType !== 'mixin') { %>
|
|
5
|
-
import type { Controllers as Controllers<%= i %> } from "<%= t.segmentMeta[segment.segmentName].segmentImportPath %>";
|
|
6
|
-
<% }}) %>
|
|
7
|
-
<% if (t.hasMixins) { %>
|
|
8
|
-
import type { Controllers as MixinControllers, Mixins } from "./mixins";
|
|
9
|
-
<% } %>
|
|
10
|
-
<% Object.entries(t.reExports).forEach(([reExportWhat, reExportFrom]) => { %>
|
|
11
|
-
export { <%= reExportWhat %> } from '<%= reExportFrom %>';
|
|
12
|
-
<% }) %>
|
|
13
|
-
<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { %>
|
|
14
|
-
<% Object.keys(segment.controllers).forEach((rpcModuleName) => { %>
|
|
15
|
-
export const <%= rpcModuleName %>: ReturnType<typeof createRPC<<%= segment.segmentType === 'mixin' ? `MixinControllers` : `Controllers${i}` %>["<%= rpcModuleName %>"], typeof import('<%- t.segmentImports[segment.segmentName].fetcher %>').fetcher extends VovkFetcher<infer U> ? U : never>>;
|
|
16
|
-
<% })
|
|
17
|
-
}) %>
|
|
18
|
-
export { schema } from './schema.js';
|
|
19
|
-
<% if (t.hasMixins) { %>
|
|
20
|
-
export { Mixins };
|
|
21
|
-
<% } %>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<%- t.getFirstLineBanner() %>
|
|
2
|
-
import { createRPC } from '<%= t.commonImports.createRPC %>';
|
|
3
|
-
import { schema } from './schema.js';
|
|
4
|
-
<% Object.entries(t.reExports).forEach(([reExportWhat, reExportFrom]) => { %>
|
|
5
|
-
export { <%= reExportWhat %> } from '<%= reExportFrom %>';
|
|
6
|
-
<% }) %>
|
|
7
|
-
<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { %>
|
|
8
|
-
<% Object.keys(segment.controllers).forEach((rpcModuleName) => {
|
|
9
|
-
const apiRoot = t.segmentMeta[segment.segmentName].forceApiRoot ?? t.apiRoot; %>
|
|
10
|
-
export const <%= rpcModuleName %> = createRPC(
|
|
11
|
-
schema, '<%= segment.segmentName %>', '<%= rpcModuleName %>', import('<%- t.segmentImports[segment.segmentName].fetcher %>'),
|
|
12
|
-
{ validateOnClient: <%- t.segmentImports[segment.segmentName].validateOnClient ? `import('${t.segmentImports[segment.segmentName].validateOnClient}')` : 'undefined' %>, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : apiRoot ? `apiRoot: '${apiRoot}'` : '' %> }
|
|
13
|
-
);
|
|
14
|
-
<%
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
%>
|
|
18
|
-
export { schema };
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<%- t.getFirstLineBanner() %>
|
|
2
|
-
import type { VovkMetaSchema, VovkSegmentSchema } from 'vovk';
|
|
3
|
-
|
|
4
|
-
export const schema: {
|
|
5
|
-
$schema: '<%- t.VovkSchemaIdEnum.SCHEMA %>';
|
|
6
|
-
meta: VovkMetaSchema;
|
|
7
|
-
segments: {<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment) => { %>
|
|
8
|
-
'<%= segment.segmentName %>': VovkSegmentSchema;<% }) %>
|
|
9
|
-
};
|
|
10
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<%- t.getFirstLineBanner() %>
|
|
2
|
-
<% if(t.isVovkProject) { %>
|
|
3
|
-
import meta from './<%= t.schemaOutDir %>/_meta.json' with { type: 'json' };
|
|
4
|
-
<% } else { %>
|
|
5
|
-
const meta = <%- JSON.stringify(t.schema.meta, null, 2) %>;
|
|
6
|
-
<% } %>
|
|
7
|
-
|
|
8
|
-
<% if(t.hasMixins) { %>
|
|
9
|
-
import mixins from './mixins.json' with { type: 'json' };
|
|
10
|
-
<% } %>
|
|
11
|
-
<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType !== 'mixin').forEach((segment, i) => { %>
|
|
12
|
-
import schema<%= i %> from './<%= t.schemaOutDir %>/<%= segment.segmentName || t.ROOT_SEGMENT_FILE_NAME %>.json' with { type: 'json' };
|
|
13
|
-
<% }) %>
|
|
14
|
-
|
|
15
|
-
const segments = {<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType !== 'mixin').forEach((segment, i) => { %>
|
|
16
|
-
'<%= segment.segmentName %>': schema<%= i %>,
|
|
17
|
-
<% }) %>
|
|
18
|
-
<% if(t.hasMixins) { %>
|
|
19
|
-
...mixins,
|
|
20
|
-
<% } %>
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const schema = {
|
|
24
|
-
$schema: '<%- t.VovkSchemaIdEnum.SCHEMA %>',
|
|
25
|
-
segments,
|
|
26
|
-
meta,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export { schema };
|