@shellui/cli 0.0.1 → 0.0.5

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 CHANGED
@@ -1,80 +1,87 @@
1
- # ShellUI
1
+ # @shellui/cli
2
2
 
3
- > A lightweight microfrontend shell to ship apps faster.
4
-
5
- ShellUI is a CLI tool that spins up a React-based microfrontend shell. It is powered by Vite and designed to be easily configurable.
6
-
7
- ## Features
8
-
9
- - 🚀 **Fast**: Built on top of Vite for instant server start.
10
- - ⚛️ **React-based**: The shell is a React application.
11
- - ⚙️ **Configurable**: Loads configuration from `shellui.json` in your project root.
12
- - 🔌 **Injectable Config**: Configuration is automatically injected into the shell application.
3
+ ShellUI CLI - Command-line tool for ShellUI
13
4
 
14
5
  ## Installation
15
6
 
16
- Install ShellUI globally or locally:
17
-
18
7
  ```bash
19
- npm install -g shellui
8
+ npm install -g @shellui/cli
20
9
  ```
21
10
 
22
- Or install it as a dev dependency in your project:
11
+ Or install as a dev dependency:
23
12
 
24
13
  ```bash
25
- npm install --save-dev shellui
14
+ npm install --save-dev @shellui/cli
26
15
  ```
27
16
 
28
17
  ## Usage
29
18
 
30
- After installation, you can use the `shellui` command directly:
31
-
32
19
  ```bash
33
- npx shellui start [path/to/project]
20
+ shellui start [path/to/project]
21
+ shellui build [path/to/project]
34
22
  ```
35
23
 
36
- Or if installed globally:
24
+ ### Commands
37
25
 
38
- ```bash
39
- shellui start [path/to/project]
40
- ```
26
+ - **start** - Start the ShellUI development server
41
27
 
42
- You can also use it via npm scripts in your `package.json`:
28
+ ```bash
29
+ shellui start
30
+ shellui start ./my-project
31
+ ```
43
32
 
44
- ```json
45
- {
46
- "scripts": {
47
- "start": "shellui start",
48
- "build": "shellui build"
49
- }
50
- }
51
- ```
33
+ - **build** - Build the ShellUI application for production
34
+ ```bash
35
+ shellui build
36
+ shellui build ./my-project
37
+ ```
52
38
 
53
- By default, it looks for configuration in the current directory.
39
+ ## Project Structure
54
40
 
55
- ## Configuration
41
+ The CLI is organized for maintainability with a clear separation of concerns:
56
42
 
57
- ShellUI looks for a `shellui.json` file in your project root.
43
+ ```
44
+ src/
45
+ ├── cli.js # Main CLI orchestrator
46
+ ├── commands/ # All commands in separate files
47
+ │ ├── index.js # Command registry
48
+ │ ├── start.js # Start command implementation
49
+ │ └── build.js # Build command implementation
50
+ └── utils/ # Utility functions
51
+ ├── index.js # Utilities export
52
+ ├── config.js # Configuration loading
53
+ └── vite.js # Vite-specific utilities
54
+ ```
55
+
56
+ ## Development
57
+
58
+ ### Adding a New Command
58
59
 
59
- **Example `shellui.json`:**
60
+ 1. Create a new file in `src/commands/` (e.g., `new-command.js`)
61
+ 2. Export a command function:
60
62
 
61
- ```json
62
- {
63
- "port": 4000
63
+ ```javascript
64
+ export async function newCommandCommand(args) {
65
+ // Command implementation
64
66
  }
65
67
  ```
66
68
 
67
- - **port**: The port number to start the server on (default: `3000`).
69
+ 3. Register it in `src/cli.js`:
68
70
 
69
- ## Development
71
+ ```javascript
72
+ import { newCommandCommand } from './commands/index.js';
70
73
 
71
- This repository contains the core logic for the shell.
74
+ cli.command('new-command [args]', 'Description').action(newCommandCommand);
75
+ ```
76
+
77
+ 4. Export it from `src/commands/index.js`:
72
78
 
73
- - `bin/shellui.js`: The CLI entry point.
74
- - `src/cli.js`: Main CLI logic using `cac` and `vite`.
75
- - `src/app.jsx`: The shell's React application entry point.
79
+ ```javascript
80
+ export { newCommandCommand } from './new-command.js';
81
+ ```
82
+
83
+ See `src/commands/README.md` for more details.
76
84
 
77
85
  ## License
78
86
 
79
87
  MIT
80
-
package/bin/shellui.js CHANGED
@@ -1,4 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import '../src/cli.js';
4
-
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@shellui/cli",
3
- "version": "0.0.1",
4
- "description": "ShellUI - a lightweight microfrontend shell to ship apps faster",
5
- "main": "index.js",
3
+ "version": "0.0.5",
4
+ "description": "ShellUI CLI - Command-line tool for ShellUI",
5
+ "main": "src/cli.js",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "shellui": "./bin/shellui.js"
@@ -13,27 +13,34 @@
13
13
  "README.md",
14
14
  "package.json"
15
15
  ],
16
- "scripts": {
17
- "start": "node bin/shellui.js start",
18
- "build": "node bin/shellui.js build",
19
- "test": "echo \"Error: no test specified\" && exit 1"
20
- },
21
16
  "keywords": [
22
- "microfrontend",
23
- "shell",
17
+ "shellui",
24
18
  "cli",
25
- "vite",
26
- "react",
27
- "frontend"
19
+ "microfrontend",
20
+ "vite"
28
21
  ],
29
22
  "author": "ShellUI",
30
23
  "license": "MIT",
31
24
  "dependencies": {
25
+ "@tailwindcss/postcss": "^4.1.18",
32
26
  "@vitejs/plugin-react": "^5.1.2",
27
+ "autoprefixer": "^10.4.23",
33
28
  "cac": "^6.7.14",
34
29
  "picocolors": "^1.1.1",
35
- "react": "^19.2.3",
36
- "react-dom": "^19.2.3",
37
- "vite": "^7.3.0"
30
+ "tsx": "^4.21.0",
31
+ "vite": "7.3.1",
32
+ "workbox-build": "^7.1.0",
33
+ "@shellui/core": "0.0.5"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "devDependencies": {
39
+ "vitest": "^4.0.16"
40
+ },
41
+ "scripts": {
42
+ "build": "echo 'CLI build complete'",
43
+ "test": "node scripts/test.js",
44
+ "test:watch": "vitest"
38
45
  }
39
- }
46
+ }
package/src/cli.js CHANGED
@@ -1,127 +1,14 @@
1
1
  import { cac } from 'cac';
2
- import { createServer, build } from 'vite';
3
- import react from '@vitejs/plugin-react';
4
- import path from 'path';
5
- import fs from 'fs';
6
- import { fileURLToPath } from 'url';
7
- import pc from 'picocolors';
2
+ import { startCommand, buildCommand } from './commands/index.js';
8
3
 
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
4
  const cli = cac('shellui');
11
5
 
12
- cli
13
- .command('start [root]', 'Start the shellui server')
14
- .action(async (root = '.') => {
15
- const cwd = process.cwd();
16
- const configDir = path.resolve(cwd, root);
17
- const configPath = path.join(configDir, 'shellui.json');
18
- const legacyConfigPath = path.join(configDir, 'shellioj.json');
6
+ // Register commands
7
+ cli.command('start [root]', 'Start the shellui server').action(startCommand);
19
8
 
20
- console.log(pc.blue(`Starting ShellUI...`));
21
-
22
- let config = {};
23
- let activeConfigPath = null;
24
-
25
- if (fs.existsSync(configPath)) {
26
- activeConfigPath = configPath;
27
- } else if (fs.existsSync(legacyConfigPath)) {
28
- activeConfigPath = legacyConfigPath;
29
- }
30
-
31
- if (activeConfigPath) {
32
- try {
33
- const configFile = fs.readFileSync(activeConfigPath, 'utf-8');
34
- config = JSON.parse(configFile);
35
- console.log(pc.green(`Loaded config from ${activeConfigPath}`));
36
- } catch (e) {
37
- console.error(pc.red(`Failed to load config: ${e.message}`));
38
- }
39
- } else {
40
- console.log(pc.yellow(`No shellui.json (or shellioj.json) found, using defaults.`));
41
- }
42
-
43
- // Path to the index.html inside the package
44
- const packageRoot = path.resolve(__dirname, '..');
45
- const templateRoot = path.join(__dirname); // src folder
46
-
47
- try {
48
- const server = await createServer({
49
- root: templateRoot, // Serve from src/ where index.html is
50
- plugins: [react()],
51
- define: {
52
- '__SHELLUI_CONFIG__': JSON.stringify(config),
53
- },
54
- server: {
55
- port: config.port || 3000,
56
- open: true,
57
- fs: {
58
- allow: [packageRoot, cwd]
59
- }
60
- },
61
- });
62
-
63
- await server.listen();
64
- server.printUrls();
65
- } catch (e) {
66
- console.error(pc.red(`Error starting server: ${e.message}`));
67
- process.exit(1);
68
- }
69
- });
70
-
71
- cli
72
- .command('build [root]', 'Build the shellui application')
73
- .action(async (root = '.') => {
74
- const cwd = process.cwd();
75
- const configDir = path.resolve(cwd, root);
76
- const configPath = path.join(configDir, 'shellui.json');
77
- const legacyConfigPath = path.join(configDir, 'shellioj.json');
78
-
79
- console.log(pc.blue(`Building ShellUI...`));
80
-
81
- let config = {};
82
- let activeConfigPath = null;
83
-
84
- if (fs.existsSync(configPath)) {
85
- activeConfigPath = configPath;
86
- } else if (fs.existsSync(legacyConfigPath)) {
87
- activeConfigPath = legacyConfigPath;
88
- }
89
-
90
- if (activeConfigPath) {
91
- try {
92
- const configFile = fs.readFileSync(activeConfigPath, 'utf-8');
93
- config = JSON.parse(configFile);
94
- console.log(pc.green(`Loaded config from ${activeConfigPath}`));
95
- } catch (e) {
96
- console.error(pc.red(`Failed to load config: ${e.message}`));
97
- }
98
- } else {
99
- console.log(pc.yellow(`No shellui.json (or shellioj.json) found, using defaults.`));
100
- }
101
-
102
- // Path to the index.html inside the package
103
- const templateRoot = path.join(__dirname); // src folder
104
-
105
- try {
106
- await build({
107
- root: templateRoot, // Serve from src/ where index.html is
108
- plugins: [react()],
109
- define: {
110
- '__SHELLUI_CONFIG__': JSON.stringify(config),
111
- },
112
- build: {
113
- outDir: path.resolve(cwd, 'dist'),
114
- emptyOutDir: true,
115
- }
116
- });
117
- console.log(pc.green('Build complete!'));
118
- } catch (e) {
119
- console.error(pc.red(`Error building: ${e.message}`));
120
- process.exit(1);
121
- }
122
- });
9
+ cli.command('build [root]', 'Build the shellui application').action(buildCommand);
123
10
 
11
+ // Setup CLI metadata
124
12
  cli.help();
125
13
  cli.version('0.0.1');
126
14
  cli.parse();
127
-
@@ -0,0 +1,40 @@
1
+ # CLI Commands
2
+
3
+ This directory contains all CLI commands, each in its own file for better maintainability.
4
+
5
+ ## Structure
6
+
7
+ Each command is a separate file that exports a command function:
8
+
9
+ - `start.js` - Start the ShellUI development server
10
+ - `build.js` - Build the ShellUI application for production
11
+
12
+ ## Adding a New Command
13
+
14
+ 1. Create a new file in this directory (e.g., `new-command.js`)
15
+ 2. Export a function that implements the command logic:
16
+
17
+ ```javascript
18
+ export async function newCommandCommand(args) {
19
+ // Command implementation
20
+ }
21
+ ```
22
+
23
+ 3. Register it in `cli.js`:
24
+
25
+ ```javascript
26
+ import { newCommandCommand } from './commands/index.js';
27
+
28
+ cli.command('new-command [args]', 'Description of the command').action(newCommandCommand);
29
+ ```
30
+
31
+ 4. Export it from `commands/index.js`:
32
+
33
+ ```javascript
34
+ export { newCommandCommand } from './new-command.js';
35
+ ```
36
+
37
+ ## Available Commands
38
+
39
+ - **start** - Starts the development server
40
+ - **build** - Builds the application for production
@@ -0,0 +1,200 @@
1
+ import { build } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import pc from 'picocolors';
6
+ import { injectManifest } from 'workbox-build';
7
+ import {
8
+ loadConfig,
9
+ getCoreSrcPath,
10
+ createResolveAlias,
11
+ createPostCSSConfig,
12
+ createViteDefine,
13
+ resolvePackagePath,
14
+ } from '../utils/index.js';
15
+
16
+ /**
17
+ * Recursively copy a directory
18
+ * @param {string} src - Source directory
19
+ * @param {string} dest - Destination directory
20
+ */
21
+ function copyDir(src, dest) {
22
+ if (!fs.existsSync(src)) {
23
+ return;
24
+ }
25
+
26
+ if (!fs.existsSync(dest)) {
27
+ fs.mkdirSync(dest, { recursive: true });
28
+ }
29
+
30
+ const entries = fs.readdirSync(src, { withFileTypes: true });
31
+
32
+ for (const entry of entries) {
33
+ const srcPath = path.join(src, entry.name);
34
+ const destPath = path.join(dest, entry.name);
35
+
36
+ if (entry.isDirectory()) {
37
+ copyDir(srcPath, destPath);
38
+ } else {
39
+ fs.copyFileSync(srcPath, destPath);
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Build command - Builds the ShellUI application for production
46
+ * @param {string} root - Root directory (default: '.')
47
+ */
48
+ export async function buildCommand(root = '.') {
49
+ const cwd = process.cwd();
50
+
51
+ console.log(pc.blue(`Building ShellUI...`));
52
+
53
+ // Set environment variable to indicate this is a build
54
+ // This allows shellui.config.ts to detect build mode and generate build ID
55
+ process.env.SHELLUI_BUILD = 'true';
56
+ process.env.NODE_ENV = 'production';
57
+
58
+ // Load configuration
59
+ const config = await loadConfig(root);
60
+
61
+ // Log config summary for debugging
62
+ console.log(pc.blue(`Config loaded:`));
63
+ console.log(pc.gray(` - Title: ${config.title || '(not set)'}`));
64
+ console.log(pc.gray(` - Navigation items: ${config.navigation?.length || 0}`));
65
+ console.log(pc.gray(` - Layout: ${config.layout || 'sidebar'}`));
66
+
67
+ // Verify config is serializable
68
+ try {
69
+ const testSerialize = JSON.parse(JSON.stringify(config));
70
+ console.log(pc.green(` ✓ Config is serializable`));
71
+ } catch (e) {
72
+ console.error(pc.red(` ✗ Config is not serializable: ${e.message}`));
73
+ throw e;
74
+ }
75
+
76
+ // Get core package paths
77
+ const corePackagePath = resolvePackagePath('@shellui/core');
78
+ const coreSrcPath = getCoreSrcPath();
79
+ const resolveAlias = createResolveAlias();
80
+ const postcssConfig = createPostCSSConfig();
81
+
82
+ try {
83
+ // Build main app
84
+ await build({
85
+ root: coreSrcPath,
86
+ plugins: [react()],
87
+ define: createViteDefine(config),
88
+ resolve: {
89
+ alias: resolveAlias,
90
+ },
91
+ css: {
92
+ postcss: postcssConfig,
93
+ },
94
+ build: {
95
+ outDir: path.resolve(cwd, 'dist'),
96
+ emptyOutDir: true,
97
+ sourcemap: true,
98
+ // Ensure every build generates unique filenames with content hashes
99
+ // Content hash changes when file content changes, ensuring cache busting
100
+ rollupOptions: {
101
+ input: {
102
+ main: path.join(coreSrcPath, 'index.html'),
103
+ },
104
+ output: {
105
+ // Use content hash in filenames for cache busting
106
+ // [hash] is based on file content, so same content = same hash
107
+ // Different content = different hash, ensuring unique filenames per build when content changes
108
+ entryFileNames: 'assets/[name]-[hash].js',
109
+ chunkFileNames: 'assets/[name]-[hash].js',
110
+ assetFileNames: 'assets/[name]-[hash].[ext]',
111
+ },
112
+ },
113
+ },
114
+ });
115
+
116
+ // Build service worker with Vite first
117
+ console.log(pc.blue('Building service worker...'));
118
+ const swInputPath = path.join(corePackagePath, 'src', 'service-worker', 'sw.ts');
119
+ const distPath = path.resolve(cwd, 'dist');
120
+ const swTempPath = path.join(distPath, 'sw-temp.js');
121
+
122
+ // Build service worker TypeScript to JavaScript
123
+ await build({
124
+ root: coreSrcPath,
125
+ define: createViteDefine(config),
126
+ resolve: {
127
+ alias: resolveAlias,
128
+ },
129
+ build: {
130
+ outDir: distPath,
131
+ emptyOutDir: false,
132
+ sourcemap: true,
133
+ rollupOptions: {
134
+ input: swInputPath,
135
+ output: {
136
+ dir: distPath,
137
+ entryFileNames: 'sw-temp.js',
138
+ format: 'es',
139
+ },
140
+ },
141
+ write: true,
142
+ },
143
+ });
144
+
145
+ // Use workbox-build to inject manifest
146
+ const { count, size, warnings } = await injectManifest({
147
+ swSrc: swTempPath,
148
+ swDest: path.join(distPath, 'sw.js'),
149
+ globDirectory: distPath,
150
+ globPatterns: ['**/*.{js,css,html,svg,png,jpg,jpeg,gif,webp,woff,woff2,ttf,eot,ico}'],
151
+ // Don't precache the service worker itself or source maps
152
+ globIgnores: ['sw.js', 'sw-temp.js', '**/*.map', '**/node_modules/**'],
153
+ // Maximum file size to precache (5MB)
154
+ maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
155
+ });
156
+
157
+ // Remove temporary file
158
+ if (fs.existsSync(swTempPath)) {
159
+ fs.unlinkSync(swTempPath);
160
+ }
161
+
162
+ if (warnings.length > 0) {
163
+ warnings.forEach((warning) => console.warn(pc.yellow(`Warning: ${warning}`)));
164
+ }
165
+
166
+ console.log(
167
+ pc.green(
168
+ `Service worker generated: ${count} files precached (${(size / 1024).toFixed(2)} KB)`,
169
+ ),
170
+ );
171
+
172
+ // Copy static folder contents to dist if it exists
173
+ // This ensures icons are served from the same path in dev and prod
174
+ const staticPath = path.resolve(cwd, 'static');
175
+
176
+ if (fs.existsSync(staticPath)) {
177
+ console.log(pc.blue('Copying static assets...'));
178
+ // Copy contents of static directly to dist (not dist/static)
179
+ // This way /icons/... works in both dev and prod
180
+ copyDir(staticPath, distPath);
181
+ console.log(pc.green('Static assets copied!'));
182
+ }
183
+
184
+ // Copy index.html to 404.html for SPA routing support
185
+ // This allows hosting providers (like Netlify, Vercel) to serve index.html for all routes
186
+ const indexPath = path.join(distPath, 'index.html');
187
+ const notFoundPath = path.join(distPath, '404.html');
188
+
189
+ if (fs.existsSync(indexPath)) {
190
+ console.log(pc.blue('Creating 404.html for SPA routing...'));
191
+ fs.copyFileSync(indexPath, notFoundPath);
192
+ console.log(pc.green('404.html created!'));
193
+ }
194
+
195
+ console.log(pc.green('Build complete!'));
196
+ } catch (e) {
197
+ console.error(pc.red(`Error building: ${e.message}`));
198
+ process.exit(1);
199
+ }
200
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Commands index - Export all available commands
3
+ *
4
+ * This file serves as a central registry of all CLI commands.
5
+ * Each command is in its own file for better maintainability.
6
+ */
7
+
8
+ export { startCommand } from './start.js';
9
+ export { buildCommand } from './build.js';