@gtkx/cli 0.9.3 → 0.10.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/bin/gtkx.js +2 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/create.d.ts +37 -6
- package/dist/create.js +44 -844
- package/dist/dev-server.d.ts +26 -4
- package/dist/dev-server.js +64 -9
- package/dist/refresh-runtime.d.ts +9 -0
- package/dist/refresh-runtime.js +44 -0
- package/dist/templates.d.ts +8 -0
- package/dist/templates.js +18 -0
- package/dist/vite-plugin-gtkx-refresh.d.ts +7 -0
- package/dist/vite-plugin-gtkx-refresh.js +36 -0
- package/dist/vite-plugin-swc-ssr-refresh.d.ts +7 -0
- package/dist/vite-plugin-swc-ssr-refresh.js +45 -0
- package/package.json +14 -5
- package/templates/claude/EXAMPLES.md.ejs +364 -0
- package/templates/claude/SKILL.md.ejs +372 -0
- package/templates/claude/WIDGETS.md.ejs +531 -0
- package/templates/config/jest.config.js.ejs +19 -0
- package/templates/config/vitest.config.ts.ejs +13 -0
- package/templates/gitignore.ejs +4 -0
- package/templates/package.json.ejs +15 -0
- package/templates/src/app.tsx.ejs +17 -0
- package/templates/src/dev.tsx.ejs +5 -0
- package/templates/src/index.tsx.ejs +4 -0
- package/templates/tests/app.test.tsx.ejs +27 -0
- package/templates/tsconfig.json.ejs +14 -0
package/dist/dev-server.d.ts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { type InlineConfig, type ViteDevServer } from "vite";
|
|
2
2
|
/**
|
|
3
|
-
* Options for
|
|
3
|
+
* Options for the GTKX development server.
|
|
4
4
|
*/
|
|
5
5
|
export type DevServerOptions = {
|
|
6
|
-
/** Path to the
|
|
6
|
+
/** Path to the entry file (e.g., "src/dev.tsx") */
|
|
7
7
|
entry: string;
|
|
8
|
-
/** Vite configuration
|
|
8
|
+
/** Additional Vite configuration */
|
|
9
9
|
vite?: InlineConfig;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
|
-
* Creates
|
|
12
|
+
* Creates a Vite-based development server with hot module replacement.
|
|
13
|
+
*
|
|
14
|
+
* Provides fast refresh for React components and full reload for other changes.
|
|
15
|
+
* The server watches for file changes and automatically updates the running
|
|
16
|
+
* GTK application.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Server configuration including entry point and Vite options
|
|
19
|
+
* @returns A Vite development server instance
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import { createDevServer } from "@gtkx/cli";
|
|
24
|
+
* import { render } from "@gtkx/react";
|
|
25
|
+
*
|
|
26
|
+
* const server = await createDevServer({
|
|
27
|
+
* entry: "./src/dev.tsx",
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* const mod = await server.ssrLoadModule("./src/dev.tsx");
|
|
31
|
+
* render(<mod.default />, mod.appId);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @see {@link DevServerOptions} for configuration options
|
|
13
35
|
*/
|
|
14
36
|
export declare const createDevServer: (options: DevServerOptions) => Promise<ViteDevServer>;
|
|
15
37
|
export type { ViteDevServer };
|
package/dist/dev-server.js
CHANGED
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { events } from "@gtkx/ffi";
|
|
3
|
-
import { update } from "@gtkx/react";
|
|
4
|
-
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { setHotReloading, update } from "@gtkx/react";
|
|
5
4
|
import { createServer } from "vite";
|
|
5
|
+
import { isReactRefreshBoundary, performRefresh } from "./refresh-runtime.js";
|
|
6
|
+
import { gtkxRefresh } from "./vite-plugin-gtkx-refresh.js";
|
|
7
|
+
import { swcSsrRefresh } from "./vite-plugin-swc-ssr-refresh.js";
|
|
6
8
|
/**
|
|
7
|
-
* Creates
|
|
9
|
+
* Creates a Vite-based development server with hot module replacement.
|
|
10
|
+
*
|
|
11
|
+
* Provides fast refresh for React components and full reload for other changes.
|
|
12
|
+
* The server watches for file changes and automatically updates the running
|
|
13
|
+
* GTK application.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Server configuration including entry point and Vite options
|
|
16
|
+
* @returns A Vite development server instance
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { createDevServer } from "@gtkx/cli";
|
|
21
|
+
* import { render } from "@gtkx/react";
|
|
22
|
+
*
|
|
23
|
+
* const server = await createDevServer({
|
|
24
|
+
* entry: "./src/dev.tsx",
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* const mod = await server.ssrLoadModule("./src/dev.tsx");
|
|
28
|
+
* render(<mod.default />, mod.appId);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see {@link DevServerOptions} for configuration options
|
|
8
32
|
*/
|
|
9
33
|
export const createDevServer = async (options) => {
|
|
10
34
|
const { entry, vite: viteConfig } = options;
|
|
35
|
+
const moduleExports = new Map();
|
|
11
36
|
const server = await createServer({
|
|
12
37
|
...viteConfig,
|
|
13
38
|
appType: "custom",
|
|
14
39
|
plugins: [
|
|
15
|
-
|
|
40
|
+
swcSsrRefresh(),
|
|
41
|
+
gtkxRefresh(),
|
|
16
42
|
{
|
|
17
43
|
name: "gtkx:remove-react-dom-optimized",
|
|
18
44
|
enforce: "post",
|
|
@@ -37,29 +63,58 @@ export const createDevServer = async (options) => {
|
|
|
37
63
|
},
|
|
38
64
|
});
|
|
39
65
|
const loadModule = async () => {
|
|
40
|
-
|
|
66
|
+
const mod = (await server.ssrLoadModule(entry));
|
|
67
|
+
moduleExports.set(entry, { ...mod });
|
|
68
|
+
return mod;
|
|
41
69
|
};
|
|
42
70
|
const invalidateAllModules = () => {
|
|
43
71
|
for (const module of server.moduleGraph.idToModuleMap.values()) {
|
|
44
72
|
server.moduleGraph.invalidateModule(module);
|
|
45
73
|
}
|
|
46
74
|
};
|
|
75
|
+
const invalidateModuleAndImporters = (filePath) => {
|
|
76
|
+
const module = server.moduleGraph.getModuleById(filePath);
|
|
77
|
+
if (module) {
|
|
78
|
+
server.moduleGraph.invalidateModule(module);
|
|
79
|
+
for (const importer of module.importers) {
|
|
80
|
+
server.moduleGraph.invalidateModule(importer);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
47
84
|
events.on("stop", () => {
|
|
48
85
|
server.close();
|
|
49
86
|
});
|
|
50
87
|
server.watcher.on("change", async (changedPath) => {
|
|
51
88
|
console.log(`[gtkx] File changed: ${changedPath}`);
|
|
52
|
-
invalidateAllModules();
|
|
53
89
|
try {
|
|
90
|
+
const module = server.moduleGraph.getModuleById(changedPath);
|
|
91
|
+
if (module) {
|
|
92
|
+
invalidateModuleAndImporters(changedPath);
|
|
93
|
+
const newMod = (await server.ssrLoadModule(changedPath));
|
|
94
|
+
moduleExports.set(changedPath, { ...newMod });
|
|
95
|
+
if (isReactRefreshBoundary(newMod)) {
|
|
96
|
+
console.log("[gtkx] Fast refreshing...");
|
|
97
|
+
performRefresh();
|
|
98
|
+
console.log("[gtkx] Fast refresh complete");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
console.log("[gtkx] Full reload...");
|
|
103
|
+
invalidateAllModules();
|
|
54
104
|
const mod = await loadModule();
|
|
55
105
|
const App = mod.default;
|
|
56
106
|
if (typeof App !== "function") {
|
|
57
107
|
console.error("[gtkx] Entry file must export a default function component");
|
|
58
108
|
return;
|
|
59
109
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
setHotReloading(true);
|
|
111
|
+
try {
|
|
112
|
+
await update(_jsx(App, {}));
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
setHotReloading(false);
|
|
116
|
+
}
|
|
117
|
+
console.log("[gtkx] Full reload complete");
|
|
63
118
|
}
|
|
64
119
|
catch (error) {
|
|
65
120
|
console.error("[gtkx] Hot reload failed:", error);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import RefreshRuntime from "react-refresh/runtime";
|
|
2
|
+
type ComponentType = (...args: unknown[]) => unknown;
|
|
3
|
+
export declare function createModuleRegistration(moduleId: string): {
|
|
4
|
+
$RefreshReg$: (type: ComponentType, id: string) => void;
|
|
5
|
+
$RefreshSig$: typeof RefreshRuntime.createSignatureFunctionForTransform;
|
|
6
|
+
};
|
|
7
|
+
export declare function isReactRefreshBoundary(moduleExports: Record<string, unknown>): boolean;
|
|
8
|
+
export declare function performRefresh(): void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import RefreshRuntime from "react-refresh/runtime";
|
|
2
|
+
RefreshRuntime.injectIntoGlobalHook(globalThis);
|
|
3
|
+
globalThis.$RefreshReg$ = () => { };
|
|
4
|
+
globalThis.$RefreshSig$ = () => (type) => type;
|
|
5
|
+
export function createModuleRegistration(moduleId) {
|
|
6
|
+
return {
|
|
7
|
+
$RefreshReg$: (type, id) => {
|
|
8
|
+
RefreshRuntime.register(type, `${moduleId} ${id}`);
|
|
9
|
+
},
|
|
10
|
+
$RefreshSig$: RefreshRuntime.createSignatureFunctionForTransform,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function isLikelyComponentType(value) {
|
|
14
|
+
if (typeof value !== "function") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const func = value;
|
|
18
|
+
if (func.$$typeof === Symbol.for("react.memo") || func.$$typeof === Symbol.for("react.forward_ref")) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const name = value.name;
|
|
22
|
+
if (typeof name === "string" && /^[A-Z]/.test(name)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
export function isReactRefreshBoundary(moduleExports) {
|
|
28
|
+
if (RefreshRuntime.isLikelyComponentType(moduleExports)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
for (const key in moduleExports) {
|
|
32
|
+
if (key === "__esModule") {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const value = moduleExports[key];
|
|
36
|
+
if (!isLikelyComponentType(value)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return Object.keys(moduleExports).filter((k) => k !== "__esModule").length > 0;
|
|
41
|
+
}
|
|
42
|
+
export function performRefresh() {
|
|
43
|
+
RefreshRuntime.performReactRefresh();
|
|
44
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TestingFramework } from "./create.js";
|
|
2
|
+
export interface TemplateContext {
|
|
3
|
+
name: string;
|
|
4
|
+
appId: string;
|
|
5
|
+
title: string;
|
|
6
|
+
testing: TestingFramework;
|
|
7
|
+
}
|
|
8
|
+
export declare const renderFile: (templateName: string, context: TemplateContext) => string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import ejs from "ejs";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const getTemplatesDir = () => {
|
|
8
|
+
return join(__dirname, "..", "templates");
|
|
9
|
+
};
|
|
10
|
+
const renderTemplate = (templatePath, context) => {
|
|
11
|
+
const templateContent = readFileSync(templatePath, "utf-8");
|
|
12
|
+
return ejs.render(templateContent, context);
|
|
13
|
+
};
|
|
14
|
+
export const renderFile = (templateName, context) => {
|
|
15
|
+
const templatesDir = getTemplatesDir();
|
|
16
|
+
const templatePath = join(templatesDir, templateName);
|
|
17
|
+
return renderTemplate(templatePath, context);
|
|
18
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const defaultInclude = /\.[tj]sx?$/;
|
|
2
|
+
const defaultExclude = /node_modules/;
|
|
3
|
+
const refreshRuntimePath = "@gtkx/cli/refresh-runtime";
|
|
4
|
+
export function gtkxRefresh(options = {}) {
|
|
5
|
+
const include = options.include ?? defaultInclude;
|
|
6
|
+
const exclude = options.exclude ?? defaultExclude;
|
|
7
|
+
return {
|
|
8
|
+
name: "gtkx:refresh",
|
|
9
|
+
enforce: "post",
|
|
10
|
+
transform(code, id, transformOptions) {
|
|
11
|
+
if (!transformOptions?.ssr) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!include.test(id)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (exclude.test(id)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const hasRefreshReg = code.includes("$RefreshReg$");
|
|
21
|
+
const hasRefreshSig = code.includes("$RefreshSig$");
|
|
22
|
+
if (!hasRefreshReg && !hasRefreshSig) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const moduleIdJson = JSON.stringify(id);
|
|
26
|
+
const header = `
|
|
27
|
+
import { createModuleRegistration as __createModuleRegistration__ } from "${refreshRuntimePath}";
|
|
28
|
+
const { $RefreshReg$, $RefreshSig$ } = __createModuleRegistration__(${moduleIdJson});
|
|
29
|
+
`;
|
|
30
|
+
return {
|
|
31
|
+
code: header + code,
|
|
32
|
+
map: null,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { transform } from "@swc/core";
|
|
2
|
+
const defaultInclude = /\.[tj]sx?$/;
|
|
3
|
+
const defaultExclude = /node_modules/;
|
|
4
|
+
export function swcSsrRefresh(options = {}) {
|
|
5
|
+
const include = options.include ?? defaultInclude;
|
|
6
|
+
const exclude = options.exclude ?? defaultExclude;
|
|
7
|
+
return {
|
|
8
|
+
name: "gtkx:swc-ssr-refresh",
|
|
9
|
+
enforce: "pre",
|
|
10
|
+
async transform(code, id, transformOptions) {
|
|
11
|
+
if (!transformOptions?.ssr) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!include.test(id)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (exclude.test(id)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const isTsx = id.endsWith(".tsx");
|
|
21
|
+
const isTs = id.endsWith(".ts") || isTsx;
|
|
22
|
+
const swcOptions = {
|
|
23
|
+
filename: id,
|
|
24
|
+
sourceFileName: id,
|
|
25
|
+
sourceMaps: true,
|
|
26
|
+
jsc: {
|
|
27
|
+
parser: isTs ? { syntax: "typescript", tsx: isTsx } : { syntax: "ecmascript", jsx: true },
|
|
28
|
+
transform: {
|
|
29
|
+
react: {
|
|
30
|
+
runtime: "automatic",
|
|
31
|
+
development: true,
|
|
32
|
+
refresh: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
target: "es2022",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const result = await transform(code, swcOptions);
|
|
39
|
+
return {
|
|
40
|
+
code: result.code,
|
|
41
|
+
map: result.map,
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "CLI for GTKX - create and develop GTK4 React applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtk",
|
|
@@ -33,24 +33,33 @@
|
|
|
33
33
|
"./dev-server": {
|
|
34
34
|
"types": "./dist/dev-server.d.ts",
|
|
35
35
|
"default": "./dist/dev-server.js"
|
|
36
|
+
},
|
|
37
|
+
"./refresh-runtime": {
|
|
38
|
+
"types": "./dist/refresh-runtime.d.ts",
|
|
39
|
+
"default": "./dist/refresh-runtime.js"
|
|
36
40
|
}
|
|
37
41
|
},
|
|
38
42
|
"bin": {
|
|
39
|
-
"gtkx": "
|
|
43
|
+
"gtkx": "bin/gtkx.js"
|
|
40
44
|
},
|
|
41
45
|
"files": [
|
|
46
|
+
"bin",
|
|
42
47
|
"dist",
|
|
43
48
|
"templates"
|
|
44
49
|
],
|
|
45
50
|
"dependencies": {
|
|
46
51
|
"@clack/prompts": "^0.11.0",
|
|
47
|
-
"@
|
|
52
|
+
"@swc/core": "^1.15.7",
|
|
48
53
|
"citty": "^0.1.6",
|
|
54
|
+
"ejs": "^3.1.10",
|
|
55
|
+
"react-refresh": "^0.18.0",
|
|
49
56
|
"vite": "^7.3.0",
|
|
50
|
-
"@gtkx/ffi": "0.
|
|
51
|
-
"@gtkx/react": "0.
|
|
57
|
+
"@gtkx/ffi": "0.10.0",
|
|
58
|
+
"@gtkx/react": "0.10.0"
|
|
52
59
|
},
|
|
53
60
|
"devDependencies": {
|
|
61
|
+
"@types/ejs": "^3.1.5",
|
|
62
|
+
"@types/react-refresh": "^0.14.7",
|
|
54
63
|
"memfs": "^4.51.1"
|
|
55
64
|
},
|
|
56
65
|
"peerDependencies": {
|