Skip to content

Commit 1fc6490

Browse files
authored
feat(cube-cli): Schema validation command (#6208)
1 parent d0f9187 commit 1fc6490

File tree

20 files changed

+96
-36
lines changed

20 files changed

+96
-36
lines changed

packages/cubejs-server-core/src/core/FileRepository.ts renamed to packages/cubejs-backend-shared/src/FileRepository.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import path from 'path';
22
import fs from 'fs-extra';
3-
import R from 'ramda';
43

54
export interface FileContent {
65
fileName: string;
76
content: string;
7+
readOnly?: boolean;
88
}
99

1010
export interface SchemaFileRepository {
@@ -41,7 +41,7 @@ export class FileRepository implements SchemaFileRepository {
4141

4242
let result = await Promise.all(
4343
files
44-
.filter(file => R.endsWith('.js', file) || R.endsWith('.yml', file) || R.endsWith('.yaml', file))
44+
.filter(file => file.endsWith('.js') || file.endsWith('.yml') || file.endsWith('.yaml'))
4545
.map(async file => {
4646
const content = await fs.readFile(path.join(this.localPath(), file), 'utf-8');
4747

@@ -70,7 +70,7 @@ export class FileRepository implements SchemaFileRepository {
7070

7171
const files = await Promise.all(
7272
Object.keys(packageJson.dependencies).map(async module => {
73-
if (R.endsWith('-schema', module)) {
73+
if (module.endsWith('-schema')) {
7474
return this.readModuleFiles(path.join('node_modules', module));
7575
}
7676

@@ -81,7 +81,7 @@ export class FileRepository implements SchemaFileRepository {
8181
return files.reduce((a, b) => a.concat(b));
8282
}
8383

84-
protected async readModuleFiles(modulePath: string) {
84+
protected async readModuleFiles(modulePath: string): Promise<FileContent[]> {
8585
const files = await fs.readdir(modulePath);
8686

8787
const result = await Promise.all(
@@ -90,7 +90,7 @@ export class FileRepository implements SchemaFileRepository {
9090
const stats = await fs.lstat(fileName);
9191
if (stats.isDirectory()) {
9292
return this.readModuleFiles(fileName);
93-
} else if (R.endsWith('.js', file)) {
93+
} else if (file.endsWith('.js')) {
9494
const content = await fs.readFile(fileName, 'utf-8');
9595
return [
9696
{
@@ -105,6 +105,6 @@ export class FileRepository implements SchemaFileRepository {
105105
})
106106
);
107107

108-
return result.reduce((a, b) => a.concat(b), []);
108+
return result.reduce<FileContent[]>((a, b) => a.concat(b), []);
109109
}
110110
}

packages/cubejs-backend-shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export * from './cli';
1818
export * from './proxy';
1919
export * from './time';
2020
export * from './process';
21+
export * from './FileRepository';

packages/cubejs-cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
],
3232
"dependencies": {
3333
"@cubejs-backend/dotenv": "^9.0.2",
34-
"@cubejs-backend/shared": "^0.31.66",
34+
"@cubejs-backend/schema-compiler": "^0.31.65",
35+
"@cubejs-backend/shared": "^0.31.65",
3536
"chalk": "^2.4.2",
3637
"cli-progress": "^3.10",
3738
"commander": "^2.19.0",

packages/cubejs-cli/src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { configureTokenCommand } from './command/token';
88
import { configureTypegenCommand } from './command/typegen';
99
import { configureAuthCommand } from './command/auth';
1010
import { loadCliManifest } from './utils';
11+
import { configureValidateCommand } from './command/validate';
1112

1213
const packageJson = loadCliManifest();
1314

@@ -30,6 +31,7 @@ program
3031
await configureGenerateCommand(program);
3132
await configureDeployCommand(program);
3233
await configureServerCommand(program);
34+
await configureValidateCommand(program);
3335

3436
if (!process.argv.slice(2).length) {
3537
program.help();

packages/cubejs-cli/src/command/create.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export function configureCreateCommand(program: CommanderStatic) {
185185
'-t, --template <template>',
186186
'App template. Options: docker (default), express, serverless, serverless-google.'
187187
)
188-
.description('Create new Cube.js app')
188+
.description('Create new Cube app')
189189
.action(
190190
(projectName, options) => create(projectName, options)
191191
.catch(e => displayError(e.stack || e, { projectName, dbType: options.dbType }))

packages/cubejs-cli/src/command/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export function configureGenerateCommand(program: CommanderStatic) {
9797
.command('generate')
9898
.option('-t, --tables <tables>', 'Comma delimited list of tables to generate schema from', list)
9999
.option('-d, --dataSource <dataSource>', '', 'default')
100-
.description('Generate Cube.js schema from DB tables schema')
100+
.description('Generate Cube schema from DB tables schema')
101101
.action(
102102
(options) => generate(options)
103103
.catch(e => displayError(e.stack || e, { dbType: options.dbType }))

packages/cubejs-cli/src/command/token.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const token = async (options: TokenOptions) => {
6666
}
6767
}
6868

69-
console.log('Generating Cube.js JWT token');
69+
console.log('Generating Cube JWT token');
7070
console.log('');
7171
console.log(`${chalk.yellow('-----------------------------------------------------------------------------------------')}`);
7272
console.log(` ${chalk.yellow('Use these manually generated tokens in production with caution.')}`);
@@ -93,7 +93,7 @@ export function configureTokenCommand(program: CommanderStatic) {
9393
program
9494
.command('token')
9595
.option('-e, --expiry [expiry]', 'Token expiry. Set to 0 for no expiry')
96-
.option('-s, --secret [secret]', 'Cube.js app secret. Also can be set via environment variable CUBEJS_API_SECRET')
96+
.option('-s, --secret [secret]', 'Cube app secret. Also can be set via environment variable CUBEJS_API_SECRET')
9797
.option('-p, --payload [values]', 'Payload. Example: -p foo=bar', collect, [])
9898
.option('-u, --user-context [values]', 'USER_CONTEXT. Example: -u baz=qux', collect, [])
9999
.description('Create JWT token')
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import fs from 'fs-extra';
2+
import path from 'path';
3+
import { CommanderStatic } from 'commander';
4+
import { FileRepository } from '@cubejs-backend/shared';
5+
import { compile } from '@cubejs-backend/schema-compiler';
6+
7+
import { displayError } from '../utils';
8+
9+
async function validate(options) {
10+
const schemaPath = options.schemaPath || 'schema';
11+
12+
if (!fs.existsSync(path.join(process.cwd(), schemaPath))) {
13+
displayError(`Schema path not found at "${path.join(process.cwd(), schemaPath)}". Please run validate command from project directory.`);
14+
return;
15+
}
16+
17+
try {
18+
const repo = new FileRepository(schemaPath);
19+
await compile(repo);
20+
} catch (error: any) {
21+
console.log('❌ Cube Schema validation failed');
22+
displayError(error.messages);
23+
return;
24+
}
25+
26+
console.log('✅ Cube Schema is valid');
27+
}
28+
29+
export function configureValidateCommand(program: CommanderStatic) {
30+
program
31+
.command('validate')
32+
.option(
33+
'-p, --schema-path <schema-path>',
34+
'Path to schema files. Default: schema'
35+
)
36+
.description('Validate Cube schema')
37+
.action(
38+
(options) => validate(options)
39+
.catch(error => displayError(error))
40+
)
41+
.on('--help', () => {
42+
console.log('');
43+
console.log('Examples:');
44+
console.log('');
45+
console.log(' $ cubejs validate');
46+
});
47+
}

packages/cubejs-cli/src/templates.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const indexJs = `const CubejsServer = require('@cubejs-backend/server');
1818
const server = new CubejsServer();
1919
2020
server.listen().then(({ version, port }) => {
21-
console.log(\`🚀 Cube.js server (\${version}) is listening on \${port}\`);
21+
console.log(\`🚀 Cube server (\${version}) is listening on \${port}\`);
2222
}).catch(e => {
2323
console.error('Fatal error during server start: ');
2424
console.error(e.stack || e);
@@ -35,11 +35,11 @@ CUBEJS_API_SECRET=${env.apiSecret}
3535
CUBEJS_EXTERNAL_DEFAULT=true
3636
CUBEJS_SCHEDULED_REFRESH_DEFAULT=true`;
3737

38-
const defaultDotEnvVars = env => `# Cube.js environment variables: https://cube.dev/docs/reference/environment-variables
38+
const defaultDotEnvVars = env => `# Cube environment variables: https://cube.dev/docs/reference/environment-variables
3939
${sharedDotEnvVars(env)}
4040
CUBEJS_WEB_SOCKETS=true`;
4141

42-
const athenaDotEnvVars = env => `# Cube.js environment variables: https://cube.dev/docs/reference/environment-variables
42+
const athenaDotEnvVars = env => `# Cube environment variables: https://cube.dev/docs/reference/environment-variables
4343
CUBEJS_AWS_KEY=<YOUR ATHENA AWS KEY HERE>
4444
CUBEJS_AWS_SECRET=<YOUR ATHENA SECRET KEY HERE>
4545
CUBEJS_AWS_REGION=<AWS REGION STRING, e.g. us-east-1>
@@ -229,7 +229,7 @@ const ordersJs = `cube(\`Orders\`, {
229229
});
230230
`;
231231

232-
const cubeJs = `// Cube.js configuration options: https://cube.dev/docs/config
232+
const cubeJs = `// Cube configuration options: https://cube.dev/docs/config
233233
/** @type{ import('@cubejs-backend/server-core').CreateOptions } */
234234
module.exports = {
235235
};
@@ -244,12 +244,12 @@ services:
244244
ports:
245245
# It's better to use random port binding for 4000/3000 ports
246246
# without it you will not able to start multiple projects inside docker
247-
- 4000:4000 # Cube.js API and Developer Playground
247+
- 4000:4000 # Cube API and Developer Playground
248248
- 3000:3000 # Dashboard app, if created
249249
env_file: .env
250250
volumes:
251251
- .:/cube/conf
252-
# We ignore Cube.js deps, because they are built-in inside the official Docker image
252+
# We ignore Cube deps, because they are built-in inside the official Docker image
253253
- .empty:/cube/conf/node_modules/@cubejs-backend/
254254
`;
255255

packages/cubejs-cli/src/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export async function event(opts: BaseEvent) {
5151

5252
export const displayError = async (text: string | string[], options = {}) => {
5353
console.error('');
54-
console.error(chalk.cyan('Cube.js Error ---------------------------------------'));
54+
console.error(chalk.cyan('Cube Error ---------------------------------------'));
5555
console.error('');
5656

5757
if (Array.isArray(text)) {
@@ -70,7 +70,7 @@ export const displayError = async (text: string | string[], options = {}) => {
7070
});
7171

7272
console.error('');
73-
console.error(`${chalk.yellow(' Ask this question in Cube.js Slack:')} https://slack.cube.dev`);
73+
console.error(`${chalk.yellow(' Ask this question in Cube Slack:')} https://slack.cube.dev`);
7474
console.error(`${chalk.yellow(' Post an issue:')} https://github.com/cube-js/cube.js/issues`);
7575
console.error('');
7676

0 commit comments

Comments
 (0)