@dsz-examaware/plugin-demo 0.1.1

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.
@@ -0,0 +1,2 @@
1
+ import type { PluginRuntimeContext } from '@dsz-examaware/plugin-sdk';
2
+ export default function setupRenderer(ctx: PluginRuntimeContext): Promise<void>;
package/env.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue';
5
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, any>;
6
+ export default component;
7
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@dsz-examaware/plugin-demo",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/main/index.cjs",
7
+ "module": "./dist/renderer/index.mjs",
8
+ "types": "./dist/main/index.d.ts",
9
+ "examaware": {
10
+ "displayName": "ExamAware Demo Plugin",
11
+ "description": "A starter plugin showing main + renderer wiring with a settings page.",
12
+ "targets": {
13
+ "main": "./dist/main/index.cjs",
14
+ "renderer": "./dist/renderer/index.mjs"
15
+ },
16
+ "services": {
17
+ "provide": [
18
+ "hello.message"
19
+ ],
20
+ "inject": []
21
+ },
22
+ "settings": {
23
+ "namespace": "examaware-plugin-template",
24
+ "schema": "./schema.json"
25
+ }
26
+ },
27
+ "dependencies": {
28
+ "vue": "^3.5.26",
29
+ "@dsz-examaware/plugin-sdk": "^1.1.1"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.19.27",
33
+ "@vitejs/plugin-vue": "^5.2.4",
34
+ "npm-run-all2": "^7.0.2",
35
+ "run-p": "0.0.0",
36
+ "typescript": "~5.7.3",
37
+ "vite": "^6.4.1",
38
+ "vue-tsc": "^2.2.12"
39
+ },
40
+ "scripts": {
41
+ "dev": "run-p dev:renderer dev:main",
42
+ "dev:renderer": "vite build --watch --mode development --config vite.config.ts",
43
+ "dev:main": "vite build --watch --mode development --config vite.main.config.ts",
44
+ "build": "pnpm run build:renderer && pnpm run build:main && pnpm run build:types",
45
+ "build:renderer": "vite build --config vite.config.ts",
46
+ "build:main": "vite build --config vite.main.config.ts",
47
+ "build:types": "vue-tsc --declaration --emitDeclarationOnly --outDir dist",
48
+ "pack": "pnpm run build && pack-examaware-plugin",
49
+ "lint": "vue-tsc --noEmit"
50
+ }
51
+ }
package/schema.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "demo": {
5
+ "type": "object",
6
+ "properties": {
7
+ "clicks": {
8
+ "type": "integer",
9
+ "minimum": 0,
10
+ "default": 0,
11
+ "description": "Number of times the demo button was clicked."
12
+ },
13
+ "message": {
14
+ "type": "string",
15
+ "default": "Hello from ExamAware Demo Plugin",
16
+ "description": "Message shown in main-process heartbeat logs."
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,54 @@
1
+ import { defineExamAwarePlugin } from '@dsz-examaware/plugin-sdk';
2
+ import type { HostedService } from '@dsz-examaware/plugin-sdk';
3
+
4
+ interface HelloMessage {
5
+ text: string;
6
+ timestamp: number;
7
+ }
8
+
9
+ class HeartbeatService implements HostedService {
10
+ constructor(private readonly message: HelloMessage) {}
11
+
12
+ private interval?: ReturnType<typeof setInterval>;
13
+
14
+ async start() {
15
+ this.interval = setInterval(() => {
16
+ console.info('[examaware-plugin-template]', this.message.text, new Date().toISOString());
17
+ }, 10_000);
18
+ }
19
+
20
+ async stop() {
21
+ if (this.interval) clearInterval(this.interval);
22
+ }
23
+ }
24
+
25
+ export default defineExamAwarePlugin((builder) => {
26
+ builder.configureServices((context, services) => {
27
+ services.addSingleton('hello.message', () => ({
28
+ text: context.ctx.config?.message ?? 'Hello from ExamAware Demo Plugin1',
29
+ timestamp: Date.now()
30
+ }));
31
+
32
+ // Register both class and string tokens to avoid identity mismatches
33
+ services.addSingleton(HeartbeatService, (sp) => new HeartbeatService(sp.get('hello.message')));
34
+ services.tryAddSingleton('heartbeat.service', (sp) => sp.get(HeartbeatService));
35
+ context.ctx.logger.info(
36
+ '[examaware-plugin-template] registered HeartbeatService/heartbeat.service'
37
+ );
38
+ });
39
+
40
+ builder.exposeHostService('hello.message', { token: 'hello.message' });
41
+ builder.addHostedService('heartbeat.service');
42
+
43
+ builder.use(async ({ ctx }, next) => {
44
+ ctx.logger.info('Plugin boot sequence started');
45
+ await next();
46
+ ctx.logger.info('Plugin boot sequence completed');
47
+ });
48
+
49
+ builder.configure((host, app) => {
50
+ host.lifetime.onStarted(() => {
51
+ app.ctx.logger.info('Host lifetime onStarted hook invoked');
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <p>插件设置页面</p>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import type { PluginRuntimeContext } from '@dsz-examaware/plugin-sdk';
7
+
8
+ const props = defineProps<{ ctx: PluginRuntimeContext }>();
9
+ </script>
@@ -0,0 +1,34 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import type { PluginRuntimeContext } from '@dsz-examaware/plugin-sdk';
3
+ import PluginSettingsPage from './components/PluginSettingsPage.vue';
4
+
5
+ const SETTINGS_PAGE_ID = 'examaware-plugin-template-settings';
6
+
7
+ export default async function setupRenderer(ctx: PluginRuntimeContext) {
8
+ if (ctx.app !== 'renderer') return;
9
+ const desktopApi = ctx.desktopApi as any;
10
+ const settingsUi = desktopApi?.ui?.settings;
11
+ if (!settingsUi) {
12
+ ctx.logger.warn('Desktop API does not expose settings UI in this build.');
13
+ return;
14
+ }
15
+
16
+ const SettingsEntry = defineComponent({
17
+ name: 'ExamAwarePluginSettingsEntry',
18
+ setup() {
19
+ return () => h(PluginSettingsPage, { ctx });
20
+ }
21
+ });
22
+
23
+ const handle = await settingsUi.registerPage({
24
+ id: SETTINGS_PAGE_ID,
25
+ label: 'Plugin Settings',
26
+ icon: 'extension',
27
+ order: 50,
28
+ component: () => Promise.resolve(SettingsEntry)
29
+ });
30
+
31
+ if (handle) {
32
+ ctx.effect(() => () => handle.dispose());
33
+ }
34
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "types": ["node", "vite/client"]
12
+ },
13
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue", "env.d.ts"],
14
+ "references": [{ "path": "./tsconfig.node.json" }]
15
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true,
7
+ "types": ["node"]
8
+ },
9
+ "include": ["vite.config.ts", "vite.main.config.ts"]
10
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'vite';
3
+ import type { ResolvedConfig } from 'vite';
4
+ import vue from '@vitejs/plugin-vue';
5
+
6
+ const inlineAllDeps = () => ({
7
+ name: 'inline-all-deps',
8
+ configResolved(config: ResolvedConfig) {
9
+ if (!config?.build?.rollupOptions) return;
10
+ config.build.rollupOptions.external = undefined;
11
+ }
12
+ });
13
+
14
+ export default defineConfig(({ mode }) => {
15
+ const isProd = mode === 'production';
16
+
17
+ return {
18
+ define: {
19
+ __DEV__: !isProd,
20
+ 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development'),
21
+ 'process.env': JSON.stringify({ NODE_ENV: isProd ? 'production' : 'development' }),
22
+ process: JSON.stringify({ env: { NODE_ENV: isProd ? 'production' : 'development' } })
23
+ },
24
+ plugins: [vue(), inlineAllDeps()],
25
+ base: './',
26
+ build: {
27
+ emptyOutDir: false,
28
+ outDir: 'dist/renderer',
29
+ sourcemap: !isProd,
30
+ minify: isProd ? 'esbuild' : false,
31
+ cssCodeSplit: false,
32
+ target: 'esnext',
33
+ watch: isProd ? undefined : {},
34
+ lib: {
35
+ entry: resolve(__dirname, 'src/renderer/main.ts'),
36
+ fileName: () => 'index.mjs',
37
+ formats: ['es'],
38
+ name: 'ExamAwareRendererPlugin'
39
+ },
40
+ rollupOptions: {
41
+ output: {
42
+ format: 'es',
43
+ entryFileNames: 'index.mjs',
44
+ inlineDynamicImports: true,
45
+ chunkFileNames: 'chunks/[name].js',
46
+ assetFileNames: 'assets/[name][extname]'
47
+ }
48
+ }
49
+ }
50
+ };
51
+ });
@@ -0,0 +1,37 @@
1
+ import { builtinModules } from 'node:module';
2
+ import { resolve } from 'node:path';
3
+ import { defineConfig } from 'vite';
4
+
5
+ const builtins = new Set<string>([
6
+ ...builtinModules,
7
+ ...builtinModules.map((mod) => `node:${mod}`)
8
+ ]);
9
+ // Bundle plugin-sdk; only externalize Electron + Node builtins so the plugin is self-contained.
10
+ const externalDeps = ['electron', 'electron/main', ...builtins];
11
+
12
+ export default defineConfig(({ mode }) => {
13
+ const isProd = mode === 'production';
14
+
15
+ return {
16
+ build: {
17
+ lib: {
18
+ entry: resolve(__dirname, 'src/main/index.ts'),
19
+ fileName: () => 'index.cjs',
20
+ formats: ['cjs']
21
+ },
22
+ emptyOutDir: false,
23
+ outDir: 'dist/main',
24
+ target: 'node20',
25
+ sourcemap: !isProd,
26
+ minify: false,
27
+ rollupOptions: {
28
+ external: (source) =>
29
+ externalDeps.some((dep) => source === dep || source.startsWith(`${dep}/`)),
30
+ output: {
31
+ exports: 'default',
32
+ esModule: false
33
+ }
34
+ }
35
+ }
36
+ };
37
+ });