@wsxjs/cli 0.0.18

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WSXJS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # @wsxjs/cli
2
+
3
+ CLI tool for WSXJS initialization and configuration with beautiful Ink-based UI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @wsxjs/cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Initialize WSXJS
14
+
15
+ ```bash
16
+ npx wsx init
17
+ ```
18
+
19
+ This command will interactively guide you through setting up WSXJS in your project:
20
+
21
+ - ✅ Configure TypeScript (`tsconfig.json`)
22
+ - ✅ Configure Vite (`vite.config.ts`)
23
+ - ✅ Generate `wsx.d.ts` type declarations
24
+ - ✅ Configure ESLint with WSX rules
25
+
26
+ The CLI uses [Ink](https://github.com/vadimdemedes/ink) to provide a beautiful, interactive terminal UI with real-time progress updates.
27
+
28
+ ### Check Configuration
29
+
30
+ ```bash
31
+ npx wsx check
32
+ ```
33
+
34
+ Check your current WSXJS configuration and get suggestions for improvements.
35
+
36
+ ## Commands
37
+
38
+ ### `wsx init`
39
+
40
+ Initialize WSXJS in your project with an interactive setup wizard.
41
+
42
+ **Options:**
43
+ - `--skip-tsconfig`: Skip TypeScript configuration
44
+ - `--skip-vite`: Skip Vite configuration
45
+ - `--skip-eslint`: Skip ESLint configuration
46
+ - `--skip-types`: Skip `wsx.d.ts` generation
47
+ - `--use-tsconfig-package`: Use `@wsxjs/wsx-tsconfig` package
48
+ - `--use-decorators`: Enable decorator support (`@state`)
49
+ - `--no-interactive`: Skip interactive prompts (use command-line options only)
50
+
51
+ **Examples:**
52
+
53
+ ```bash
54
+ # Full interactive setup
55
+ npx wsx init
56
+
57
+ # Skip TypeScript config
58
+ npx wsx init --skip-tsconfig
59
+
60
+ # Use @wsxjs/wsx-tsconfig package
61
+ npx wsx init --use-tsconfig-package
62
+
63
+ # Non-interactive mode
64
+ npx wsx init --skip-types --skip-eslint --no-interactive
65
+ ```
66
+
67
+ ### `wsx check`
68
+
69
+ Check your WSXJS configuration and display any issues or suggestions.
70
+
71
+ **Example:**
72
+
73
+ ```bash
74
+ npx wsx check
75
+ ```
76
+
77
+ Output:
78
+ ```
79
+ 🔍 WSX Configuration Check
80
+
81
+ Status:
82
+ wsx.d.ts: ✓ Found
83
+ tsconfig.json: ✓ Valid
84
+ vite.config: ✓ Valid
85
+
86
+ ✅ All checks passed! Your WSX configuration looks good.
87
+ ```
88
+
89
+ ## Features
90
+
91
+ ### 🎨 Beautiful Terminal UI
92
+
93
+ The CLI uses [Ink](https://github.com/vadimdemedes/ink) to provide a React-based terminal UI with:
94
+ - Real-time progress indicators
95
+ - Spinner animations
96
+ - Color-coded status messages
97
+ - Step-by-step progress tracking
98
+
99
+ ### ⚙️ Smart Configuration
100
+
101
+ - **TypeScript**: Automatically configures `tsconfig.json` with WSX-compatible settings
102
+ - **Vite**: Adds `@wsxjs/wsx-vite-plugin` to your Vite configuration
103
+ - **ESLint**: Configures ESLint with WSX-specific rules
104
+ - **Type Declarations**: Generates `wsx.d.ts` for `.wsx` file type support
105
+
106
+ ### 🔍 Configuration Validation
107
+
108
+ The `check` command validates your setup and provides:
109
+ - Issue detection
110
+ - Actionable suggestions
111
+ - Configuration status overview
112
+
113
+ ## Examples
114
+
115
+ ### Basic Setup
116
+
117
+ ```bash
118
+ # Install WSXJS packages
119
+ npm install @wsxjs/wsx-core @wsxjs/wsx-vite-plugin
120
+
121
+ # Install CLI tool
122
+ npm install -D @wsxjs/cli
123
+
124
+ # Initialize with interactive UI
125
+ npx wsx init
126
+ ```
127
+
128
+ ### Advanced Setup
129
+
130
+ ```bash
131
+ # Use @wsxjs/wsx-tsconfig package
132
+ npx wsx init --use-tsconfig-package --use-decorators
133
+
134
+ # Skip certain configurations
135
+ npx wsx init --skip-vite --skip-eslint
136
+
137
+ # Non-interactive mode
138
+ npx wsx init --no-interactive --use-decorators
139
+ ```
140
+
141
+ ## Architecture
142
+
143
+ The CLI is built with:
144
+ - **[Commander.js](https://github.com/tj/commander.js)**: Command-line interface
145
+ - **[Ink](https://github.com/vadimdemedes/ink)**: React for CLI apps
146
+ - **[Inquirer](https://github.com/SBoudrias/Inquirer.js)**: Interactive prompts
147
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,892 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import { existsSync as existsSync4, mkdirSync, writeFileSync as writeFileSync4 } from "fs";
8
+ import { join as join4 } from "path";
9
+ import React2 from "react";
10
+ import { render } from "ink";
11
+ import inquirer from "inquirer";
12
+
13
+ // src/ui/InitUI.tsx
14
+ import { useState, useEffect, useRef } from "react";
15
+ import { Box, Text, Newline } from "ink";
16
+ import Spinner from "ink-spinner";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ var InitUI = ({ onComplete, configSteps }) => {
19
+ const [steps, setSteps] = useState(
20
+ configSteps.map((step) => ({
21
+ name: step.name,
22
+ status: step.skip ? "skipped" : "pending",
23
+ message: step.skip ? "\u5DF2\u8DF3\u8FC7" : void 0
24
+ }))
25
+ );
26
+ const [allComplete, setAllComplete] = useState(false);
27
+ const executingRef = useRef(false);
28
+ useEffect(() => {
29
+ const allSkipped = steps.every((s) => s.status === "skipped");
30
+ if (allSkipped) {
31
+ setAllComplete(true);
32
+ setTimeout(() => onComplete(), 1e3);
33
+ return;
34
+ }
35
+ if (executingRef.current) {
36
+ return;
37
+ }
38
+ executingRef.current = true;
39
+ const runSteps = async () => {
40
+ const stepsToExecute = configSteps.filter((step) => !step.skip);
41
+ for (let i = 0; i < stepsToExecute.length; i++) {
42
+ const step = stepsToExecute[i];
43
+ setSteps((prev) => {
44
+ const newSteps = [...prev];
45
+ const index = newSteps.findIndex((s) => s.name === step.name);
46
+ if (index !== -1) {
47
+ newSteps[index] = {
48
+ ...newSteps[index],
49
+ status: "running"
50
+ };
51
+ }
52
+ return newSteps;
53
+ });
54
+ try {
55
+ const result = await step.execute();
56
+ setSteps((prev) => {
57
+ const newSteps = [...prev];
58
+ const index = newSteps.findIndex((s) => s.name === step.name);
59
+ if (index !== -1) {
60
+ newSteps[index] = {
61
+ ...newSteps[index],
62
+ status: result.success ? "completed" : "error",
63
+ message: result.message
64
+ };
65
+ }
66
+ return newSteps;
67
+ });
68
+ } catch (error) {
69
+ setSteps((prev) => {
70
+ const newSteps = [...prev];
71
+ const index = newSteps.findIndex((s) => s.name === step.name);
72
+ if (index !== -1) {
73
+ newSteps[index] = {
74
+ ...newSteps[index],
75
+ status: "error",
76
+ message: `\u9519\u8BEF: ${error instanceof Error ? error.message : String(error)}`
77
+ };
78
+ }
79
+ return newSteps;
80
+ });
81
+ }
82
+ await new Promise((resolve) => setTimeout(resolve, 200));
83
+ }
84
+ setAllComplete(true);
85
+ setTimeout(() => onComplete(), 1500);
86
+ };
87
+ const timer = setTimeout(() => {
88
+ runSteps().catch((error) => {
89
+ setSteps((prev) => {
90
+ const newSteps = [...prev];
91
+ const errorStep = newSteps.find((s) => s.status === "running");
92
+ if (errorStep) {
93
+ const index = newSteps.findIndex((s) => s.name === errorStep.name);
94
+ if (index !== -1) {
95
+ newSteps[index] = {
96
+ ...newSteps[index],
97
+ status: "error",
98
+ message: `\u9519\u8BEF: ${error instanceof Error ? error.message : String(error)}`
99
+ };
100
+ }
101
+ }
102
+ return newSteps;
103
+ });
104
+ setAllComplete(true);
105
+ setTimeout(() => onComplete(), 1500);
106
+ });
107
+ }, 300);
108
+ return () => {
109
+ clearTimeout(timer);
110
+ executingRef.current = false;
111
+ };
112
+ }, []);
113
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
114
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "\u{1F680} \u6B63\u5728\u521D\u59CB\u5316 WSXJS..." }),
115
+ /* @__PURE__ */ jsx(Newline, {}),
116
+ steps.map((step) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
117
+ step.status === "pending" && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
118
+ "\u23F3 ",
119
+ step.name,
120
+ ": \u7B49\u5F85\u4E2D..."
121
+ ] }),
122
+ step.status === "running" && /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
123
+ /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
124
+ " ",
125
+ step.name,
126
+ ": \u914D\u7F6E\u4E2D..."
127
+ ] }),
128
+ step.status === "completed" && /* @__PURE__ */ jsxs(Text, { color: "green", children: [
129
+ "\u2713 ",
130
+ step.name,
131
+ ": ",
132
+ step.message || "\u5B8C\u6210"
133
+ ] }),
134
+ step.status === "skipped" && /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
135
+ "\u2298 ",
136
+ step.name,
137
+ ": ",
138
+ step.message || "\u5DF2\u8DF3\u8FC7"
139
+ ] }),
140
+ step.status === "error" && /* @__PURE__ */ jsxs(Text, { color: "red", children: [
141
+ "\u2717 ",
142
+ step.name,
143
+ ": ",
144
+ step.message || "\u5931\u8D25"
145
+ ] })
146
+ ] }, step.name)),
147
+ allComplete && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
148
+ /* @__PURE__ */ jsx(Newline, {}),
149
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "\u2705 WSXJS \u521D\u59CB\u5316\u5B8C\u6210\uFF01" }),
150
+ /* @__PURE__ */ jsx(Newline, {}),
151
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u4E0B\u4E00\u6B65\uFF1A" }),
152
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "1. \u5B89\u88C5\u4F9D\u8D56: npm install" }),
153
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "2. \u5F00\u59CB\u5F00\u53D1: npm run dev" })
154
+ ] })
155
+ ] });
156
+ };
157
+
158
+ // src/config/configure-typescript.ts
159
+ import { existsSync, readFileSync, writeFileSync } from "fs";
160
+ import { join } from "path";
161
+ async function configureTypeScript(projectRoot, options = {}) {
162
+ const tsconfigPath = join(projectRoot, "tsconfig.json");
163
+ const { useTsConfigPackage = false, useDecorators = true } = options;
164
+ if (useTsConfigPackage) {
165
+ const config = {
166
+ extends: "@wsxjs/wsx-tsconfig/tsconfig.base.json",
167
+ compilerOptions: {
168
+ outDir: "./dist"
169
+ },
170
+ include: ["src/**/*"]
171
+ };
172
+ if (existsSync(tsconfigPath)) {
173
+ try {
174
+ const existing = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
175
+ const merged = mergeTsConfig(existing, config);
176
+ writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
177
+ return {
178
+ success: true,
179
+ message: "\u5DF2\u66F4\u65B0 tsconfig.json\uFF08\u4F7F\u7528 @wsxjs/wsx-tsconfig\uFF09",
180
+ created: false
181
+ };
182
+ } catch (error) {
183
+ return {
184
+ success: false,
185
+ message: `\u65E0\u6CD5\u89E3\u6790\u73B0\u6709 tsconfig.json: ${error}`,
186
+ created: false
187
+ };
188
+ }
189
+ } else {
190
+ writeFileSync(tsconfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
191
+ return {
192
+ success: true,
193
+ message: "\u5DF2\u521B\u5EFA tsconfig.json\uFF08\u4F7F\u7528 @wsxjs/wsx-tsconfig\uFF09",
194
+ created: true
195
+ };
196
+ }
197
+ } else {
198
+ const compilerOptions = {
199
+ jsx: "react-jsx",
200
+ jsxImportSource: "@wsxjs/wsx-core",
201
+ types: ["@wsxjs/wsx-core"]
202
+ };
203
+ if (useDecorators) {
204
+ compilerOptions.experimentalDecorators = true;
205
+ compilerOptions.useDefineForClassFields = false;
206
+ }
207
+ const config = {
208
+ compilerOptions,
209
+ include: ["src/**/*"]
210
+ };
211
+ if (existsSync(tsconfigPath)) {
212
+ try {
213
+ const existing = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
214
+ const merged = mergeTsConfig(existing, config);
215
+ writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
216
+ return {
217
+ success: true,
218
+ message: "\u5DF2\u66F4\u65B0 tsconfig.json",
219
+ created: false
220
+ };
221
+ } catch (error) {
222
+ return {
223
+ success: false,
224
+ message: `\u65E0\u6CD5\u89E3\u6790\u73B0\u6709 tsconfig.json: ${error}`,
225
+ created: false
226
+ };
227
+ }
228
+ } else {
229
+ writeFileSync(tsconfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
230
+ return {
231
+ success: true,
232
+ message: "\u5DF2\u521B\u5EFA tsconfig.json",
233
+ created: true
234
+ };
235
+ }
236
+ }
237
+ }
238
+ function mergeTsConfig(existing, newConfig) {
239
+ const merged = { ...existing };
240
+ if (newConfig.compilerOptions) {
241
+ merged.compilerOptions = {
242
+ ...existing.compilerOptions,
243
+ ...newConfig.compilerOptions
244
+ };
245
+ }
246
+ if (newConfig.include) {
247
+ const existingInclude = existing.include || [];
248
+ const newInclude = newConfig.include || [];
249
+ merged.include = [.../* @__PURE__ */ new Set([...existingInclude, ...newInclude])];
250
+ }
251
+ if (existing.extends && !newConfig.extends) {
252
+ } else if (newConfig.extends) {
253
+ merged.extends = newConfig.extends;
254
+ }
255
+ return merged;
256
+ }
257
+
258
+ // src/config/configure-vite.ts
259
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
260
+ import { join as join2 } from "path";
261
+ async function configureVite(projectRoot) {
262
+ const viteConfigPaths = [
263
+ { path: join2(projectRoot, "vite.config.ts"), ext: "ts" },
264
+ { path: join2(projectRoot, "vite.config.js"), ext: "js" },
265
+ { path: join2(projectRoot, "vite.config.mts"), ext: "mts" },
266
+ { path: join2(projectRoot, "vite.config.mjs"), ext: "mjs" }
267
+ ];
268
+ let existingConfig = null;
269
+ for (const configPath of viteConfigPaths) {
270
+ if (existsSync2(configPath.path)) {
271
+ existingConfig = configPath;
272
+ break;
273
+ }
274
+ }
275
+ if (existingConfig) {
276
+ try {
277
+ const content = readFileSync2(existingConfig.path, "utf-8");
278
+ const hasWsxImport = content.includes("@wsxjs/wsx-vite-plugin");
279
+ const hasWsxPlugin = content.includes("wsx()") || content.includes("wsx(");
280
+ if (hasWsxImport && hasWsxPlugin) {
281
+ return {
282
+ success: true,
283
+ message: "vite.config \u5DF2\u5305\u542B WSX \u63D2\u4EF6",
284
+ created: false
285
+ };
286
+ }
287
+ const updated = addWsxPluginToViteConfig(content, existingConfig.ext);
288
+ writeFileSync2(existingConfig.path, updated, "utf-8");
289
+ return {
290
+ success: true,
291
+ message: `\u5DF2\u66F4\u65B0 ${existingConfig.path}`,
292
+ created: false
293
+ };
294
+ } catch (error) {
295
+ return {
296
+ success: false,
297
+ message: `\u65E0\u6CD5\u8BFB\u53D6/\u66F4\u65B0 vite.config: ${error}`,
298
+ created: false
299
+ };
300
+ }
301
+ } else {
302
+ const configContent = `import { defineConfig } from "vite";
303
+ import { wsx } from "@wsxjs/wsx-vite-plugin";
304
+
305
+ export default defineConfig({
306
+ plugins: [wsx()],
307
+ });
308
+ `;
309
+ const newConfigPath = join2(projectRoot, "vite.config.ts");
310
+ writeFileSync2(newConfigPath, configContent, "utf-8");
311
+ return {
312
+ success: true,
313
+ message: "\u5DF2\u521B\u5EFA vite.config.ts",
314
+ created: true
315
+ };
316
+ }
317
+ }
318
+ function addWsxPluginToViteConfig(content, _ext) {
319
+ const hasWsxImport = content.includes("@wsxjs/wsx-vite-plugin");
320
+ const hasDefineConfigImport = content.includes("defineConfig");
321
+ let updated = content;
322
+ if (!hasWsxImport) {
323
+ const importRegex = /^import\s+.*?from\s+['"].*?['"];?$/gm;
324
+ const imports = content.match(importRegex);
325
+ if (imports && imports.length > 0) {
326
+ const lastImport = imports[imports.length - 1];
327
+ const lastImportIndex = content.lastIndexOf(lastImport);
328
+ const insertIndex = lastImportIndex + lastImport.length;
329
+ const wsxImport = hasDefineConfigImport ? `import { wsx } from "@wsxjs/wsx-vite-plugin";
330
+ ` : `import { defineConfig } from "vite";
331
+ import { wsx } from "@wsxjs/wsx-vite-plugin";
332
+ `;
333
+ updated = content.slice(0, insertIndex) + "\n" + wsxImport + content.slice(insertIndex);
334
+ } else {
335
+ const wsxImport = hasDefineConfigImport ? `import { wsx } from "@wsxjs/wsx-vite-plugin";
336
+
337
+ ` : `import { defineConfig } from "vite";
338
+ import { wsx } from "@wsxjs/wsx-vite-plugin";
339
+
340
+ `;
341
+ updated = wsxImport + content;
342
+ }
343
+ }
344
+ if (!updated.includes("wsx()") && !updated.includes("wsx(")) {
345
+ const pluginsArrayRegex = /plugins:\s*\[([^\]]*)\]/s;
346
+ const match = updated.match(pluginsArrayRegex);
347
+ if (match) {
348
+ const pluginsContent = match[1].trim();
349
+ const newPluginsContent = pluginsContent ? `${pluginsContent}
350
+ wsx(),` : `wsx(),`;
351
+ updated = updated.replace(
352
+ pluginsArrayRegex,
353
+ `plugins: [
354
+ ${newPluginsContent}
355
+ ]`
356
+ );
357
+ } else {
358
+ const defineConfigRegex = /defineConfig\s*\(\s*\{([^}]*)\}\s*\)/s;
359
+ const defineMatch = updated.match(defineConfigRegex);
360
+ if (defineMatch) {
361
+ const configContent = defineMatch[1].trim();
362
+ const newConfigContent = configContent ? `${configContent}
363
+ plugins: [wsx()],` : `plugins: [wsx()],`;
364
+ updated = updated.replace(
365
+ defineConfigRegex,
366
+ `defineConfig({
367
+ ${newConfigContent}
368
+ })`
369
+ );
370
+ }
371
+ }
372
+ }
373
+ return updated;
374
+ }
375
+
376
+ // src/config/configure-eslint.ts
377
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
378
+ import { join as join3 } from "path";
379
+ async function configureESLint(projectRoot, options = {}) {
380
+ const flatConfigPath = join3(projectRoot, "eslint.config.js");
381
+ const flatConfigMjsPath = join3(projectRoot, "eslint.config.mjs");
382
+ const legacyConfigPath = join3(projectRoot, ".eslintrc.js");
383
+ const legacyConfigJsonPath = join3(projectRoot, ".eslintrc.json");
384
+ const hasFlatConfig = existsSync3(flatConfigPath) || existsSync3(flatConfigMjsPath);
385
+ const hasLegacyConfig = existsSync3(legacyConfigPath) || existsSync3(legacyConfigJsonPath);
386
+ const useFlatConfig = options.useFlatConfig ?? (hasFlatConfig || !hasLegacyConfig);
387
+ if (useFlatConfig) {
388
+ return await configureFlatESLint(projectRoot);
389
+ } else {
390
+ return await configureLegacyESLint(projectRoot);
391
+ }
392
+ }
393
+ async function configureFlatESLint(projectRoot) {
394
+ const configPath = existsSync3(join3(projectRoot, "eslint.config.js")) ? join3(projectRoot, "eslint.config.js") : join3(projectRoot, "eslint.config.mjs");
395
+ if (existsSync3(configPath)) {
396
+ try {
397
+ const content = readFileSync3(configPath, "utf-8");
398
+ const hasWsxImport = content.includes("@wsxjs/eslint-plugin-wsx");
399
+ const hasWsxPlugin = content.includes("wsx:") || content.includes("wsxPlugin");
400
+ if (hasWsxImport && hasWsxPlugin) {
401
+ return {
402
+ success: true,
403
+ message: "eslint.config \u5DF2\u5305\u542B WSX \u63D2\u4EF6",
404
+ created: false
405
+ };
406
+ }
407
+ const updated = addWsxPluginToFlatConfig(content);
408
+ writeFileSync3(configPath, updated, "utf-8");
409
+ return {
410
+ success: true,
411
+ message: `\u5DF2\u66F4\u65B0 ${configPath}`,
412
+ created: false
413
+ };
414
+ } catch (error) {
415
+ return {
416
+ success: false,
417
+ message: `\u65E0\u6CD5\u8BFB\u53D6/\u66F4\u65B0 eslint.config: ${error}`,
418
+ created: false
419
+ };
420
+ }
421
+ } else {
422
+ const configContent = `import wsxPlugin from "@wsxjs/eslint-plugin-wsx";
423
+
424
+ export default [
425
+ {
426
+ files: ["**/*.{ts,tsx,js,jsx,wsx}"],
427
+ plugins: { wsx: wsxPlugin },
428
+ rules: {
429
+ "wsx/no-react-imports": "error",
430
+ "wsx/render-method-required": "error",
431
+ "wsx/state-requires-initial-value": "error",
432
+ "wsx/require-jsx-import-source": "error",
433
+ },
434
+ },
435
+ ];
436
+ `;
437
+ writeFileSync3(configPath, configContent, "utf-8");
438
+ return {
439
+ success: true,
440
+ message: `\u5DF2\u521B\u5EFA ${configPath}`,
441
+ created: true
442
+ };
443
+ }
444
+ }
445
+ async function configureLegacyESLint(projectRoot) {
446
+ const configPath = existsSync3(join3(projectRoot, ".eslintrc.js")) ? join3(projectRoot, ".eslintrc.js") : join3(projectRoot, ".eslintrc.json");
447
+ if (existsSync3(configPath)) {
448
+ try {
449
+ const content = readFileSync3(configPath, "utf-8");
450
+ const hasWsxPlugin = content.includes("@wsxjs/eslint-plugin-wsx") || content.includes("@wsxjs/wsx");
451
+ if (hasWsxPlugin) {
452
+ return {
453
+ success: true,
454
+ message: ".eslintrc \u5DF2\u5305\u542B WSX \u63D2\u4EF6",
455
+ created: false
456
+ };
457
+ }
458
+ return {
459
+ success: false,
460
+ message: "\u68C0\u6D4B\u5230 Legacy ESLint \u914D\u7F6E\uFF0C\u5EFA\u8BAE\u8FC1\u79FB\u5230 Flat Config \u683C\u5F0F",
461
+ created: false
462
+ };
463
+ } catch (error) {
464
+ return {
465
+ success: false,
466
+ message: `\u65E0\u6CD5\u8BFB\u53D6/\u66F4\u65B0 .eslintrc: ${error}`,
467
+ created: false
468
+ };
469
+ }
470
+ } else {
471
+ const configContent = `module.exports = {
472
+ plugins: ["@wsxjs/wsx"],
473
+ rules: {
474
+ "@wsxjs/wsx/no-react-imports": "error",
475
+ "@wsxjs/wsx/render-method-required": "error",
476
+ "@wsxjs/wsx/state-requires-initial-value": "error",
477
+ },
478
+ };
479
+ `;
480
+ writeFileSync3(join3(projectRoot, ".eslintrc.js"), configContent, "utf-8");
481
+ return {
482
+ success: true,
483
+ message: "\u5DF2\u521B\u5EFA .eslintrc.js\uFF08\u5EFA\u8BAE\u4F7F\u7528 Flat Config\uFF09",
484
+ created: true
485
+ };
486
+ }
487
+ }
488
+ function addWsxPluginToFlatConfig(content) {
489
+ const hasWsxImport = content.includes("@wsxjs/eslint-plugin-wsx");
490
+ let updated = content;
491
+ if (!hasWsxImport) {
492
+ const importRegex = /^import\s+.*?from\s+['"].*?['"];?$/gm;
493
+ const imports = content.match(importRegex);
494
+ if (imports && imports.length > 0) {
495
+ const lastImport = imports[imports.length - 1];
496
+ const lastImportIndex = content.lastIndexOf(lastImport);
497
+ const insertIndex = lastImportIndex + lastImport.length;
498
+ const wsxImport = `import wsxPlugin from "@wsxjs/eslint-plugin-wsx";
499
+ `;
500
+ updated = content.slice(0, insertIndex) + "\n" + wsxImport + content.slice(insertIndex);
501
+ } else {
502
+ updated = `import wsxPlugin from "@wsxjs/eslint-plugin-wsx";
503
+
504
+ ` + content;
505
+ }
506
+ }
507
+ if (!updated.includes("wsx:") && !updated.includes("wsxPlugin")) {
508
+ const configObjectRegex = /\{\s*files:\s*\[([^\]]+)\],([^}]*)\}/s;
509
+ const match = updated.match(configObjectRegex);
510
+ if (match) {
511
+ const configContent = match[2].trim();
512
+ let newConfigContent = configContent;
513
+ if (!configContent.includes("plugins:")) {
514
+ newConfigContent = `plugins: { wsx: wsxPlugin },
515
+ ${newConfigContent}`;
516
+ } else {
517
+ const pluginsRegex = /plugins:\s*\{([^}]*)\}/s;
518
+ const pluginsMatch = newConfigContent.match(pluginsRegex);
519
+ if (pluginsMatch) {
520
+ const pluginsContent = pluginsMatch[1].trim();
521
+ newConfigContent = newConfigContent.replace(
522
+ pluginsRegex,
523
+ `plugins: { ${pluginsContent}
524
+ wsx: wsxPlugin,
525
+ }`
526
+ );
527
+ }
528
+ }
529
+ if (!configContent.includes("rules:")) {
530
+ newConfigContent = `${newConfigContent}
531
+ rules: {
532
+ "wsx/no-react-imports": "error",
533
+ "wsx/render-method-required": "error",
534
+ "wsx/state-requires-initial-value": "error",
535
+ "wsx/require-jsx-import-source": "error",
536
+ },`;
537
+ } else {
538
+ const rulesRegex = /rules:\s*\{([^}]*)\}/s;
539
+ const rulesMatch = newConfigContent.match(rulesRegex);
540
+ if (rulesMatch) {
541
+ const rulesContent = rulesMatch[1].trim();
542
+ const newRules = `"wsx/no-react-imports": "error",
543
+ "wsx/render-method-required": "error",
544
+ "wsx/state-requires-initial-value": "error",
545
+ "wsx/require-jsx-import-source": "error",`;
546
+ newConfigContent = newConfigContent.replace(
547
+ rulesRegex,
548
+ `rules: {
549
+ ${newRules}
550
+ ${rulesContent}
551
+ }`
552
+ );
553
+ }
554
+ }
555
+ updated = updated.replace(
556
+ configObjectRegex,
557
+ `{
558
+ files: [${match[1]}],
559
+ ${newConfigContent}
560
+ }`
561
+ );
562
+ } else {
563
+ const exportDefaultRegex = /export\s+default\s*\[([^\]]*)\]/s;
564
+ const exportMatch = updated.match(exportDefaultRegex);
565
+ if (exportMatch) {
566
+ const existingConfigs = exportMatch[1].trim();
567
+ const newConfig = ` {
568
+ files: ["**/*.{ts,tsx,js,jsx,wsx}"],
569
+ plugins: { wsx: wsxPlugin },
570
+ rules: {
571
+ "wsx/no-react-imports": "error",
572
+ "wsx/render-method-required": "error",
573
+ "wsx/state-requires-initial-value": "error",
574
+ "wsx/require-jsx-import-source": "error",
575
+ },
576
+ },`;
577
+ updated = updated.replace(
578
+ exportDefaultRegex,
579
+ `export default [
580
+ ${existingConfigs ? existingConfigs + ",\n " : ""}${newConfig}
581
+ ]`
582
+ );
583
+ }
584
+ }
585
+ }
586
+ return updated;
587
+ }
588
+
589
+ // src/commands/init.ts
590
+ async function initWSX(options = {}) {
591
+ const projectRoot = process.cwd();
592
+ let finalOptions = { ...options };
593
+ if (options.interactive !== false) {
594
+ finalOptions = await promptUserOptions(options);
595
+ }
596
+ const results = [];
597
+ let resolveComplete;
598
+ const completePromise = new Promise((resolve) => {
599
+ resolveComplete = resolve;
600
+ });
601
+ const configSteps = [
602
+ {
603
+ name: "TypeScript",
604
+ skip: finalOptions.skipTsconfig ?? false,
605
+ execute: async () => {
606
+ const tsOptions = {
607
+ useTsConfigPackage: finalOptions.useTsconfigPackage,
608
+ useDecorators: finalOptions.useDecorators ?? true
609
+ };
610
+ const result = await configureTypeScript(projectRoot, tsOptions);
611
+ results.push({ name: "TypeScript", ...result });
612
+ return result;
613
+ }
614
+ },
615
+ {
616
+ name: "Vite",
617
+ skip: finalOptions.skipVite ?? false,
618
+ execute: async () => {
619
+ const result = await configureVite(projectRoot);
620
+ results.push({ name: "Vite", ...result });
621
+ return result;
622
+ }
623
+ },
624
+ {
625
+ name: "wsx.d.ts",
626
+ skip: finalOptions.skipTypes ?? false,
627
+ execute: async () => {
628
+ const result = await generateWsxTypes(projectRoot);
629
+ const stepResult = {
630
+ success: result.success,
631
+ message: result.message,
632
+ created: result.created
633
+ };
634
+ results.push({ name: "wsx.d.ts", ...stepResult });
635
+ return stepResult;
636
+ }
637
+ },
638
+ {
639
+ name: "ESLint",
640
+ skip: finalOptions.skipEslint ?? false,
641
+ execute: async () => {
642
+ const eslintOptions = {
643
+ useFlatConfig: void 0
644
+ // 自动检测
645
+ };
646
+ const result = await configureESLint(projectRoot, eslintOptions);
647
+ results.push({ name: "ESLint", ...result });
648
+ return result;
649
+ }
650
+ }
651
+ ];
652
+ const { unmount } = render(
653
+ React2.createElement(InitUI, {
654
+ onComplete: () => {
655
+ resolveComplete();
656
+ },
657
+ options: finalOptions,
658
+ // Keep for potential future use
659
+ configSteps
660
+ })
661
+ );
662
+ await completePromise;
663
+ unmount();
664
+ console.log("\n\u914D\u7F6E\u5B8C\u6210\u6458\u8981:");
665
+ results.forEach((result) => {
666
+ const status = result.success ? "\u2713" : "\u2717";
667
+ console.log(` ${status} ${result.name}: ${result.message}`);
668
+ });
669
+ }
670
+ async function promptUserOptions(existingOptions) {
671
+ const answers = await inquirer.prompt([
672
+ {
673
+ type: "confirm",
674
+ name: "useDecorators",
675
+ message: "\u662F\u5426\u4F7F\u7528\u88C5\u9970\u5668\uFF08@state\uFF09\uFF1F",
676
+ default: existingOptions.useDecorators ?? true,
677
+ when: existingOptions.useDecorators === void 0
678
+ },
679
+ {
680
+ type: "confirm",
681
+ name: "useTsconfigPackage",
682
+ message: "\u662F\u5426\u4F7F\u7528 @wsxjs/wsx-tsconfig \u5305\uFF1F",
683
+ default: existingOptions.useTsconfigPackage ?? false,
684
+ when: existingOptions.useTsconfigPackage === void 0
685
+ },
686
+ {
687
+ type: "confirm",
688
+ name: "configureTsconfig",
689
+ message: "\u662F\u5426\u914D\u7F6E TypeScript\uFF1F",
690
+ default: !existingOptions.skipTsconfig,
691
+ when: existingOptions.skipTsconfig === void 0
692
+ },
693
+ {
694
+ type: "confirm",
695
+ name: "configureVite",
696
+ message: "\u662F\u5426\u914D\u7F6E Vite\uFF1F",
697
+ default: !existingOptions.skipVite,
698
+ when: existingOptions.skipVite === void 0
699
+ },
700
+ {
701
+ type: "confirm",
702
+ name: "configureEslint",
703
+ message: "\u662F\u5426\u914D\u7F6E ESLint\uFF1F",
704
+ default: !existingOptions.skipEslint,
705
+ when: existingOptions.skipEslint === void 0
706
+ },
707
+ {
708
+ type: "confirm",
709
+ name: "generateTypes",
710
+ message: "\u662F\u5426\u751F\u6210 wsx.d.ts\uFF1F",
711
+ default: !existingOptions.skipTypes,
712
+ when: existingOptions.skipTypes === void 0
713
+ }
714
+ ]);
715
+ return {
716
+ ...existingOptions,
717
+ useDecorators: answers.useDecorators ?? existingOptions.useDecorators,
718
+ useTsconfigPackage: answers.useTsconfigPackage ?? existingOptions.useTsconfigPackage,
719
+ skipTsconfig: !answers.configureTsconfig,
720
+ skipVite: !answers.configureVite,
721
+ skipEslint: !answers.configureEslint,
722
+ skipTypes: !answers.generateTypes
723
+ };
724
+ }
725
+ async function generateWsxTypes(projectRoot) {
726
+ const typesDir = join4(projectRoot, "src", "types");
727
+ const wsxDtsPath = join4(typesDir, "wsx.d.ts");
728
+ if (existsSync4(wsxDtsPath)) {
729
+ return {
730
+ success: true,
731
+ message: "wsx.d.ts \u5DF2\u5B58\u5728",
732
+ created: false
733
+ };
734
+ }
735
+ if (!existsSync4(typesDir)) {
736
+ mkdirSync(typesDir, { recursive: true });
737
+ }
738
+ const wsxDtsContent = `// Type declaration for .wsx files
739
+ // Auto-generated by @wsxjs/cli
740
+ // You can modify this file if needed
741
+
742
+ declare module "*.wsx" {
743
+ import { WebComponent, LightComponent } from "@wsxjs/wsx-core";
744
+ const Component: new (...args: unknown[]) => WebComponent | LightComponent;
745
+ export default Component;
746
+ }
747
+ `;
748
+ writeFileSync4(wsxDtsPath, wsxDtsContent, "utf-8");
749
+ return {
750
+ success: true,
751
+ message: "\u5DF2\u751F\u6210 wsx.d.ts",
752
+ created: true
753
+ };
754
+ }
755
+
756
+ // src/commands/check-config.ts
757
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
758
+ import { join as join5 } from "path";
759
+ import chalk from "chalk";
760
+ async function checkConfig(projectRoot = process.cwd()) {
761
+ const result = {
762
+ hasWsxTypes: false,
763
+ hasTsConfig: false,
764
+ hasViteConfig: false,
765
+ tsConfigValid: false,
766
+ viteConfigValid: false,
767
+ issues: [],
768
+ suggestions: []
769
+ };
770
+ const wsxDtsPath = join5(projectRoot, "src", "types", "wsx.d.ts");
771
+ result.hasWsxTypes = existsSync5(wsxDtsPath);
772
+ if (!result.hasWsxTypes) {
773
+ result.issues.push("Missing src/types/wsx.d.ts");
774
+ result.suggestions.push("Run: npx wsx init");
775
+ }
776
+ const tsconfigPath = join5(projectRoot, "tsconfig.json");
777
+ result.hasTsConfig = existsSync5(tsconfigPath);
778
+ if (result.hasTsConfig) {
779
+ try {
780
+ const tsconfigContent = readFileSync4(tsconfigPath, "utf-8");
781
+ const tsconfig = JSON.parse(tsconfigContent);
782
+ const compilerOptions = tsconfig.compilerOptions || {};
783
+ result.tsConfigValid = true;
784
+ if (compilerOptions.jsx !== "react-jsx") {
785
+ result.issues.push('tsconfig.json: jsx should be "react-jsx"');
786
+ result.suggestions.push(
787
+ 'Add to tsconfig.json: "compilerOptions": { "jsx": "react-jsx" }'
788
+ );
789
+ result.tsConfigValid = false;
790
+ }
791
+ if (compilerOptions.jsxImportSource !== "@wsxjs/wsx-core") {
792
+ result.issues.push('tsconfig.json: jsxImportSource should be "@wsxjs/wsx-core"');
793
+ result.suggestions.push(
794
+ 'Add to tsconfig.json: "compilerOptions": { "jsxImportSource": "@wsxjs/wsx-core" }'
795
+ );
796
+ result.tsConfigValid = false;
797
+ }
798
+ if (compilerOptions.experimentalDecorators !== true) {
799
+ result.suggestions.push(
800
+ "Recommended: Enable experimentalDecorators for @state decorator support"
801
+ );
802
+ }
803
+ if (compilerOptions.useDefineForClassFields !== false) {
804
+ result.suggestions.push(
805
+ "Recommended: Set useDefineForClassFields to false for @state decorator support"
806
+ );
807
+ }
808
+ } catch {
809
+ result.issues.push("tsconfig.json: Invalid JSON format");
810
+ result.tsConfigValid = false;
811
+ }
812
+ } else {
813
+ result.issues.push("Missing tsconfig.json");
814
+ result.suggestions.push("Create a tsconfig.json with WSX-compatible settings");
815
+ }
816
+ const viteConfigPaths = [
817
+ join5(projectRoot, "vite.config.ts"),
818
+ join5(projectRoot, "vite.config.js"),
819
+ join5(projectRoot, "vite.config.mts"),
820
+ join5(projectRoot, "vite.config.mjs")
821
+ ];
822
+ for (const viteConfigPath of viteConfigPaths) {
823
+ if (existsSync5(viteConfigPath)) {
824
+ result.hasViteConfig = true;
825
+ try {
826
+ const viteConfigContent = readFileSync4(viteConfigPath, "utf-8");
827
+ const hasWsxImport = viteConfigContent.includes("@wsxjs/wsx-vite-plugin");
828
+ const hasWsxPlugin = viteConfigContent.includes("wsx()");
829
+ if (!hasWsxImport || !hasWsxPlugin) {
830
+ result.issues.push("vite.config: Missing @wsxjs/wsx-vite-plugin");
831
+ result.suggestions.push(
832
+ 'Add to vite.config: import { wsx } from "@wsxjs/wsx-vite-plugin" and use wsx() in plugins array'
833
+ );
834
+ result.viteConfigValid = false;
835
+ } else {
836
+ result.viteConfigValid = true;
837
+ }
838
+ } catch {
839
+ result.issues.push("vite.config: Unable to read file");
840
+ }
841
+ break;
842
+ }
843
+ }
844
+ if (!result.hasViteConfig) {
845
+ result.suggestions.push("Recommended: Create vite.config.ts with @wsxjs/wsx-vite-plugin");
846
+ }
847
+ return result;
848
+ }
849
+ async function displayCheckResults(result) {
850
+ console.log(chalk.blue.bold("\n\u{1F50D} WSX Configuration Check\n"));
851
+ console.log(chalk.bold("Status:"));
852
+ console.log(
853
+ ` wsx.d.ts: ${result.hasWsxTypes ? chalk.green("\u2713 Found") : chalk.red("\u2717 Missing")}`
854
+ );
855
+ console.log(
856
+ ` tsconfig.json: ${result.hasTsConfig ? result.tsConfigValid ? chalk.green("\u2713 Valid") : chalk.yellow("\u26A0 Needs update") : chalk.red("\u2717 Missing")}`
857
+ );
858
+ console.log(
859
+ ` vite.config: ${result.hasViteConfig ? result.viteConfigValid ? chalk.green("\u2713 Valid") : chalk.yellow("\u26A0 Needs update") : chalk.gray("- Not found")}`
860
+ );
861
+ if (result.issues.length > 0) {
862
+ console.log(chalk.red.bold("\n\u274C Issues:"));
863
+ result.issues.forEach((issue) => {
864
+ console.log(chalk.red(` \u2022 ${issue}`));
865
+ });
866
+ }
867
+ if (result.suggestions.length > 0) {
868
+ console.log(chalk.yellow.bold("\n\u{1F4A1} Suggestions:"));
869
+ result.suggestions.forEach((suggestion) => {
870
+ console.log(chalk.yellow(` \u2022 ${suggestion}`));
871
+ });
872
+ }
873
+ if (result.issues.length === 0) {
874
+ console.log(
875
+ chalk.green.bold("\n\u2705 All checks passed! Your WSX configuration looks good.\n")
876
+ );
877
+ } else {
878
+ console.log(chalk.yellow.bold("\n\u26A0\uFE0F Some issues need attention.\n"));
879
+ }
880
+ }
881
+
882
+ // src/index.ts
883
+ var program = new Command();
884
+ program.name("wsx").description("WSXJS CLI tool").version("0.0.17");
885
+ program.command("init").description("Initialize WSXJS in your project").option("--skip-tsconfig", "Skip TypeScript configuration").option("--skip-vite", "Skip Vite configuration").option("--skip-eslint", "Skip ESLint configuration").option("--skip-types", "Skip wsx.d.ts generation").option("--use-tsconfig-package", "Use @wsxjs/wsx-tsconfig package").option("--use-decorators", "Enable decorator support (@state)").option("--no-interactive", "Skip interactive prompts").action(async (options) => {
886
+ await initWSX(options);
887
+ });
888
+ program.command("check").description("Check WSX configuration").action(async () => {
889
+ const result = await checkConfig();
890
+ await displayCheckResults(result);
891
+ });
892
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@wsxjs/cli",
3
+ "version": "0.0.18",
4
+ "description": "CLI tool for WSXJS initialization and configuration",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "wsx": "./dist/index.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/wsxjs/wsxjs.git",
14
+ "directory": "packages/cli"
15
+ },
16
+ "author": "Albert Li <albert.li@wsxjs.com>",
17
+ "license": "MIT",
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "keywords": [
23
+ "wsx",
24
+ "cli",
25
+ "web-components",
26
+ "typescript",
27
+ "init"
28
+ ],
29
+ "dependencies": {
30
+ "chalk": "^5.3.0",
31
+ "commander": "^11.0.0",
32
+ "ink": "^4.4.1",
33
+ "ink-spinner": "^5.0.0",
34
+ "inquirer": "^10.2.2",
35
+ "react": "^18.2.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/inquirer": "^9.0.0",
39
+ "@types/jest": "^29.0.0",
40
+ "@types/node": "^20.0.0",
41
+ "@types/react": "^18.2.0",
42
+ "jest": "^29.0.0",
43
+ "ts-jest": "^29.0.0",
44
+ "tsup": "^8.0.0",
45
+ "typescript": "^5.0.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup src/index.ts --format esm --dts --clean",
52
+ "dev": "tsup src/index.ts --format esm --dts --watch",
53
+ "test": "jest",
54
+ "test:watch": "jest --watch",
55
+ "test:coverage": "jest --coverage",
56
+ "lint": "eslint .",
57
+ "lint:fix": "eslint . --fix",
58
+ "typecheck": "tsc --noEmit"
59
+ }
60
+ }