Command-line interface for UniversalPWA - Transform any web project into a Progressive Web App (PWA) with one click.
npx @julien-lin/universal-pwa-cli initThis command will:
- Guide you through an interactive setup
- Generate all PWA assets (icons, manifest, service worker)
- Inject meta tags into your HTML files
No global installation needed — npx is the recommended way to run the CLI.
UniversalPWA peut être configuré via un fichier de configuration pour éviter de passer toutes les options en ligne de commande.
npx @julien-lin/universal-pwa-cli generate-configCette commande génère un fichier universal-pwa.config.ts (ou .js, .json, .yaml) basé sur votre projet.
-
universal-pwa.config.ts(TypeScript - recommandé) -
universal-pwa.config.js(JavaScript) -
universal-pwa.config.json(JSON) -
universal-pwa.config.yaml(YAML)
- Arguments CLI (priorité la plus haute)
- Fichier de configuration
- Valeurs par défaut (priorité la plus basse)
Voir CONFIGURATION.md pour la documentation complète avec tous les exemples.
Initialize a PWA in your project.
For projects using build tools (React, Vite, Vue, etc.), always build first, then initialize the PWA:
# 1. Build your project first (generates assets with hashes)
npm run build
# or
pnpm build
# or
yarn build
# 2. Then initialize PWA
# In interactive mode, select "Production" when prompted
# The CLI will auto-detect dist/ directory and suggest it
npx @julien-lin/universal-pwa-cli init
# Or explicitly specify output directory
npx @julien-lin/universal-pwa-cli init --output-dir distWhy? The service worker needs to precache all your built assets (JS/CSS with hashes). If you initialize before building, the service worker won't know about the hashed filenames.
Environment Detection:
- The CLI automatically detects your environment:
-
Production: If
dist/orbuild/exists with recent files (< 24h) -
Local: Otherwise, defaults to
public/
-
Production: If
- Detection indicators are displayed during interactive prompts
- You can override the detection by explicitly choosing Local or Production
Simply run without arguments to launch interactive prompts:
npx @julien-lin/universal-pwa-cli initThe CLI will guide you through a 2-phase workflow:
Phase 1: Environment Selection
- Choose between Local (development) or Production (build)
- The CLI automatically detects your environment based on the presence of
dist/orbuild/directories - Displays detection indicators (e.g., "dist/ directory exists with 15 built files")
Phase 2: Application Configuration
- App name (auto-detected from
package.json) - Short name (max 12 characters, auto-generated from app name)
- Icon source path (auto-detected from common locations)
- Theme and background colors (suggested based on detected framework)
- Icon generation options
All prompts include smart defaults, validation, and contextual suggestions!
npx @julien-lin/universal-pwa-cli init [options]Options:
-
-p, --project-path <path>: Project path (default: current directory) -
-n, --name <name>: Application name -
-s, --short-name <shortName>: Short name (max 12 characters) -
-i, --icon-source <path>: Source image for icons -
-t, --theme-color <color>: Theme color (hex, e.g.,#2c3e50) -
-b, --background-color <color>: Background color (hex) -
--skip-icons: Skip icon generation -
--skip-service-worker: Skip service worker generation -
--skip-injection: Skip meta-tags injection -
-o, --output-dir <dir>: Output directory (auto-detectsdist/for React/Vite, otherwisepublic/) -
--base-path <path>: Base path for deployment (e.g.,/app/,/api/pwa/)
Examples:
# For production build (React/Vite)
npm run build
npx @julien-lin/universal-pwa-cli init --output-dir dist --icon-source ./logo.png
# For development or static sites
npx @julien-lin/universal-pwa-cli init \
--name "My Application" \
--short-name "MyApp" \
--icon-source ./logo.png \
--theme-color "#2c3e50"
# For deployment under a subpath
npx @julien-lin/universal-pwa-cli init \
--name "CreativeHub" \
--output-dir public \
--base-path "/creativehub/"
# For API-based PWA
npx @julien-lin/universal-pwa-cli init \
--name "PWA API" \
--output-dir dist \
--base-path "/api/pwa/"If your PWA is deployed under a subpath (e.g., behind a reverse proxy or on a shared domain), use the --base-path option to ensure all resources are properly scoped.
-
Reverse Proxy/Load Balancer: App served at
/app/instead of/ - Multiple PWAs on Same Domain: Each PWA has its own path
-
Shared Hosting: PWA is in a subdirectory like
/pwa/or/myapp/ -
API-Mounted PWA: Served from
/api/v1/pwa/
When you specify --base-path /app/:
- Manifest link becomes:
<link rel="manifest" href="/app/manifest.json"> - Service Worker registered at:
/app/sw.js - All resources are scoped to the
/app/path
This ensures:
- ✅ Manifest is found at the correct path
- ✅ Service Worker operates in the correct scope
- ✅ No conflicts with other apps on the same domain
Symfony Project - Deployed under /creative-hub/ path:
npm run build
npx @julien-lin/universal-pwa-cli init \
--name "Creative Hub" \
--output-dir public \
--base-path "/creative-hub/"Next.js with Custom Base Path:
pnpm build
npx @julien-lin/universal-pwa-cli init \
--output-dir .next \
--base-path "/dashboard/"Static Site on Shared Hosting - Deployed at example.com/apps/myapp/:
npx @julien-lin/universal-pwa-cli init \
--name "My App" \
--output-dir dist \
--base-path "/apps/myapp/"Important Notes:
- Base path must start with
/and ideally end with/ - The base path is used for both manifest and service worker registration
- Ensure your web server is configured to serve the PWA files from the specified base path
- Test that
https://yourdomain/basePath/manifest.jsonis accessible
The CLI automatically injects a PWA install handler into your HTML. To display an install button in your application, use the exposed global functions:
-
window.installPWA(): Triggers the install prompt -
window.isPWAInstalled(): Checks if the app is already installed -
window.isPWAInstallable(): Checks if the app is installable
// Check if installable and show a button
if (window.isPWAInstallable && window.isPWAInstallable()) {
const installButton = document.createElement("button");
installButton.textContent = "Install App";
installButton.onclick = () => {
window.installPWA().catch(console.error);
};
document.body.appendChild(installButton);
}import { useState, useEffect } from "react";
function InstallButton() {
const [isInstallable, setIsInstallable] = useState(false);
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
// Check initial state
if (window.isPWAInstalled) {
setIsInstalled(window.isPWAInstalled());
}
if (window.isPWAInstallable) {
setIsInstallable(window.isPWAInstallable());
}
// Listen to custom events
const handleInstallable = () => setIsInstallable(true);
const handleInstalled = () => {
setIsInstalled(true);
setIsInstallable(false);
};
window.addEventListener("pwa-installable", handleInstallable);
window.addEventListener("pwa-installed", handleInstalled);
return () => {
window.removeEventListener("pwa-installable", handleInstallable);
window.removeEventListener("pwa-installed", handleInstalled);
};
}, []);
if (isInstalled || !isInstallable) {
return null;
}
return <button onClick={() => window.installPWA?.()}>Install App</button>;
}The injected script emits custom events you can listen to:
-
pwa-installable: Emitted when the app becomes installable -
pwa-installed: Emitted after successful installation -
pwa-install-choice: Emitted with user's choice ({ detail: { outcome: 'accepted' | 'dismissed' } })
Scan a project and detect framework, architecture, and assets.
npx @julien-lin/universal-pwa-cli scan [options]Options:
-
-p, --project-path <path>: Project path (default: current directory)
Example:
npx @julien-lin/universal-pwa-cli scanOutput:
- Detected framework (React, Vue, WordPress, etc.)
- Architecture (SPA, SSR, static)
- Build tool
- Assets found (JS, CSS, images, fonts)
Preview the PWA configuration of a project.
npx @julien-lin/universal-pwa-cli preview [options]Options:
-
-p, --project-path <path>: Project path (default: current directory) -
--port <port>: Server port (default:3000) -
--open: Open in browser
Example:
npx @julien-lin/universal-pwa-cli preview --port 8080After running npx @julien-lin/universal-pwa-cli init, the following files are generated:
-
manifest.json- PWA manifest file -
sw.js- Service Worker (Workbox) -
sw-src.js- Service Worker source (for customization) -
icon-*.png- PWA icons in multiple sizes (72x72 to 512x512) -
apple-touch-icon.png- Apple Touch Icon (180x180) -
splash-*.png- Splash screens for iOS
Meta tags are automatically injected into your HTML files.
You can also use the CLI as a module:
import { initCommand } from "@julien-lin/universal-pwa-cli";
const result = await initCommand({
projectPath: "./my-project",
name: "My App",
iconSource: "./icon.png",
});If UniversalPWA is useful to you, please consider sponsoring the project to help maintain and improve it.
# Install dependencies
pnpm install
# Build
pnpm build
# Tests
pnpm test
# Lint
pnpm lint- Repository: https://github.com/julien-lin/UniversalPWA
- Issues: https://github.com/julien-lin/UniversalPWA/issues
- Discussions: https://github.com/julien-lin/UniversalPWA/discussions
- Contributing: https://github.com/julien-lin/UniversalPWA/blob/main/CONTRIBUTING.md
- Releases: https://github.com/julien-lin/UniversalPWA/releases
- Sponsor: https://github.com/sponsors/julien-lin
- npm Package: https://npm.gzweb.eu.org/package/@julien-lin/universal-pwa-cli