create-liferay-react-cx 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Laxit Khanpara
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,288 @@
1
+ # create-liferay-react-cx
2
+
3
+ > ⚡ Zero-config CLI scaffold for **Liferay React Client Extensions** (custom element type).
4
+ > Powered by [Vite](https://vitejs.dev/) · Compatible with **Liferay 7.4 / DXP 2024.Q1+**
5
+
6
+ [![npm version](https://img.shields.io/npm/v/create-liferay-react-cx?color=8B5CF6&label=npm)](https://www.npmjs.com/package/create-liferay-react-cx)
7
+ [![license](https://img.shields.io/npm/l/create-liferay-react-cx?color=8B5CF6)](./LICENSE)
8
+ [![node](https://img.shields.io/node/v/create-liferay-react-cx?color=8B5CF6)](https://nodejs.org)
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ - [What is a Liferay Client Extension?](#what-is-a-liferay-client-extension)
15
+ - [Requirements](#requirements)
16
+ - [Quick Start](#quick-start)
17
+ - [All Ways to Use This CLI](#all-ways-to-use-this-cli)
18
+ - [Method 1 — npm create (Recommended)](#method-1--npm-create-recommended)
19
+ - [Method 2 — npx (No Install)](#method-2--npx-no-install)
20
+ - [Method 3 — Global Install](#method-3--global-install)
21
+ - [Options & Flags](#options--flags)
22
+ - [Interactive Mode](#interactive-mode)
23
+ - [Generated Project Structure](#generated-project-structure)
24
+ - [Development Workflow](#development-workflow)
25
+ - [React Version Compatibility](#react-version-compatibility)
26
+ - [Contributing](#contributing)
27
+ - [License](#license)
28
+
29
+ ---
30
+
31
+ ## What is a Liferay Client Extension?
32
+
33
+ A **Client Extension (CE)** is the modern, decoupled way to extend Liferay DXP / Community Edition — no Java, no OSGi, no portal restarts required.
34
+
35
+ A **Custom Element** CE wraps your React app as a standard Web Component (`<my-widget />`), which Liferay embeds directly on any page. This CLI scaffolds everything you need to build, preview, and deploy one in seconds.
36
+
37
+ ---
38
+
39
+ ## Requirements
40
+
41
+ Before you start, make sure you have:
42
+
43
+ | Requirement | Version |
44
+ |---|---|
45
+ | Node.js | ≥ 18.0.0 |
46
+ | npm | ≥ 9.0.0 |
47
+ | [Liferay Workspace](https://learn.liferay.com/dxp/latest/en/building-applications/tooling/liferay-workspace/what-is-liferay-workspace.html) | For `gradlew deploy` |
48
+
49
+ > **Note:** The generated project must sit inside a Liferay Workspace at `[workspace]/client-extensions/my-widget/` for Gradle deployment to work.
50
+
51
+ ---
52
+
53
+ ## Quick Start
54
+
55
+ ```bash
56
+ npm create liferay-react-cx my-widget
57
+ cd my-widget
58
+ ../../gradlew deploy
59
+ ```
60
+
61
+ That's it. Your custom element is live in Liferay. 🚀
62
+
63
+ ---
64
+
65
+ ## All Ways to Use This CLI
66
+
67
+ ### Method 1 — `npm create` (Recommended)
68
+
69
+ `npm create` is the standard npm convention for scaffolding tools (like `npm create vite`, `npm create react-app`). It automatically resolves `create-liferay-react-cx` under the hood.
70
+
71
+ **With a project name (fastest):**
72
+ ```bash
73
+ npm create liferay-react-cx my-widget
74
+ ```
75
+
76
+ **With a specific React version:**
77
+ ```bash
78
+ npm create liferay-react-cx -- --name my-widget --react-version 18.2.0
79
+ ```
80
+ > ⚠️ Note the `--` before flags — this is required when using `npm create` with named options, so npm passes them through to the CLI correctly.
81
+
82
+ **Interactive wizard (no arguments):**
83
+ ```bash
84
+ npm create liferay-react-cx
85
+ ```
86
+
87
+ ---
88
+
89
+ ### Method 2 — `npx` (No Install)
90
+
91
+ Use `npx` to run the CLI directly without installing anything globally. The full package name `create-liferay-react-cx` is used here.
92
+
93
+ **With a project name:**
94
+ ```bash
95
+ npx create-liferay-react-cx my-widget
96
+ ```
97
+
98
+ **With a specific React version:**
99
+ ```bash
100
+ npx create-liferay-react-cx --name my-widget --react-version 18.2.0
101
+ ```
102
+
103
+ **Interactive wizard:**
104
+ ```bash
105
+ npx create-liferay-react-cx
106
+ ```
107
+
108
+ ---
109
+
110
+ ### Method 3 — Global Install
111
+
112
+ Install once, use anywhere. After a global install, you can use the short alias `liferay-react-cx` from any directory.
113
+
114
+ **Step 1 — Install globally:**
115
+ ```bash
116
+ npm install -g create-liferay-react-cx
117
+ ```
118
+
119
+ **Step 2 — Use it:**
120
+ ```bash
121
+ liferay-react-cx my-widget
122
+ liferay-react-cx --name my-widget --react-version 18.2.0
123
+ liferay-react-cx --help
124
+ ```
125
+
126
+ > **Tip:** To update a globally installed version later, run `npm update -g create-liferay-react-cx`.
127
+
128
+ ---
129
+
130
+ ## Options & Flags
131
+
132
+ | Flag | Short | Description | Default |
133
+ |------|-------|-------------|---------|
134
+ | `--name` | `-n` | App name in **kebab-case** | _(prompted)_ |
135
+ | `--react-version` | `-r` | React version to scaffold with | `16.12.0` |
136
+ | `--help` | `-h` | Print help and exit | — |
137
+ | `--version` | `-v` | Print version and exit | — |
138
+
139
+ **App name rules:** lowercase letters, numbers, and hyphens only. Must start with a letter. Examples: `my-widget`, `employee-portal`, `news-feed`.
140
+
141
+ ---
142
+
143
+ ## Interactive Mode
144
+
145
+ Run the CLI with no arguments to enter the interactive wizard:
146
+
147
+ ```bash
148
+ npm create liferay-react-cx
149
+ # or
150
+ npx create-liferay-react-cx
151
+ ```
152
+
153
+ You will be asked:
154
+
155
+ 1. **App name** — enter a kebab-case name for your widget
156
+ 2. **React version** — choose from a list or enter a custom version:
157
+ - `16.12.0` — Liferay 7.4 / DXP classic
158
+ - `18.2.0` — Liferay 7.4 U45+ / DXP 2024.Q1+
159
+ - `Custom` — enter any valid semver (e.g. `17.0.2`)
160
+ 3. **Install dependencies now?** — yes/no confirm
161
+
162
+ ---
163
+
164
+ ## Generated Project Structure
165
+
166
+ Running the CLI creates the following layout:
167
+
168
+ ```
169
+ my-widget/
170
+ ├── client-extension.yaml ← Liferay CE descriptor (auto-configured)
171
+ ├── index.html ← Vite dev-server entry point
172
+ ├── package.json ← Project dependencies & scripts
173
+ ├── vite.config.js ← Vite build config
174
+ ├── eslint.config.js ← ESLint flat config
175
+ └── src/
176
+ ├── main.jsx ← Web Component registration (HTMLElement)
177
+ ├── App.jsx ← Your React component — start editing here
178
+ ├── index.css ← Global reset (shadow DOM scoped)
179
+ └── assets/
180
+ └── style.css ← Component styles (shadow DOM isolated)
181
+ ```
182
+
183
+ ### What `client-extension.yaml` does
184
+
185
+ This file tells Liferay how to register and display your widget:
186
+
187
+ ```yaml
188
+ my-widget:
189
+ type: customElement # renders as <my-widget> on Liferay pages
190
+ htmlElementName: my-widget # the HTML tag name
191
+ urls:
192
+ - assets/*.js # built JS bundle from vite-build/
193
+ cssURLs:
194
+ - assets/*.css # built CSS bundle from vite-build/
195
+ useESM: true # ES module support
196
+ instanceable: false # one instance per page
197
+ portletCategoryName: category.client-extensions
198
+ ```
199
+
200
+ All values are automatically replaced with your app name during scaffolding.
201
+
202
+ ---
203
+
204
+ ## Development Workflow
205
+
206
+ ### ✅ Deploying to Liferay (Standard Workflow)
207
+
208
+ When working inside a Liferay Workspace, **you only need one command**. Gradle handles the npm install, Vite build, and deployment automatically — no manual `npm run build` needed.
209
+
210
+ **1. Navigate into your project:**
211
+ ```bash
212
+ cd my-widget
213
+ ```
214
+
215
+ **2. Deploy directly to Liferay** (from the Liferay Workspace root):
216
+ ```bash
217
+ ../../gradlew deploy
218
+ ```
219
+
220
+ That's it. Under the hood, Gradle will:
221
+ - Run `npm install` (if `node_modules` is missing)
222
+ - Run `npm run build` (Vite compiles to `vite-build/`)
223
+ - Copy the output into Liferay's deploy folder
224
+ - Hot-reload the bundle in your running Liferay instance
225
+
226
+ > After deployment, go to **Liferay Admin → Fragments and Resources → Client Extensions** to find and add your widget to a page.
227
+
228
+ ---
229
+
230
+ ### 🖥️ Local Preview (Optional)
231
+
232
+ If you want to preview your React component in a browser **outside of Liferay** during development (faster feedback, hot reload), you can use Vite's dev server:
233
+
234
+ ```bash
235
+ npm install # only needed once
236
+ npm run dev # starts at http://localhost:5173
237
+ ```
238
+
239
+ > This is purely optional and useful for rapid UI development. The component will render standalone — not inside Liferay's portal context.
240
+
241
+ ---
242
+
243
+ ## React Version Compatibility
244
+
245
+ | React Version | Liferay Compatibility | Notes |
246
+ |:---:|:---|:---|
247
+ | `16.12.0` | Liferay 7.4 GA / DXP classic | Uses Liferay's bundled React. Smaller bundle. |
248
+ | `18.2.0` | Liferay 7.4 U45+ / DXP 2024.Q1+ | Fully isolated in shadow DOM. Concurrent features. |
249
+
250
+ Not sure which to pick? Use `16.12.0` for maximum compatibility with older Liferay instances, or `18.2.0` for newer deployments that support isolated client extensions.
251
+
252
+ ---
253
+
254
+ ## Contributing
255
+
256
+ Contributions, issues, and feature requests are welcome!
257
+
258
+ ```bash
259
+ # 1. Clone the repo
260
+ git clone https://github.com/laxitkhanpara/liferay-react-ce.git
261
+ cd liferay-react-cx
262
+
263
+ # 2. Install dependencies
264
+ npm install
265
+
266
+ # 3. Test locally
267
+ node bin/index.js my-test-app
268
+
269
+ # 4. Clean up
270
+ rm -rf my-test-app
271
+ ```
272
+
273
+ Please open an [issue](https://github.com/laxitkhanpara/liferay-react-ce/issues) first to discuss any significant changes.
274
+
275
+ ---
276
+
277
+ ## License
278
+
279
+ [MIT](./LICENSE) © [Laxit Khanpara](https://github.com/laxitkhanpara)
280
+
281
+ ---
282
+
283
+ ## Links
284
+
285
+ - 📦 [npm package](https://www.npmjs.com/package/create-liferay-react-cx)
286
+ - 🐛 [Report a bug](https://github.com/laxitkhanpara/liferay-react-ce/issues)
287
+ - 📖 [Liferay Client Extensions docs](https://learn.liferay.com/dxp/latest/en/building-applications/client-extensions.html)
288
+ - 🏗️ [Liferay Workspace docs](https://learn.liferay.com/dxp/latest/en/building-applications/tooling/liferay-workspace/what-is-liferay-workspace.html)
package/bin/index.js ADDED
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+
3
+ import inquirer from 'inquirer';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs-extra';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { execa } from 'execa';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ const commandName = 'liferay-react-cx';
14
+
15
+ const PKG_VERSION = '1.0.1';
16
+
17
+ // ─── Helpers ────────────────────────────────────────────────────────────────
18
+
19
+ const args = process.argv.slice(2);
20
+
21
+ const hasFlag = (flags) => flags.some((f) => args.includes(f));
22
+
23
+ const getArgValue = (names) => {
24
+ const i = args.findIndex((arg) => names.includes(arg));
25
+ return i >= 0 && args[i + 1] ? args[i + 1] : undefined;
26
+ };
27
+
28
+ const positionalArgs = args.filter((a) => !a.startsWith('-'));
29
+
30
+ // ─── Banner ──────────────────────────────────────────────────────────────────
31
+
32
+ function printBanner() {
33
+ console.log('');
34
+
35
+ console.log(
36
+ chalk.bold.hex('#0B5FFF')(
37
+ ' ██╗ ██╗███████╗███████╗██████╗ █████╗ ██╗ ██╗'
38
+ )
39
+ );
40
+ console.log(
41
+ chalk.bold.hex('#0B5FFF')(
42
+ ' ██║ ██║██╔════╝██╔════╝██╔══██╗██╔══██╗╚██╗ ██╔╝'
43
+ )
44
+ );
45
+ console.log(
46
+ chalk.bold.hex('#0B5FFF')(
47
+ ' ██║ ██║█████╗ █████╗ ██████╔╝███████║ ╚████╔╝ '
48
+ )
49
+ );
50
+ console.log(
51
+ chalk.bold.hex('#0B5FFF')(
52
+ ' ██║ ██║██╔══╝ ██╔══╝ ██╔══██╗██╔══██║ ╚██╔╝ '
53
+ )
54
+ );
55
+ console.log(
56
+ chalk.bold.hex('#0B5FFF')(
57
+ ' ███████╗██║██║ ███████╗██║ ██║██║ ██║ ██║ '
58
+ )
59
+ );
60
+ console.log(
61
+ chalk.bold.hex('#0B5FFF')(
62
+ ' ╚══════╝╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ '
63
+ )
64
+ );
65
+
66
+ console.log('');
67
+
68
+ console.log(
69
+ chalk.bold(' Liferay React Client Extension')
70
+ );
71
+
72
+ console.log(
73
+ chalk.dim(' Official-style scaffolding tool for Liferay DXP')
74
+ );
75
+
76
+ console.log(
77
+ chalk.dim(` Version: ${PKG_VERSION}`)
78
+ );
79
+
80
+ console.log(
81
+ chalk.dim(' ─────────────────────────────────────────────')
82
+ );
83
+
84
+ console.log('');
85
+ }
86
+
87
+ // ─── Version / Help ──────────────────────────────────────────────────────────
88
+
89
+ if (hasFlag(['--version', '-v'])) {
90
+ console.log(`liferay-react-cx v${PKG_VERSION}`);
91
+ process.exit(0);
92
+ }
93
+
94
+ if (hasFlag(['--help', '-h'])) {
95
+ console.log(`
96
+ ${chalk.bold('liferay-react-cx')} — Scaffold a Liferay React Client Extension
97
+
98
+ ${chalk.bold('USAGE')}
99
+ liferay-react-cx [app-name] [react-version]
100
+ liferay-react-cx --name <app-name> [--react-version <version>]
101
+ -n, --name App name in kebab-case (e.g. my-widget)
102
+ -r, --react-version React version to use (default: 16.12.0)
103
+ -v, --version Print version
104
+ -h, --help Show this help
105
+
106
+ ${chalk.bold('EXAMPLES')}
107
+ npm create liferay-react-cx my-widget
108
+ npm create liferay-react-cx -- --name my-widget --react-version 18.2.0
109
+ npm create liferay-react-cx # interactive mode
110
+
111
+ ${chalk.bold('AFTER SCAFFOLDING')}
112
+ cd <app-name>
113
+ ../../gradlew deploy # deploy to Liferay
114
+
115
+ ${chalk.dim('https://github.com/laxitkhanpara/liferay-react-ce')}
116
+ `);
117
+ process.exit(0);
118
+ }
119
+
120
+ // ─── Main ────────────────────────────────────────────────────────────────────
121
+
122
+ (async () => {
123
+ printBanner();
124
+
125
+ // Resolve inputs
126
+ const appNameArg = getArgValue(['--name', '-n']) || positionalArgs[0];
127
+ const reactVersionArg =
128
+ getArgValue(['--react-version', '-r']) || positionalArgs[1];
129
+
130
+ // Interactive prompts only for missing fields
131
+ let answers;
132
+
133
+ if (appNameArg) {
134
+ answers = {
135
+ appName: appNameArg,
136
+ reactVersion: reactVersionArg || '16.12.0',
137
+ installDeps: true,
138
+ };
139
+ } else {
140
+ answers = await inquirer.prompt([
141
+ {
142
+ type: 'input',
143
+ name: 'appName',
144
+ message: chalk.cyan('App name') + chalk.dim(' (kebab-case):'),
145
+ validate: (input) =>
146
+ /^[a-z][a-z0-9-]*$/.test(input.trim())
147
+ ? true
148
+ : chalk.red('Use lowercase kebab-case — e.g. my-widget'),
149
+ filter: (v) => v.trim(),
150
+ },
151
+ {
152
+ type: 'list',
153
+ name: 'reactVersion',
154
+ message: chalk.cyan('React version:'),
155
+ choices: [
156
+ { name: '16.12.0 (Liferay 7.4 / DXP classic)', value: '16.12.0' },
157
+ { name: '18.2.0 (Liferay 7.4 U45+ / DXP 2024.Q1+)', value: '18.2.0' },
158
+ { name: 'Custom (enter manually)', value: '__custom__' },
159
+ ],
160
+ default: '16.12.0',
161
+ },
162
+ {
163
+ type: 'input',
164
+ name: 'reactVersionCustom',
165
+ message: chalk.cyan('Enter React version:'),
166
+ when: (prev) => prev.reactVersion === '__custom__',
167
+ validate: (v) =>
168
+ /^\d+\.\d+\.\d+/.test(v.trim()) ? true : 'Enter a valid semver e.g. 17.0.2',
169
+ filter: (v) => v.trim(),
170
+ },
171
+ {
172
+ type: 'confirm',
173
+ name: 'installDeps',
174
+ message: chalk.cyan('Install npm dependencies now?'),
175
+ default: true,
176
+ },
177
+ ]);
178
+
179
+ if (answers.reactVersion === '__custom__') {
180
+ answers.reactVersion = answers.reactVersionCustom;
181
+ }
182
+ }
183
+
184
+ const { appName, reactVersion, installDeps } = answers;
185
+ const projectPath = path.resolve(process.cwd(), appName);
186
+ const templatePath = path.join(__dirname, '..', 'templates', 'custom-element');
187
+
188
+ // Guard: existing directory
189
+ if (fs.existsSync(projectPath)) {
190
+ console.error(
191
+ chalk.red(`\n ✖ Directory "${appName}" already exists. Choose a different name.\n`)
192
+ );
193
+ process.exit(1);
194
+ }
195
+
196
+ // ── Step 1: Copy template ──────────────────────────────────────────────────
197
+ process.stdout.write(chalk.blue('\n 📁 Scaffolding project…'));
198
+ await fs.copy(templatePath, projectPath);
199
+ console.log(chalk.green(' ✔'));
200
+
201
+ // ── Step 2: Replace __APP_NAME__ placeholders ─────────────────────────────
202
+ const replaceInFile = (filePath) => {
203
+ let content = fs.readFileSync(filePath, 'utf-8');
204
+ content = content.replace(/__APP_NAME__/g, appName);
205
+ fs.writeFileSync(filePath, content);
206
+ };
207
+
208
+ const walk = (dir) => {
209
+ for (const file of fs.readdirSync(dir)) {
210
+ const full = path.join(dir, file);
211
+ fs.statSync(full).isDirectory() ? walk(full) : replaceInFile(full);
212
+ }
213
+ };
214
+
215
+ walk(projectPath);
216
+
217
+ // ── Step 3: Write package.json ────────────────────────────────────────────
218
+ process.stdout.write(chalk.blue(' 📦 Writing package.json…'));
219
+
220
+ const projectPackageJson = {
221
+ name: appName,
222
+ version: '1.0.0',
223
+ private: true,
224
+ type: 'module',
225
+ scripts: {
226
+ dev: 'vite',
227
+ build: 'vite build',
228
+ preview: 'vite preview',
229
+ },
230
+ dependencies: {
231
+ react: reactVersion,
232
+ 'react-dom': reactVersion,
233
+ },
234
+ devDependencies: {
235
+ '@vitejs/plugin-react': '^4.3.3',
236
+ vite: '^4.4.9',
237
+ },
238
+ };
239
+
240
+ fs.writeJsonSync(path.join(projectPath, 'package.json'), projectPackageJson, {
241
+ spaces: 2,
242
+ });
243
+ console.log(chalk.green(' ✔'));
244
+
245
+ // ── Step 4: Install dependencies ──────────────────────────────────────────
246
+ if (installDeps !== false) {
247
+ console.log(chalk.blue('\n 📥 Installing dependencies…\n'));
248
+ try {
249
+ await execa('npm', ['install'], { cwd: projectPath, stdio: 'inherit' });
250
+ } catch {
251
+ console.warn(
252
+ chalk.yellow(
253
+ '\n ⚠ npm install failed. Run it manually inside the project folder.\n'
254
+ )
255
+ );
256
+ }
257
+ }
258
+
259
+ // ── Done ──────────────────────────────────────────────────────────────────
260
+ console.log('');
261
+ console.log(
262
+ chalk.bold.green(' ✅ Project created successfully!\n')
263
+ );
264
+ console.log(chalk.bold(' Next steps:\n'));
265
+ console.log(chalk.cyan(` cd ${appName}`));
266
+ if (installDeps === false) {
267
+ console.log(chalk.cyan(' npm install'));
268
+ }
269
+ console.log(
270
+ chalk.cyan(' ../../gradlew deploy') + chalk.dim(' # deploy to Liferay')
271
+ );
272
+ console.log('');
273
+ console.log(
274
+ chalk.dim(
275
+ ' Docs: https://learn.liferay.com/dxp/latest/en/building-applications/client-extensions.html'
276
+ )
277
+ );
278
+ console.log('');
279
+ })();
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "create-liferay-react-cx",
3
+ "version": "1.0.1",
4
+ "description": "CLI scaffold tool to generate Liferay React Client Extension (custom element) projects — zero-config, Vite-powered, Liferay 7.4+ compatible.",
5
+ "type": "module",
6
+ "main": "./bin/index.js",
7
+ "bin": {
8
+ "create-liferay-react-cx": "./bin/index.js",
9
+ "liferay-react-cx": "./bin/index.js"
10
+ },
11
+ "keywords": [
12
+ "liferay",
13
+ "client-extension",
14
+ "custom-element",
15
+ "react",
16
+ "vite",
17
+ "scaffold",
18
+ "cli",
19
+ "liferay-dxp",
20
+ "liferay-portal",
21
+ "web-component"
22
+ ],
23
+ "author": {
24
+ "name": "Laxit Khanpara",
25
+ "email": "laxitkhanpara3646@gmail.com",
26
+ "url": "https://github.com/laxitkhanpara"
27
+ },
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/laxitkhanpara/liferay-react-ce.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/laxitkhanpara/liferay-react-ce/issues"
35
+ },
36
+ "homepage": "https://github.com/laxitkhanpara/liferay-react-ce#readme",
37
+ "engines": {
38
+ "node": ">=18.0.0",
39
+ "npm": ">=9.0.0"
40
+ },
41
+ "files": [
42
+ "bin/",
43
+ "templates/",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "scripts": {
48
+ "test": "echo \"No tests yet\" && exit 0"
49
+ },
50
+ "dependencies": {
51
+ "chalk": "^5.3.0",
52
+ "execa": "^9.6.1",
53
+ "fs-extra": "^11.2.0",
54
+ "inquirer": "^9.2.12"
55
+ }
56
+ }
@@ -0,0 +1,17 @@
1
+ assemble:
2
+ - from: vite-build
3
+ into: static
4
+
5
+ __APP_NAME__:
6
+ cssURLs:
7
+ - assets/*.css
8
+ friendlyURLMapping: __APP_NAME__
9
+ htmlElementName: __APP_NAME__
10
+ instanceable: false
11
+ name: __APP_NAME__
12
+ portletCategoryName: category.client-extensions
13
+ type: customElement
14
+ urls:
15
+ - assets/*.js
16
+ useESM: true
17
+ liferay.virtual.instance.id: default
@@ -0,0 +1 @@
1
+ export default [{ rules: { 'no-console': 'warn' } }];
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__APP_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <__APP_NAME__></__APP_NAME__>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import './assets/style.css';
3
+
4
+ function App() {
5
+ return (
6
+ <div className="lce-container">
7
+ <div className="lce-card">
8
+ <header className="lce-header">
9
+ <h1 className="lce-title">__APP_NAME__</h1>
10
+ <p className="lce-subtitle">
11
+ React {React.version}
12
+ </p>
13
+ </header>
14
+ </div>
15
+ </div>
16
+ );
17
+ }
18
+
19
+ export default App;
@@ -0,0 +1,47 @@
1
+ /* ── Shadow DOM reset ─────────────────────────────────────────────────────── */
2
+
3
+ :host {
4
+ all: initial;
5
+ display: block;
6
+ font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
7
+ color: #1e1b4b;
8
+ }
9
+
10
+ /* ── Layout ───────────────────────────────────────────────────────────────── */
11
+
12
+ .lce-container {
13
+ width: 100%;
14
+ min-height: 100px;
15
+ padding: 24px 16px;
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
20
+ }
21
+
22
+ .lce-card {
23
+ width: min(100%, 500px);
24
+ padding: 36px 40px;
25
+ border-radius: 20px;
26
+ background: #ffffff;
27
+ box-shadow: 0 8px 32px rgba(109, 40, 217, 0.12);
28
+ text-align: center;
29
+ }
30
+
31
+ /* ── Header ───────────────────────────────────────────────────────────────── */
32
+
33
+ .lce-title {
34
+ font-size: 28px;
35
+ font-weight: 800;
36
+ letter-spacing: -0.5px;
37
+ background: linear-gradient(135deg, #4c1d95, #7c3aed);
38
+ -webkit-background-clip: text;
39
+ -webkit-text-fill-color: transparent;
40
+ background-clip: text;
41
+ margin-bottom: 10px;
42
+ }
43
+
44
+ .lce-subtitle {
45
+ color: #6b7280;
46
+ font-size: 14px;
47
+ }
@@ -0,0 +1,5 @@
1
+ *, *::before, *::after {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { render, unmountComponentAtNode } from 'react-dom';
3
+ import App from './App';
4
+
5
+ class WebComponent extends HTMLElement {
6
+ connectedCallback() {
7
+ render(
8
+ <React.StrictMode>
9
+ <App />
10
+ </React.StrictMode>,
11
+ this
12
+ );
13
+ }
14
+
15
+ disconnectedCallback() {
16
+ unmountComponentAtNode(this);
17
+ }
18
+ }
19
+
20
+ const ELEMENT_NAME = '__APP_NAME__';
21
+
22
+ if (customElements.get(ELEMENT_NAME)) {
23
+ // eslint-disable-next-line no-console
24
+ console.log(`Skipping registration for <${ELEMENT_NAME}> (already registered)`);
25
+ } else {
26
+ customElements.define(ELEMENT_NAME, WebComponent);
27
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ base: './',
6
+ build: {
7
+ outDir: 'vite-build',
8
+ },
9
+ plugins: [
10
+ react({
11
+ jsxRuntime: 'classic',
12
+ }),
13
+ ],
14
+ });