Skip to content

Commit 4fe27ef

Browse files
jiashengguoymc9
andauthored
chore(cli): show notifications for generate command (#2409)
Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com>
1 parent 75bc4a1 commit 4fe27ef

File tree

6 files changed

+137
-9
lines changed

6 files changed

+137
-9
lines changed

packages/cli/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
},
3838
"dependencies": {
3939
"@zenstackhq/common-helpers": "workspace:*",
40-
"@zenstackhq/schema": "workspace:*",
4140
"@zenstackhq/language": "workspace:*",
4241
"@zenstackhq/orm": "workspace:*",
42+
"@zenstackhq/schema": "workspace:*",
4343
"@zenstackhq/sdk": "workspace:*",
4444
"@zenstackhq/server": "workspace:*",
4545
"chokidar": "^5.0.0",
@@ -56,7 +56,9 @@
5656
"package-manager-detector": "^1.3.0",
5757
"prisma": "catalog:",
5858
"semver": "^7.7.2",
59-
"ts-pattern": "catalog:"
59+
"terminal-link": "^5.0.0",
60+
"ts-pattern": "catalog:",
61+
"zod": "catalog:"
6062
},
6163
"devDependencies": {
6264
"@types/better-sqlite3": "catalog:",
@@ -72,9 +74,9 @@
7274
"tmp": "catalog:"
7375
},
7476
"peerDependencies": {
75-
"pg": "catalog:",
7677
"better-sqlite3": "catalog:",
77-
"mysql2": "catalog:"
78+
"mysql2": "catalog:",
79+
"pg": "catalog:"
7880
},
7981
"peerDependenciesMeta": {
8082
"pg": {

packages/cli/src/actions/action-utils.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import fs from 'node:fs';
66
import { createRequire } from 'node:module';
77
import path from 'node:path';
88
import { CliError } from '../cli-error';
9+
import terminalLink from 'terminal-link';
10+
import { z } from 'zod';
911

1012
export function getSchemaFile(file?: string) {
1113
if (file) {
@@ -216,3 +218,69 @@ export async function getZenStackPackages(
216218

217219
return result.filter((p) => !!p);
218220
}
221+
222+
const FETCH_CLI_MAX_TIME = 1000;
223+
const CLI_CONFIG_ENDPOINT = 'https://zenstack.dev/config/cli-v3.json';
224+
225+
const usageTipsSchema = z.object({
226+
notifications: z.array(z.object({ title: z.string(), url: z.url().optional(), active: z.boolean() })),
227+
});
228+
229+
/**
230+
* Starts the usage tips fetch in the background. Returns a callback that, when invoked check if the fetch
231+
* is complete. If not complete, it will wait until the max time is reached. After that, if fetch is still
232+
* not complete, just return.
233+
*/
234+
export function startUsageTipsFetch() {
235+
let fetchedData: z.infer<typeof usageTipsSchema> | undefined = undefined;
236+
let fetchComplete = false;
237+
238+
const start = Date.now();
239+
const controller = new AbortController();
240+
241+
fetch(CLI_CONFIG_ENDPOINT, {
242+
headers: { accept: 'application/json' },
243+
signal: controller.signal,
244+
})
245+
.then(async (res) => {
246+
if (!res.ok) return;
247+
const data = await res.json();
248+
const parseResult = usageTipsSchema.safeParse(data);
249+
if (parseResult.success) {
250+
fetchedData = parseResult.data;
251+
}
252+
})
253+
.catch(() => {
254+
// noop
255+
})
256+
.finally(() => {
257+
fetchComplete = true;
258+
});
259+
260+
return async () => {
261+
const elapsed = Date.now() - start;
262+
263+
if (!fetchComplete && elapsed < FETCH_CLI_MAX_TIME) {
264+
// wait for the timeout
265+
await new Promise((resolve) => setTimeout(resolve, FETCH_CLI_MAX_TIME - elapsed));
266+
}
267+
268+
if (!fetchComplete) {
269+
controller.abort();
270+
return;
271+
}
272+
273+
if (!fetchedData) return;
274+
275+
const activeItems = fetchedData.notifications.filter((item) => item.active);
276+
// show a random active item
277+
if (activeItems.length > 0) {
278+
const item = activeItems[Math.floor(Math.random() * activeItems.length)]!;
279+
if (item.url) {
280+
console.log(terminalLink(item.title, item.url));
281+
} else {
282+
console.log(item.title);
283+
}
284+
}
285+
};
286+
}

packages/cli/src/actions/generate.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import { invariant, singleDebounce } from '@zenstackhq/common-helpers';
22
import { ZModelLanguageMetaData } from '@zenstackhq/language';
3-
import { type AbstractDeclaration, isPlugin, LiteralExpr, Plugin, type Model } from '@zenstackhq/language/ast';
3+
import { isPlugin, LiteralExpr, Plugin, type AbstractDeclaration, type Model } from '@zenstackhq/language/ast';
44
import { getLiteral, getLiteralArray } from '@zenstackhq/language/utils';
55
import { type CliPlugin } from '@zenstackhq/sdk';
6+
import { watch } from 'chokidar';
67
import colors from 'colors';
78
import { createJiti } from 'jiti';
89
import fs from 'node:fs';
910
import path from 'node:path';
1011
import { pathToFileURL } from 'node:url';
11-
import { watch } from 'chokidar';
1212
import ora, { type Ora } from 'ora';
13+
import semver from 'semver';
1314
import { CliError } from '../cli-error';
1415
import * as corePlugins from '../plugins';
15-
import { getOutputPath, getSchemaFile, getZenStackPackages, loadSchemaDocument } from './action-utils';
16-
import semver from 'semver';
16+
import {
17+
getOutputPath,
18+
getSchemaFile,
19+
getZenStackPackages,
20+
loadSchemaDocument,
21+
startUsageTipsFetch,
22+
} from './action-utils';
1723

1824
type Options = {
1925
schema?: string;
@@ -24,6 +30,7 @@ type Options = {
2430
liteOnly?: boolean;
2531
generateModels?: boolean;
2632
generateInput?: boolean;
33+
tips?: boolean;
2734
};
2835

2936
/**
@@ -35,8 +42,13 @@ export async function run(options: Options) {
3542
} catch (err) {
3643
console.warn(colors.yellow(`Failed to check for mismatched ZenStack packages: ${err}`));
3744
}
45+
46+
const maybeShowUsageTips = options.tips && !options.silent && !options.watch ? startUsageTipsFetch() : undefined;
47+
3848
const model = await pureGenerate(options, false);
3949

50+
await maybeShowUsageTips?.();
51+
4052
if (options.watch) {
4153
const logsEnabled = !options.silent;
4254

packages/cli/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,14 @@ function createProgram() {
7474
);
7575

7676
const noVersionCheckOption = new Option('--no-version-check', 'do not check for new version');
77+
const noTipsOption = new Option('--no-tips', 'do not show usage tips');
7778

7879
program
7980
.command('generate')
8081
.description('Run code generation plugins')
8182
.addOption(schemaOption)
8283
.addOption(noVersionCheckOption)
84+
.addOption(noTipsOption)
8385
.addOption(new Option('-o, --output <path>', 'default output directory for code generation'))
8486
.addOption(new Option('-w, --watch', 'enable watch mode').default(false))
8587
.addOption(

pnpm-lock.yaml

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/test-generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async function generate(schemaPath: string, options: string[]) {
2222
const cliPath = path.join(_dirname, '../packages/cli/dist/index.js');
2323
const RUNTIME = process.env.RUNTIME ?? 'node';
2424
execSync(
25-
`${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false`,
25+
`${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false --no-version-check --no-tips`,
2626
{
2727
cwd: path.dirname(schemaPath),
2828
},

0 commit comments

Comments
 (0)