@gtkx/cli 1.3.0 → 1.5.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/README.md +5 -4
- package/dist/builder.d.ts +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +6 -6
- package/dist/builder.js.map +1 -1
- package/dist/codegen/run-codegen.d.ts +3 -2
- package/dist/codegen/run-codegen.d.ts.map +1 -1
- package/dist/codegen/run-codegen.js +41 -12
- package/dist/codegen/run-codegen.js.map +1 -1
- package/dist/commands/build.d.ts +0 -4
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +0 -5
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/codegen.d.ts.map +1 -1
- package/dist/commands/codegen.js +2 -4
- package/dist/commands/codegen.js.map +1 -1
- package/dist/deploy/depends.d.ts +2 -2
- package/dist/deploy/depends.d.ts.map +1 -1
- package/dist/deploy/depends.js +30 -15
- package/dist/deploy/depends.js.map +1 -1
- package/dist/deploy/freedesktop/localize.d.ts +6 -0
- package/dist/deploy/freedesktop/localize.d.ts.map +1 -0
- package/dist/deploy/freedesktop/localize.js +107 -0
- package/dist/deploy/freedesktop/localize.js.map +1 -0
- package/dist/deploy/install-hints.d.ts.map +1 -1
- package/dist/deploy/install-hints.js +5 -0
- package/dist/deploy/install-hints.js.map +1 -1
- package/dist/deploy/nfpm/config.js +1 -1
- package/dist/deploy/nfpm/config.js.map +1 -1
- package/dist/deploy/node-runtime/elf.d.ts +1 -1
- package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
- package/dist/deploy/node-runtime/elf.js +2 -2
- package/dist/deploy/node-runtime/elf.js.map +1 -1
- package/dist/deploy/node-runtime/index.js +2 -2
- package/dist/deploy/node-runtime/index.js.map +1 -1
- package/dist/deploy/notices/collect.d.ts +3 -1
- package/dist/deploy/notices/collect.d.ts.map +1 -1
- package/dist/deploy/notices/collect.js +2 -2
- package/dist/deploy/notices/collect.js.map +1 -1
- package/dist/deploy/notices/packages.d.ts +2 -1
- package/dist/deploy/notices/packages.d.ts.map +1 -1
- package/dist/deploy/notices/packages.js +4 -27
- package/dist/deploy/notices/packages.js.map +1 -1
- package/dist/deploy/payload/icons.d.ts +7 -3
- package/dist/deploy/payload/icons.d.ts.map +1 -1
- package/dist/deploy/payload/icons.js +46 -28
- package/dist/deploy/payload/icons.js.map +1 -1
- package/dist/deploy/payload/launcher.d.ts.map +1 -1
- package/dist/deploy/payload/launcher.js +2 -0
- package/dist/deploy/payload/launcher.js.map +1 -1
- package/dist/deploy/payload/stage.d.ts +1 -1
- package/dist/deploy/payload/stage.d.ts.map +1 -1
- package/dist/deploy/payload/stage.js +23 -13
- package/dist/deploy/payload/stage.js.map +1 -1
- package/dist/deploy/run-deploy.d.ts.map +1 -1
- package/dist/deploy/run-deploy.js +149 -25
- package/dist/deploy/run-deploy.js.map +1 -1
- package/dist/deploy/settings/build-manifest.d.ts +9 -0
- package/dist/deploy/settings/build-manifest.d.ts.map +1 -0
- package/dist/deploy/settings/build-manifest.js +80 -0
- package/dist/deploy/settings/build-manifest.js.map +1 -0
- package/dist/deploy/settings/index.d.ts.map +1 -1
- package/dist/deploy/settings/index.js +9 -3
- package/dist/deploy/settings/index.js.map +1 -1
- package/dist/deploy/settings/libraries.d.ts +8 -0
- package/dist/deploy/settings/libraries.d.ts.map +1 -0
- package/dist/deploy/settings/libraries.js +39 -0
- package/dist/deploy/settings/libraries.js.map +1 -0
- package/dist/deploy/settings/paths.d.ts +3 -0
- package/dist/deploy/settings/paths.d.ts.map +1 -1
- package/dist/deploy/settings/paths.js +6 -32
- package/dist/deploy/settings/paths.js.map +1 -1
- package/dist/deploy/targets/appimage.d.ts.map +1 -1
- package/dist/deploy/targets/appimage.js +19 -13
- package/dist/deploy/targets/appimage.js.map +1 -1
- package/dist/deploy/targets/flatpak-manifest.d.ts +2 -1
- package/dist/deploy/targets/flatpak-manifest.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-manifest.js +5 -1
- package/dist/deploy/targets/flatpak-manifest.js.map +1 -1
- package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-source.js +30 -16
- package/dist/deploy/targets/flatpak-source.js.map +1 -1
- package/dist/deploy/tools.d.ts +6 -1
- package/dist/deploy/tools.d.ts.map +1 -1
- package/dist/deploy/tools.js +26 -1
- package/dist/deploy/tools.js.map +1 -1
- package/dist/deploy/types.d.ts +4 -4
- package/dist/deploy/types.d.ts.map +1 -1
- package/dist/deploy/types.js.map +1 -1
- package/dist/dev/icon-dir.d.ts +1 -1
- package/dist/dev/icon-dir.d.ts.map +1 -1
- package/dist/dev/icon-dir.js +20 -7
- package/dist/dev/icon-dir.js.map +1 -1
- package/dist/dev/locale-dir.d.ts +3 -0
- package/dist/dev/locale-dir.d.ts.map +1 -0
- package/dist/dev/locale-dir.js +17 -0
- package/dist/dev/locale-dir.js.map +1 -0
- package/dist/dev/runner-deps.d.ts.map +1 -1
- package/dist/dev/runner-deps.js +5 -1
- package/dist/dev/runner-deps.js.map +1 -1
- package/dist/dev/runner-main.d.ts.map +1 -1
- package/dist/dev/runner-main.js +6 -4
- package/dist/dev/runner-main.js.map +1 -1
- package/dist/dev/runner.d.ts +1 -1
- package/dist/dev/runner.d.ts.map +1 -1
- package/dist/dev/runner.js +7 -1
- package/dist/dev/runner.js.map +1 -1
- package/dist/dev/schema-dir.d.ts +1 -1
- package/dist/dev/schema-dir.d.ts.map +1 -1
- package/dist/dev/schema-dir.js +3 -1
- package/dist/dev/schema-dir.js.map +1 -1
- package/dist/dev/vite-dev-server.d.ts +1 -0
- package/dist/dev/vite-dev-server.d.ts.map +1 -1
- package/dist/dev/vite-dev-server.js.map +1 -1
- package/dist/i18n/binding-extractor.d.ts +30 -0
- package/dist/i18n/binding-extractor.d.ts.map +1 -0
- package/dist/i18n/binding-extractor.js +1324 -0
- package/dist/i18n/binding-extractor.js.map +1 -0
- package/dist/i18n/catalog-template.d.ts +3 -0
- package/dist/i18n/catalog-template.d.ts.map +1 -0
- package/dist/i18n/catalog-template.js +18 -0
- package/dist/i18n/catalog-template.js.map +1 -0
- package/dist/i18n/catalogs.d.ts +21 -0
- package/dist/i18n/catalogs.d.ts.map +1 -0
- package/dist/i18n/catalogs.js +139 -0
- package/dist/i18n/catalogs.js.map +1 -0
- package/dist/i18n/extractor-plugin.d.ts +8 -0
- package/dist/i18n/extractor-plugin.d.ts.map +1 -0
- package/dist/i18n/extractor-plugin.js +247 -0
- package/dist/i18n/extractor-plugin.js.map +1 -0
- package/dist/i18n/metadata-templates.d.ts +11 -0
- package/dist/i18n/metadata-templates.d.ts.map +1 -0
- package/dist/i18n/metadata-templates.js +11 -0
- package/dist/i18n/metadata-templates.js.map +1 -0
- package/dist/i18n/source-messages.d.ts +23 -0
- package/dist/i18n/source-messages.d.ts.map +1 -0
- package/dist/i18n/source-messages.js +403 -0
- package/dist/i18n/source-messages.js.map +1 -0
- package/dist/i18n/types.d.ts +10 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +108 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/internal/build-manifest.d.ts +20 -0
- package/dist/internal/build-manifest.d.ts.map +1 -0
- package/dist/internal/build-manifest.js +6 -0
- package/dist/internal/build-manifest.js.map +1 -0
- package/dist/internal/icon-path.d.ts +13 -0
- package/dist/internal/icon-path.d.ts.map +1 -0
- package/dist/internal/icon-path.js +86 -0
- package/dist/internal/icon-path.js.map +1 -0
- package/dist/internal/source-imports.d.ts +12 -0
- package/dist/internal/source-imports.d.ts.map +1 -0
- package/dist/internal/source-imports.js +123 -0
- package/dist/internal/source-imports.js.map +1 -0
- package/dist/mcp/client.d.ts +0 -1
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +11 -39
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/handlers.d.ts +2 -1
- package/dist/mcp/handlers.d.ts.map +1 -1
- package/dist/mcp/handlers.js.map +1 -1
- package/dist/mcp/widget-properties.d.ts.map +1 -1
- package/dist/mcp/widget-properties.js +3 -12
- package/dist/mcp/widget-properties.js.map +1 -1
- package/dist/settings/render.d.ts +10 -1
- package/dist/settings/render.d.ts.map +1 -1
- package/dist/settings/render.js +78 -20
- package/dist/settings/render.js.map +1 -1
- package/dist/settings/schema.d.ts +4 -3
- package/dist/settings/schema.d.ts.map +1 -1
- package/dist/settings/schema.js +124 -15
- package/dist/settings/schema.js.map +1 -1
- package/dist/vite-plugins/asset-emitter.d.ts +1 -1
- package/dist/vite-plugins/asset-emitter.d.ts.map +1 -1
- package/dist/vite-plugins/asset-emitter.js.map +1 -1
- package/dist/vite-plugins/asset-extensions.d.ts.map +1 -1
- package/dist/vite-plugins/asset-extensions.js +1 -0
- package/dist/vite-plugins/asset-extensions.js.map +1 -1
- package/dist/vite-plugins/asset-imports.d.ts +2 -1
- package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
- package/dist/vite-plugins/asset-imports.js +39 -28
- package/dist/vite-plugins/asset-imports.js.map +1 -1
- package/dist/vite-plugins/asset-specifier.d.ts +13 -1
- package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
- package/dist/vite-plugins/asset-specifier.js +39 -1
- package/dist/vite-plugins/asset-specifier.js.map +1 -1
- package/dist/vite-plugins/build-manifest.d.ts +5 -0
- package/dist/vite-plugins/build-manifest.d.ts.map +1 -0
- package/dist/vite-plugins/{bundled-packages.js → build-manifest.js} +20 -14
- package/dist/vite-plugins/build-manifest.js.map +1 -0
- package/dist/vite-plugins/built-url.d.ts +1 -1
- package/dist/vite-plugins/built-url.d.ts.map +1 -1
- package/dist/vite-plugins/built-url.js +37 -24
- package/dist/vite-plugins/built-url.js.map +1 -1
- package/dist/vite-plugins/i18n.d.ts +5 -0
- package/dist/vite-plugins/i18n.d.ts.map +1 -0
- package/dist/vite-plugins/i18n.js +88 -0
- package/dist/vite-plugins/i18n.js.map +1 -0
- package/dist/vite-plugins/icons.d.ts +2 -1
- package/dist/vite-plugins/icons.d.ts.map +1 -1
- package/dist/vite-plugins/icons.js +25 -12
- package/dist/vite-plugins/icons.js.map +1 -1
- package/dist/vite-plugins/index.d.ts +2 -1
- package/dist/vite-plugins/index.d.ts.map +1 -1
- package/dist/vite-plugins/index.js +9 -5
- package/dist/vite-plugins/index.js.map +1 -1
- package/dist/vite-plugins/native.d.ts.map +1 -1
- package/dist/vite-plugins/native.js +23 -4
- package/dist/vite-plugins/native.js.map +1 -1
- package/dist/vite-plugins/resource-init-module.d.ts +7 -3
- package/dist/vite-plugins/resource-init-module.d.ts.map +1 -1
- package/dist/vite-plugins/resource-init-module.js +39 -14
- package/dist/vite-plugins/resource-init-module.js.map +1 -1
- package/dist/vite-plugins/resource-shared.d.ts +3 -1
- package/dist/vite-plugins/resource-shared.d.ts.map +1 -1
- package/dist/vite-plugins/resource-shared.js +3 -1
- package/dist/vite-plugins/resource-shared.js.map +1 -1
- package/dist/vite-plugins/resources.d.ts +1 -1
- package/dist/vite-plugins/resources.d.ts.map +1 -1
- package/dist/vite-plugins/resources.js +877 -86
- package/dist/vite-plugins/resources.js.map +1 -1
- package/dist/vite-plugins/self-contained.d.ts.map +1 -1
- package/dist/vite-plugins/self-contained.js +128 -135
- package/dist/vite-plugins/self-contained.js.map +1 -1
- package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
- package/dist/vite-plugins/settings-worker-env.js +6 -3
- package/dist/vite-plugins/settings-worker-env.js.map +1 -1
- package/dist/vite-plugins/settings.d.ts +3 -1
- package/dist/vite-plugins/settings.d.ts.map +1 -1
- package/dist/vite-plugins/settings.js +63 -23
- package/dist/vite-plugins/settings.js.map +1 -1
- package/env.d.ts +11 -192
- package/package.json +11 -10
- package/src/builder.ts +7 -7
- package/src/codegen/run-codegen.ts +64 -15
- package/src/commands/build.ts +0 -5
- package/src/commands/codegen.ts +2 -4
- package/src/deploy/depends.ts +47 -18
- package/src/deploy/freedesktop/localize.ts +144 -0
- package/src/deploy/install-hints.ts +5 -0
- package/src/deploy/nfpm/config.ts +1 -1
- package/src/deploy/node-runtime/elf.ts +3 -3
- package/src/deploy/node-runtime/index.ts +2 -2
- package/src/deploy/notices/collect.ts +4 -2
- package/src/deploy/notices/packages.ts +5 -43
- package/src/deploy/payload/icons.ts +64 -34
- package/src/deploy/payload/launcher.ts +2 -0
- package/src/deploy/payload/stage.ts +40 -12
- package/src/deploy/run-deploy.ts +214 -24
- package/src/deploy/settings/build-manifest.ts +122 -0
- package/src/deploy/settings/index.ts +9 -4
- package/src/deploy/settings/libraries.ts +57 -0
- package/src/deploy/settings/paths.ts +10 -47
- package/src/deploy/targets/appimage.ts +32 -15
- package/src/deploy/targets/flatpak-manifest.ts +7 -1
- package/src/deploy/targets/flatpak-source.ts +42 -20
- package/src/deploy/tools.ts +35 -0
- package/src/deploy/types.ts +4 -4
- package/src/dev/icon-dir.ts +31 -7
- package/src/dev/locale-dir.ts +22 -0
- package/src/dev/runner-deps.ts +5 -1
- package/src/dev/runner-main.ts +6 -4
- package/src/dev/runner.ts +10 -2
- package/src/dev/schema-dir.ts +3 -1
- package/src/dev/vite-dev-server.ts +1 -0
- package/src/i18n/binding-extractor.ts +1969 -0
- package/src/i18n/catalog-template.ts +24 -0
- package/src/i18n/catalogs.ts +216 -0
- package/src/i18n/extractor-plugin.ts +413 -0
- package/src/i18n/metadata-templates.ts +29 -0
- package/src/i18n/source-messages.ts +608 -0
- package/src/i18n/types.ts +154 -0
- package/src/internal/build-manifest.ts +32 -0
- package/src/internal/icon-path.ts +127 -0
- package/src/internal/source-imports.ts +171 -0
- package/src/mcp/client.ts +17 -44
- package/src/mcp/handlers.ts +7 -6
- package/src/mcp/widget-properties.ts +3 -14
- package/src/settings/render.ts +113 -21
- package/src/settings/schema.ts +192 -17
- package/src/vite-plugins/asset-emitter.ts +1 -1
- package/src/vite-plugins/asset-extensions.ts +1 -0
- package/src/vite-plugins/asset-imports.ts +62 -33
- package/src/vite-plugins/asset-specifier.ts +73 -1
- package/src/vite-plugins/{bundled-packages.ts → build-manifest.ts} +30 -19
- package/src/vite-plugins/built-url.ts +49 -28
- package/src/vite-plugins/i18n.ts +146 -0
- package/src/vite-plugins/icons.ts +33 -14
- package/src/vite-plugins/index.ts +15 -5
- package/src/vite-plugins/native.ts +31 -4
- package/src/vite-plugins/resource-init-module.ts +44 -15
- package/src/vite-plugins/resource-shared.ts +4 -0
- package/src/vite-plugins/resources.ts +1397 -100
- package/src/vite-plugins/self-contained.ts +150 -169
- package/src/vite-plugins/settings-worker-env.ts +7 -3
- package/src/vite-plugins/settings.ts +89 -21
- package/dist/vite-plugins/bundled-packages.d.ts +0 -5
- package/dist/vite-plugins/bundled-packages.d.ts.map +0 -1
- package/dist/vite-plugins/bundled-packages.js.map +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import type { SourceMessage } from "./source-messages.js";
|
|
4
|
+
|
|
5
|
+
type I18nTypesResult = {
|
|
6
|
+
isWritten: boolean;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const I18N_TYPES_FILENAME = "i18n.d.ts";
|
|
11
|
+
const INTERPOLATION_PATTERN = /\{\{[^{}]+\}\}/u;
|
|
12
|
+
|
|
13
|
+
const stringType = (values: string[]): string =>
|
|
14
|
+
[...new Set(values)].map((value) => JSON.stringify(value)).join(" | ");
|
|
15
|
+
|
|
16
|
+
const contextField = (context: string | null): string =>
|
|
17
|
+
context === null ? "" : ` context: ${JSON.stringify(context)};`;
|
|
18
|
+
|
|
19
|
+
const hasInterpolation = (values: string[]): boolean => values.some((value) => INTERPOLATION_PATTERN.test(value));
|
|
20
|
+
|
|
21
|
+
const pointVariant = (message: SourceMessage): string => {
|
|
22
|
+
const isRequired = message.context !== null || hasInterpolation([message.msgid]);
|
|
23
|
+
const isInterpolated = hasInterpolation([message.msgid]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
`{${contextField(message.context)} interpolated: ${String(isInterpolated)}; kind: "point"; ` +
|
|
27
|
+
`required: ${String(isRequired)}; value: ${JSON.stringify(message.msgid)} }`
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const defaultVariant = (message: SourceMessage): string => {
|
|
32
|
+
const values = [message.singular];
|
|
33
|
+
const isInterpolated = hasInterpolation(values);
|
|
34
|
+
const isRequired = message.context !== null || isInterpolated;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
`{${contextField(message.context)} defaultValue: ${JSON.stringify(message.singular)}; ` +
|
|
38
|
+
`interpolated: ${String(isInterpolated)}; kind: "default"; required: ${String(isRequired)}; ` +
|
|
39
|
+
`value: ${stringType(values)} }`
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const requirePlural = (message: SourceMessage): string => {
|
|
44
|
+
if (message.plural === null) {
|
|
45
|
+
throw new Error("Plural translation metadata requires a plural source string");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return message.plural;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const pluralVariant = (message: SourceMessage): string => {
|
|
52
|
+
const plural = requirePlural(message);
|
|
53
|
+
const values = [message.singular, plural];
|
|
54
|
+
const isInterpolated = hasInterpolation(values);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
`{${contextField(message.context)} defaultValue: ${JSON.stringify(plural)}; ` +
|
|
58
|
+
`interpolated: ${String(isInterpolated)}; kind: "plural"; required: true; ` +
|
|
59
|
+
`value: ${stringType(values)} }`
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const pluralDefaultsVariant = (message: SourceMessage): string => {
|
|
64
|
+
const plural = requirePlural(message);
|
|
65
|
+
const values = [message.singular, plural];
|
|
66
|
+
const isInterpolated = hasInterpolation(values);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
`{${contextField(message.context)} defaultValueOne: ${JSON.stringify(message.singular)}; ` +
|
|
70
|
+
`defaultValueOther: ${JSON.stringify(plural)}; interpolated: ${String(isInterpolated)}; ` +
|
|
71
|
+
`kind: "pluralDefaults"; required: true; value: ${stringType(values)} }`
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const variantsFor = (message: SourceMessage): string[] => {
|
|
76
|
+
if (message.kind === "plural") {
|
|
77
|
+
return [pluralVariant(message)];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (message.kind === "pluralDefaults") {
|
|
81
|
+
return [pluralDefaultsVariant(message)];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return message.kind === "default" ? [defaultVariant(message)] : [pointVariant(message)];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const renderRegistry = (messages: SourceMessage[]): string[] => {
|
|
88
|
+
const entries: Map<string, Set<string>> = new Map();
|
|
89
|
+
|
|
90
|
+
for (const message of messages) {
|
|
91
|
+
const variants = entries.get(message.msgid) ?? new Set<string>();
|
|
92
|
+
|
|
93
|
+
for (const variant of variantsFor(message)) {
|
|
94
|
+
variants.add(variant);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
entries.set(message.msgid, variants);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return [...entries]
|
|
101
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
102
|
+
.map(([msgid, variants]) => ` ${JSON.stringify(msgid)}: ${[...variants].join(" | ")};`);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const generatedHeader = (): string[] => [
|
|
106
|
+
"/**",
|
|
107
|
+
" * Translation declarations generated by GTKX. Regenerated by",
|
|
108
|
+
" * `gtkx codegen`, `gtkx dev`, and `gtkx build`; do not edit.",
|
|
109
|
+
" */",
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const renderI18nTypes = (messages: SourceMessage[], isEnabled = true): string => {
|
|
113
|
+
if (!isEnabled) {
|
|
114
|
+
return [...generatedHeader(), "export {};", ""].join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return [
|
|
118
|
+
...generatedHeader(),
|
|
119
|
+
'import "@gtkx/i18n";',
|
|
120
|
+
"",
|
|
121
|
+
'declare module "@gtkx/i18n" {',
|
|
122
|
+
" interface TranslationRegistry {",
|
|
123
|
+
...renderRegistry(messages),
|
|
124
|
+
" }",
|
|
125
|
+
"}",
|
|
126
|
+
"",
|
|
127
|
+
].join("\n");
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const i18nTypesPath = (root: string): string => join(root, "node_modules", ".gtkx", I18N_TYPES_FILENAME);
|
|
131
|
+
|
|
132
|
+
const readFileOrNull = (path: string): string | null => {
|
|
133
|
+
try {
|
|
134
|
+
return readFileSync(path, "utf8");
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const emitI18nTypes = (root: string, messages: SourceMessage[], isEnabled = true): I18nTypesResult => {
|
|
141
|
+
const path = i18nTypesPath(root);
|
|
142
|
+
const content = renderI18nTypes(messages, isEnabled);
|
|
143
|
+
|
|
144
|
+
if (readFileOrNull(path) === content) {
|
|
145
|
+
return { isWritten: false, path };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
149
|
+
writeFileSync(path, content);
|
|
150
|
+
|
|
151
|
+
return { isWritten: true, path };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export { I18N_TYPES_FILENAME, emitI18nTypes, i18nTypesPath };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type RecordedPackage = {
|
|
2
|
+
name: string;
|
|
3
|
+
version: string | null;
|
|
4
|
+
dir: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type BuildManifest = {
|
|
8
|
+
generator: typeof BUILD_MANIFEST_GENERATOR;
|
|
9
|
+
formatVersion: typeof BUILD_MANIFEST_FORMAT_VERSION;
|
|
10
|
+
schemas: string[];
|
|
11
|
+
packages: RecordedPackage[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type BuildManifestCollector = {
|
|
15
|
+
schemas: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const BUILD_MANIFEST_FILENAME = "gtkx-schemas.json";
|
|
19
|
+
const BUILD_MANIFEST_GENERATOR = "gtkx-build";
|
|
20
|
+
const BUILD_MANIFEST_FORMAT_VERSION = 1;
|
|
21
|
+
|
|
22
|
+
const createBuildManifestCollector = (): BuildManifestCollector => ({ schemas: [] });
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
BUILD_MANIFEST_FILENAME,
|
|
26
|
+
BUILD_MANIFEST_FORMAT_VERSION,
|
|
27
|
+
BUILD_MANIFEST_GENERATOR,
|
|
28
|
+
createBuildManifestCollector,
|
|
29
|
+
type BuildManifest,
|
|
30
|
+
type BuildManifestCollector,
|
|
31
|
+
type RecordedPackage,
|
|
32
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { type Stats, statSync } from "node:fs";
|
|
2
|
+
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
type ResolvedApplicationIcon = { kind: "file"; path: string } |
|
|
5
|
+
{ kind: "none" } |
|
|
6
|
+
{ kind: "theme"; path: string };
|
|
7
|
+
|
|
8
|
+
const ICON_EXTENSIONS: Set<string> = new Set([".svg", ".png", ".xpm"]);
|
|
9
|
+
const SCALABLE_DIR = "hicolor/scalable/apps";
|
|
10
|
+
const SIZE_PATTERN = /^(?<width>[1-9]\d{0,3})x(?<height>[1-9]\d{0,3})$/;
|
|
11
|
+
const SIZE_SEPARATORS = /[-_.]/;
|
|
12
|
+
|
|
13
|
+
const isInside = (parent: string, candidate: string): boolean => {
|
|
14
|
+
const rel = relative(parent, candidate);
|
|
15
|
+
|
|
16
|
+
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getStats = (path: string): Stats | undefined => {
|
|
20
|
+
try {
|
|
21
|
+
return statSync(path, { throwIfNoEntry: false });
|
|
22
|
+
} catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const iconExtension = (file: string): string => {
|
|
28
|
+
const extension = extname(file).toLowerCase();
|
|
29
|
+
|
|
30
|
+
if (!ICON_EXTENSIONS.has(extension)) {
|
|
31
|
+
throw new Error(`Cannot use ${file} as an application icon: expected an SVG, PNG, or XPM file`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return extension;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const defaultIconFiles = (root: string, applicationId: string): string[] =>
|
|
38
|
+
[...ICON_EXTENSIONS]
|
|
39
|
+
.map((extension) => join(root, `${applicationId}${extension}`))
|
|
40
|
+
.filter((path) => getStats(path)?.isFile() === true);
|
|
41
|
+
|
|
42
|
+
const resolveDefaultApplicationIcon = (root: string, applicationId: string): ResolvedApplicationIcon => {
|
|
43
|
+
const files = defaultIconFiles(root, applicationId);
|
|
44
|
+
|
|
45
|
+
if (files.length === 0) {
|
|
46
|
+
return { kind: "none" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (files.length > 1) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Found multiple default application icons for ${applicationId}; set \`applicationIcon\` to choose one`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const path = files[0];
|
|
56
|
+
|
|
57
|
+
if (path === undefined) {
|
|
58
|
+
return { kind: "none" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { kind: "file", path };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const resolveApplicationIcon = (
|
|
65
|
+
root: string,
|
|
66
|
+
applicationId: string,
|
|
67
|
+
configured: string | undefined,
|
|
68
|
+
): ResolvedApplicationIcon => {
|
|
69
|
+
if (configured === undefined) {
|
|
70
|
+
return resolveDefaultApplicationIcon(root, applicationId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isAbsolute(configured)) {
|
|
74
|
+
throw new Error(`Cannot use "${configured}" as the application icon: expected a project-relative path`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const path = resolve(root, configured);
|
|
78
|
+
|
|
79
|
+
if (!isInside(root, path)) {
|
|
80
|
+
throw new Error(`Cannot use "${configured}" as the application icon: it is outside ${root}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const stats = getStats(path);
|
|
84
|
+
|
|
85
|
+
if (stats?.isDirectory() === true) {
|
|
86
|
+
return { kind: "theme", path };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (stats?.isFile() !== true) {
|
|
90
|
+
throw new Error(`Cannot read the application icon "${configured}": no such file or directory under ${root}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
iconExtension(path);
|
|
94
|
+
|
|
95
|
+
return { kind: "file", path };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const squareSize = (token: string): number | null => {
|
|
99
|
+
const groups = SIZE_PATTERN.exec(token)?.groups;
|
|
100
|
+
|
|
101
|
+
if (groups === undefined || groups.width !== groups.height) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return Number(groups.width);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const iconThemeDir = (file: string, extension: string): string => {
|
|
109
|
+
if (extension === ".svg") {
|
|
110
|
+
return SCALABLE_DIR;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const size = basename(file)
|
|
114
|
+
.split(SIZE_SEPARATORS)
|
|
115
|
+
.map((token) => squareSize(token))
|
|
116
|
+
.find((value) => value !== null);
|
|
117
|
+
|
|
118
|
+
return size === undefined ? SCALABLE_DIR : `hicolor/${String(size)}x${String(size)}/apps`;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const relativeIconPath = (applicationId: string, file: string): string => {
|
|
122
|
+
const extension = iconExtension(file);
|
|
123
|
+
|
|
124
|
+
return `${iconThemeDir(file, extension)}/${applicationId}${extension}`;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export { relativeIconPath, resolveApplicationIcon, type ResolvedApplicationIcon };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { ESTree, ParseResult, ParserOptions } from "vite";
|
|
2
|
+
import { sortStringsBy } from "@gtkx/utils";
|
|
3
|
+
import { type Dirent, readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import { extname, join } from "node:path";
|
|
5
|
+
import { parseSync, Visitor } from "vite";
|
|
6
|
+
|
|
7
|
+
type SourceImport = {
|
|
8
|
+
importer: string;
|
|
9
|
+
source: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type SourceLanguage = NonNullable<ParserOptions["lang"]>;
|
|
13
|
+
type ParsedModule = ParseResult["module"];
|
|
14
|
+
|
|
15
|
+
const SOURCE_LANGUAGES: Map<string, SourceLanguage> = new Map([
|
|
16
|
+
[".cjs", "jsx"],
|
|
17
|
+
[".cts", "ts"],
|
|
18
|
+
[".js", "jsx"],
|
|
19
|
+
[".jsx", "jsx"],
|
|
20
|
+
[".mjs", "jsx"],
|
|
21
|
+
[".mts", "ts"],
|
|
22
|
+
[".ts", "ts"],
|
|
23
|
+
[".tsx", "tsx"],
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const EXCLUDED_DIRECTORIES: Set<string> = new Set([
|
|
27
|
+
".git",
|
|
28
|
+
".gtkx",
|
|
29
|
+
"build",
|
|
30
|
+
"coverage",
|
|
31
|
+
"dist",
|
|
32
|
+
"node_modules",
|
|
33
|
+
"out-tsc",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const sourceLanguage = (path: string): SourceLanguage | undefined => {
|
|
37
|
+
if (path.endsWith(".d.ts") || path.endsWith(".d.cts") || path.endsWith(".d.mts")) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return SOURCE_LANGUAGES.get(extname(path).toLowerCase());
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const collectSourceEntry = (dir: string, entry: Dirent, found: string[]): void => {
|
|
45
|
+
const path = join(dir, entry.name);
|
|
46
|
+
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
if (!EXCLUDED_DIRECTORIES.has(entry.name)) {
|
|
49
|
+
sourceFilesIn(path, found);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (entry.isFile() && sourceLanguage(path) !== undefined) {
|
|
56
|
+
found.push(path);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const sourceFilesIn = (dir: string, found: string[]): void => {
|
|
61
|
+
let entries: Dirent[];
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
collectSourceEntry(dir, entry, found);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const discoverSourceFiles = (dir: string): string[] => {
|
|
75
|
+
const files: string[] = [];
|
|
76
|
+
sourceFilesIn(dir, files);
|
|
77
|
+
|
|
78
|
+
return sortStringsBy(files, (path) => path);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const staticImportSources = (module: ParsedModule): string[] => [
|
|
82
|
+
...module.staticImports.map((statement) => statement.moduleRequest.value),
|
|
83
|
+
...module.staticExports.flatMap((statement) =>
|
|
84
|
+
statement.entries
|
|
85
|
+
.map((entry) => entry.moduleRequest?.value)
|
|
86
|
+
.filter((source): source is string => source !== undefined),
|
|
87
|
+
),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const staticRuntimeImportSources = (module: ParsedModule): string[] => [
|
|
91
|
+
...module.staticImports
|
|
92
|
+
.filter((statement) => statement.entries.length === 0 || statement.entries.some((entry) => !entry.isType))
|
|
93
|
+
.map((statement) => statement.moduleRequest.value),
|
|
94
|
+
...module.staticExports.flatMap((statement) =>
|
|
95
|
+
statement.entries
|
|
96
|
+
.filter((entry) => !entry.isType)
|
|
97
|
+
.map((entry) => entry.moduleRequest?.value)
|
|
98
|
+
.filter((source): source is string => source !== undefined),
|
|
99
|
+
),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const templateImportSource = (node: ESTree.TemplateLiteral): string | null => {
|
|
103
|
+
const [quasi] = node.quasis;
|
|
104
|
+
|
|
105
|
+
return quasi === undefined || node.expressions.length > 0 ? null : quasi.value.cooked;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const literalImportSource = (node: ESTree.Expression): string | null => {
|
|
109
|
+
if (node.type === "TemplateLiteral") {
|
|
110
|
+
return templateImportSource(node);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return node.type === "Literal" && typeof node.value === "string" ? node.value : null;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const collectDynamicImportSources = (program: ESTree.Program, found: string[]): void => {
|
|
117
|
+
new Visitor({
|
|
118
|
+
ImportExpression: (node) => {
|
|
119
|
+
const source = literalImportSource(node.source);
|
|
120
|
+
|
|
121
|
+
if (source !== null) {
|
|
122
|
+
found.push(source);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
}).visit(program);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const parseImportsInWith = (
|
|
129
|
+
path: string,
|
|
130
|
+
staticSources: (module: ParsedModule) => string[],
|
|
131
|
+
): SourceImport[] | null => {
|
|
132
|
+
const lang = sourceLanguage(path);
|
|
133
|
+
|
|
134
|
+
if (lang === undefined) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let parsed: ParseResult;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
parsed = parseSync(path, readFileSync(path, "utf8"), { lang });
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (parsed.errors.length > 0) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const sources = staticSources(parsed.module);
|
|
151
|
+
collectDynamicImportSources(parsed.program, sources);
|
|
152
|
+
|
|
153
|
+
return [...new Set(sources)].map((source) => ({ importer: path, source }));
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const parseImportsIn = (path: string): SourceImport[] | null => parseImportsInWith(path, staticImportSources);
|
|
157
|
+
|
|
158
|
+
const parseRuntimeImportsIn = (path: string): SourceImport[] | null =>
|
|
159
|
+
parseImportsInWith(path, staticRuntimeImportSources);
|
|
160
|
+
|
|
161
|
+
const importsIn = (path: string): SourceImport[] => parseImportsIn(path) ?? [];
|
|
162
|
+
const importKey = (entry: SourceImport): string => `${entry.importer}\0${entry.source}`;
|
|
163
|
+
|
|
164
|
+
const discoverSourceImports = (dir: string): SourceImport[] => {
|
|
165
|
+
const files = discoverSourceFiles(dir);
|
|
166
|
+
const imports = files.flatMap((path) => importsIn(path));
|
|
167
|
+
|
|
168
|
+
return sortStringsBy(imports, importKey);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export { discoverSourceFiles, discoverSourceImports, parseRuntimeImportsIn, type SourceImport, sourceLanguage };
|
package/src/mcp/client.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as Gio from "@gtkx/gi/gio";
|
|
2
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_SOCKET_PATH,
|
|
5
|
+
isConnectionClosedError,
|
|
6
|
+
type JSONRPCRequest,
|
|
7
|
+
ProtocolConnection,
|
|
8
|
+
type Result,
|
|
9
|
+
} from "@gtkx/mcp/internal";
|
|
10
|
+
import { error, errorMessage, info, normalizeError } from "@gtkx/utils";
|
|
5
11
|
import * as net from "node:net";
|
|
6
12
|
import { dispatch } from "./handlers.js";
|
|
7
13
|
import { WidgetRegistry } from "./widget-registry.js";
|
|
@@ -25,11 +31,6 @@ const RECONNECT_DELAY_MS = 2000;
|
|
|
25
31
|
const REGISTER_TIMEOUT_MS = 30_000;
|
|
26
32
|
const DISCONNECT_ERROR_CODES: Set<string> = new Set(["ENOENT", "ECONNREFUSED", "EPIPE", "ECONNRESET"]);
|
|
27
33
|
|
|
28
|
-
const toResponseError = (error: unknown): { code: number; message: string; data?: unknown } =>
|
|
29
|
-
error instanceof ProtocolError
|
|
30
|
-
? error.toErrorObject()
|
|
31
|
-
: { code: ErrorCode.INTERNAL_ERROR, message: errorMessage(error) };
|
|
32
|
-
|
|
33
34
|
const connectSettler = (callbacks: ConnectCallbacks): ConnectSettler => {
|
|
34
35
|
let isSettled = false;
|
|
35
36
|
|
|
@@ -82,7 +83,7 @@ class McpClient {
|
|
|
82
83
|
private handleSocketError(socketError: Error): void {
|
|
83
84
|
const code = (socketError as NodeJS.ErrnoException).code;
|
|
84
85
|
|
|
85
|
-
if (code !== undefined && DISCONNECT_ERROR_CODES.has(code)) {
|
|
86
|
+
if (isConnectionClosedError(socketError) || (code !== undefined && DISCONNECT_ERROR_CODES.has(code))) {
|
|
86
87
|
this.scheduleReconnect();
|
|
87
88
|
} else {
|
|
88
89
|
error("Socket error:", socketError.message);
|
|
@@ -119,14 +120,7 @@ class McpClient {
|
|
|
119
120
|
},
|
|
120
121
|
});
|
|
121
122
|
|
|
122
|
-
connection.
|
|
123
|
-
void this.handleRequest(request);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
connection.on("invalid", ({ error: parseError }) => {
|
|
127
|
-
warn(`Received invalid JSON from MCP server: ${parseError.message}`);
|
|
128
|
-
});
|
|
129
|
-
|
|
123
|
+
connection.fallbackRequestHandler = (request) => this.handleRequest(request);
|
|
130
124
|
this.socket = socket;
|
|
131
125
|
this.connection = connection;
|
|
132
126
|
}
|
|
@@ -158,35 +152,16 @@ class McpClient {
|
|
|
158
152
|
);
|
|
159
153
|
}
|
|
160
154
|
|
|
161
|
-
private
|
|
162
|
-
const
|
|
163
|
-
const connection = this.connection;
|
|
155
|
+
private handleRequest(request: JSONRPCRequest): Promise<Result> {
|
|
156
|
+
const defaultApp = Gio.Application.getDefault();
|
|
164
157
|
|
|
165
|
-
if (!
|
|
166
|
-
|
|
158
|
+
if (!(defaultApp instanceof Gtk.Application)) {
|
|
159
|
+
throw new TypeError("Application not initialized");
|
|
167
160
|
}
|
|
168
161
|
|
|
169
|
-
|
|
170
|
-
const defaultApp = Gio.Application.getDefault();
|
|
171
|
-
|
|
172
|
-
if (!(defaultApp instanceof Gtk.Application)) {
|
|
173
|
-
throw new TypeError("Application not initialized");
|
|
174
|
-
}
|
|
162
|
+
this.registry.refresh();
|
|
175
163
|
|
|
176
|
-
|
|
177
|
-
const result = await dispatch(method, params, { app: defaultApp, registry: this.registry });
|
|
178
|
-
connection.write({ id, result });
|
|
179
|
-
} catch (dispatchError) {
|
|
180
|
-
connection.write({ id, error: toResponseError(dispatchError) });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private async handleRequest(request: Request): Promise<void> {
|
|
185
|
-
try {
|
|
186
|
-
await this.respondToRequest(request);
|
|
187
|
-
} catch (requestError) {
|
|
188
|
-
error("Error handling request:", requestError);
|
|
189
|
-
}
|
|
164
|
+
return dispatch(request.method, request.params, { app: defaultApp, registry: this.registry });
|
|
190
165
|
}
|
|
191
166
|
|
|
192
167
|
async connect(): Promise<void> {
|
|
@@ -219,10 +194,8 @@ class McpClient {
|
|
|
219
194
|
this.reconnectTimer = null;
|
|
220
195
|
}
|
|
221
196
|
|
|
222
|
-
this.connection?.rejectPending(new Error("Client disconnected"));
|
|
223
|
-
|
|
224
197
|
if (this.socket) {
|
|
225
|
-
this.connection?.
|
|
198
|
+
void Promise.allSettled([this.connection?.send("app.unregister")]);
|
|
226
199
|
this.socket.destroy();
|
|
227
200
|
this.socket = null;
|
|
228
201
|
}
|
package/src/mcp/handlers.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
invalidRequestError,
|
|
4
4
|
methodNotFoundError,
|
|
5
5
|
type ParamsSchema,
|
|
6
|
+
type Result,
|
|
6
7
|
type ServerInitiatedMethod,
|
|
7
8
|
type ServerRequestParams,
|
|
8
9
|
ServerRequestParamsSchemas,
|
|
@@ -18,10 +19,10 @@ type HandlerContext = {
|
|
|
18
19
|
registry: WidgetRegistry;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
type ValidatedHandler = (ctx: HandlerContext, params: unknown) => Promise<
|
|
22
|
+
type ValidatedHandler = (ctx: HandlerContext, params: unknown) => Promise<Result>;
|
|
22
23
|
type WidgetTarget = { testing: TestingModule; widget: Gtk.Widget };
|
|
23
24
|
type WidgetParams = { widgetId?: string | undefined };
|
|
24
|
-
type TargetedHandler<Params> = (ctx: HandlerContext, target: WidgetTarget, params: Params) => Promise<
|
|
25
|
+
type TargetedHandler<Params> = (ctx: HandlerContext, target: WidgetTarget, params: Params) => Promise<Result>;
|
|
25
26
|
type QueryParams = ServerRequestParams<"widget.query">;
|
|
26
27
|
type QueryBy = QueryParams["by"];
|
|
27
28
|
type QueryRunner = (testing: TestingModule, app: Gtk.Application, params: QueryParams) => Promise<Gtk.Widget[]>;
|
|
@@ -110,7 +111,7 @@ const SEARCHED_BY: Record<QueryBy, string> = {
|
|
|
110
111
|
|
|
111
112
|
function validated<Params>(
|
|
112
113
|
schema: ParamsSchema<Params>,
|
|
113
|
-
handler: (ctx: HandlerContext, params: Params) => Promise<
|
|
114
|
+
handler: (ctx: HandlerContext, params: Params) => Promise<Result>,
|
|
114
115
|
): ValidatedHandler {
|
|
115
116
|
return (ctx, params) => {
|
|
116
117
|
const parsed = schema.safeParse(params ?? {});
|
|
@@ -229,7 +230,7 @@ const emptyQueryHint = (params: QueryParams): string =>
|
|
|
229
230
|
"Call gtkx_get_widget_tree to see what is mounted; by:\"name\" is the widest match, and by:\"role\" accepts " +
|
|
230
231
|
"options.name to match the accessible name of a known role.";
|
|
231
232
|
|
|
232
|
-
async function handleQuery({ app, registry }: HandlerContext, params: QueryParams): Promise<
|
|
233
|
+
async function handleQuery({ app, registry }: HandlerContext, params: QueryParams): Promise<Result> {
|
|
233
234
|
const testing = await loadTestingModule();
|
|
234
235
|
const widgets = await QUERY_RUNNERS[params.by](testing, app, params);
|
|
235
236
|
const resolveId = (widget: Gtk.Widget): string => registry.getOrCreateId(widget);
|
|
@@ -254,7 +255,7 @@ const defaultScreenshotTarget = (registry: WidgetRegistry): Gtk.Widget => {
|
|
|
254
255
|
async function handleScreenshot(
|
|
255
256
|
{ registry }: HandlerContext,
|
|
256
257
|
params: ServerRequestParams<"widget.screenshot">,
|
|
257
|
-
): Promise<
|
|
258
|
+
): Promise<Result> {
|
|
258
259
|
const testing = await loadTestingModule();
|
|
259
260
|
const target = params.windowId ? requireWidget(registry, params.windowId) : defaultScreenshotTarget(registry);
|
|
260
261
|
const result = await testing.screenshot(target, { path: params.path });
|
|
@@ -269,7 +270,7 @@ async function handleScreenshot(
|
|
|
269
270
|
const isServerInitiatedMethod = (method: string): method is ServerInitiatedMethod =>
|
|
270
271
|
Object.hasOwn(ServerRequestParamsSchemas, method);
|
|
271
272
|
|
|
272
|
-
const dispatch = async (method: string, params: unknown, ctx: HandlerContext): Promise<
|
|
273
|
+
const dispatch = async (method: string, params: unknown, ctx: HandlerContext): Promise<Result> => {
|
|
273
274
|
if (!isServerInitiatedMethod(method)) {
|
|
274
275
|
throw methodNotFoundError(method);
|
|
275
276
|
}
|
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
getHandle,
|
|
7
7
|
getInstanceType,
|
|
8
8
|
type Handle,
|
|
9
|
+
isReadableProperty,
|
|
9
10
|
TYPE_ENUM,
|
|
10
11
|
TYPE_FLAGS,
|
|
11
12
|
typeIsA,
|
|
12
13
|
typeName,
|
|
13
14
|
} from "@gtkx/runtime";
|
|
14
15
|
import { fromValue, getValueType } from "@gtkx/runtime/internal";
|
|
15
|
-
import {
|
|
16
|
+
import { errorMessage, kebabCase } from "@gtkx/utils";
|
|
16
17
|
import type { WidgetRegistry } from "./widget-registry.js";
|
|
17
18
|
|
|
18
19
|
type PropertyRepresentation = Omit<SerializedProperty, "type">;
|
|
@@ -39,18 +40,6 @@ function prototypesFor(widget: Gtk.Widget): object[] {
|
|
|
39
40
|
return prototypes;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function getAccessor(widget: Gtk.Widget, member: string): PropertyDescriptor | undefined {
|
|
43
|
-
for (const prototype of prototypesFor(widget)) {
|
|
44
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, member);
|
|
45
|
-
|
|
46
|
-
if (descriptor !== undefined) {
|
|
47
|
-
return descriptor;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
43
|
function getReadableNames(prototype: object): string[] {
|
|
55
44
|
const descriptors = Object.entries(Object.getOwnPropertyDescriptors(prototype));
|
|
56
45
|
|
|
@@ -64,7 +53,7 @@ function readablePropertyNames(widget: Gtk.Widget): string[] {
|
|
|
64
53
|
}
|
|
65
54
|
|
|
66
55
|
const requireReadableProperty = (widget: Gtk.Widget, property: string): void => {
|
|
67
|
-
if (
|
|
56
|
+
if (isReadableProperty(getInstanceType(widget), property)) {
|
|
68
57
|
return;
|
|
69
58
|
}
|
|
70
59
|
|