@swoff/cli 0.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/bin/swoff
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Swoff CLI Entry Point
|
|
5
|
+
* Uses tsx to run TypeScript directly
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const cliPath = join(__dirname, '../src/index.ts');
|
|
14
|
+
|
|
15
|
+
const child = spawn('npx', ['tsx', cliPath, ...process.argv.slice(2)], {
|
|
16
|
+
stdio: 'inherit',
|
|
17
|
+
cwd: process.cwd()
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
child.on('exit', (code) => {
|
|
21
|
+
process.exit(code ?? 0);
|
|
22
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@swoff/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI for Swoff - Offline-first web apps made easy",
|
|
6
|
+
"bin": {
|
|
7
|
+
"swoff": "bin/swoff"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "tsx src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/",
|
|
17
|
+
"src/"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"service-worker",
|
|
21
|
+
"offline",
|
|
22
|
+
"pwa",
|
|
23
|
+
"caching"
|
|
24
|
+
],
|
|
25
|
+
"author": "Swoff",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"tsx": "^4.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swoff CLI - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Command-line interface for managing Swoff in your project.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* swoff init Initialize Swoff in current directory
|
|
8
|
+
* swoff generate Generate service worker and files
|
|
9
|
+
* swoff validate Validate swoff.config.json
|
|
10
|
+
* swoff add <feature> Add specific feature files
|
|
11
|
+
* swoff --help Show help
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
15
|
+
import { join, dirname } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { spawn } from "child_process";
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const packageDir = join(__dirname, '..');
|
|
21
|
+
const projectRoot = process.cwd();
|
|
22
|
+
|
|
23
|
+
// Colors for console output
|
|
24
|
+
const colors = {
|
|
25
|
+
reset: '\x1b[0m',
|
|
26
|
+
bright: '\x1b[1m',
|
|
27
|
+
dim: '\x1b[2m',
|
|
28
|
+
green: '\x1b[32m',
|
|
29
|
+
yellow: '\x1b[33m',
|
|
30
|
+
blue: '\x1b[34m',
|
|
31
|
+
red: '\x1b[31m',
|
|
32
|
+
cyan: '\x1b[36m'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const log = {
|
|
36
|
+
info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
|
|
37
|
+
success: (msg) => console.log(`${colors.green}✅${colors.reset} ${msg}`),
|
|
38
|
+
warn: (msg) => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`),
|
|
39
|
+
error: (msg) => console.log(`${colors.red}❌${colors.reset} ${msg}`),
|
|
40
|
+
help: (msg) => console.log(` ${colors.cyan}${msg}${colors.reset}`),
|
|
41
|
+
header: (msg) => console.log(`\n${colors.bright}${msg}${colors.reset}\n`)
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Parse command line arguments
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
const command = args[0];
|
|
47
|
+
const options = args.slice(1);
|
|
48
|
+
|
|
49
|
+
// CLI Commands
|
|
50
|
+
const commands = {
|
|
51
|
+
init: {
|
|
52
|
+
description: 'Initialize Swoff in current directory',
|
|
53
|
+
usage: 'swoff init [--framework react-vite|nextjs|vue-vite]',
|
|
54
|
+
examples: [
|
|
55
|
+
'swoff init',
|
|
56
|
+
'swoff init --framework react-vite'
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
generate: {
|
|
60
|
+
description: 'Generate service worker and supporting files',
|
|
61
|
+
usage: 'swoff generate [--sw-only|--files-only]',
|
|
62
|
+
examples: [
|
|
63
|
+
'swoff generate',
|
|
64
|
+
'swoff generate --sw-only',
|
|
65
|
+
'swoff generate --files-only'
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
validate: {
|
|
69
|
+
description: 'Validate swoff.config.json',
|
|
70
|
+
usage: 'swoff validate',
|
|
71
|
+
examples: [
|
|
72
|
+
'swoff validate'
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
add: {
|
|
76
|
+
description: 'Add specific feature files',
|
|
77
|
+
usage: 'swoff add <feature>',
|
|
78
|
+
examples: [
|
|
79
|
+
'swoff add offline',
|
|
80
|
+
'swoff add pwa',
|
|
81
|
+
'swoff add mutation-queue'
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
help: {
|
|
85
|
+
description: 'Show help information',
|
|
86
|
+
usage: 'swoff help [command]',
|
|
87
|
+
examples: [
|
|
88
|
+
'swoff help',
|
|
89
|
+
'swoff help init'
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Show help
|
|
95
|
+
function showHelp(commandName = null) {
|
|
96
|
+
if (commandName && commands[commandName]) {
|
|
97
|
+
const cmd = commands[commandName];
|
|
98
|
+
log.header(`Swoff ${commandName} Command`);
|
|
99
|
+
console.log(`Description: ${cmd.description}`);
|
|
100
|
+
console.log(`\nUsage: ${cmd.usage}`);
|
|
101
|
+
console.log(`\nExamples:`);
|
|
102
|
+
cmd.examples.forEach(ex => console.log(` ${ex}`));
|
|
103
|
+
} else {
|
|
104
|
+
log.header('Swoff CLI');
|
|
105
|
+
console.log(`${colors.dim}Swoff${colors.reset} - Offline-first web apps made easy\n`);
|
|
106
|
+
console.log(`Usage: ${colors.cyan}swoff <command> [options]${colors.reset}\n`);
|
|
107
|
+
console.log('Commands:');
|
|
108
|
+
Object.entries(commands).forEach(([name, cmd]) => {
|
|
109
|
+
console.log(` ${colors.green}${name.padEnd(12)}${colors.reset} ${cmd.description}`);
|
|
110
|
+
});
|
|
111
|
+
console.log(`\nRun ${colors.cyan}swoff help <command>${colors.reset} for more details on a specific command.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Init command - Create config file and directory structure
|
|
116
|
+
async function initCommand(framework = null) {
|
|
117
|
+
log.header('Initializing Swoff');
|
|
118
|
+
|
|
119
|
+
// Check for existing config
|
|
120
|
+
const configFiles = ['swoff.config.json', 'swoff.config.js'];
|
|
121
|
+
const existingConfig = configFiles.find(f => existsSync(join(projectRoot, f)));
|
|
122
|
+
|
|
123
|
+
if (existingConfig) {
|
|
124
|
+
log.warn(`Found existing ${existingConfig}. Skipping init.`);
|
|
125
|
+
log.info('To reinitialize, delete the config file first.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Create config based on framework or default
|
|
130
|
+
const defaultConfig = {
|
|
131
|
+
"$schema": "https://swoff.netlify.app/schema/v1.json",
|
|
132
|
+
"enabled": true,
|
|
133
|
+
"version": "from-package",
|
|
134
|
+
"minSupportedVersion": "1.0.0",
|
|
135
|
+
"serviceWorker": {
|
|
136
|
+
"autoUpdate": false,
|
|
137
|
+
"defaultStrategy": "cache-first",
|
|
138
|
+
"strategies": {
|
|
139
|
+
"/api/*": "network-first",
|
|
140
|
+
"/static/*": "cache-first"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"features": {
|
|
144
|
+
"versionedSw": true,
|
|
145
|
+
"offlineReads": true,
|
|
146
|
+
"mutationQueue": false,
|
|
147
|
+
"backgroundSync": false,
|
|
148
|
+
"pwa": true,
|
|
149
|
+
"auth": false,
|
|
150
|
+
"crossTabSync": true,
|
|
151
|
+
"tagInvalidation": true
|
|
152
|
+
},
|
|
153
|
+
"build": {
|
|
154
|
+
"outputDir": "dist",
|
|
155
|
+
"swFilename": "sw"
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Framework-specific adjustments
|
|
160
|
+
if (framework === 'react-vite' || framework === 'react-nextjs') {
|
|
161
|
+
defaultConfig.features.mutationQueue = true;
|
|
162
|
+
defaultConfig.serviceWorker.strategies['/assets/*'] = 'cache-first';
|
|
163
|
+
} else if (framework === 'vue-vite') {
|
|
164
|
+
defaultConfig.features.mutationQueue = true;
|
|
165
|
+
defaultConfig.serviceWorker.strategies['/assets/*'] = 'cache-first';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Write config file
|
|
169
|
+
const configPath = join(projectRoot, 'swoff.config.json');
|
|
170
|
+
writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
171
|
+
log.success(`Created swoff.config.json`);
|
|
172
|
+
|
|
173
|
+
// Create directory structure
|
|
174
|
+
const dirs = ['src/hooks', 'src/components', 'src/utils'];
|
|
175
|
+
dirs.forEach(dir => {
|
|
176
|
+
const dirPath = join(projectRoot, dir);
|
|
177
|
+
if (!existsSync(dirPath)) {
|
|
178
|
+
mkdirSync(dirPath, { recursive: true });
|
|
179
|
+
log.info(`Created ${dir}/`);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
log.success('Swoff initialized successfully!');
|
|
184
|
+
log.info(`\nNext steps:`);
|
|
185
|
+
log.help('1. Review swoff.config.json and customize as needed');
|
|
186
|
+
log.help('2. Run: swoff generate');
|
|
187
|
+
log.help('3. Read the docs: https://swoff.netlify.app/docs');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Generate command - Generate SW and/or files
|
|
191
|
+
async function generateCommand(options = {}) {
|
|
192
|
+
const { swOnly = false, filesOnly = false } = options;
|
|
193
|
+
|
|
194
|
+
log.header('Generating Swoff Files');
|
|
195
|
+
|
|
196
|
+
// Try to load config
|
|
197
|
+
const configFiles = ['swoff.config.json', 'swoff.config.js'];
|
|
198
|
+
let config = null;
|
|
199
|
+
let configPath = null;
|
|
200
|
+
|
|
201
|
+
for (const file of configFiles) {
|
|
202
|
+
const path = join(projectRoot, file);
|
|
203
|
+
if (existsSync(path)) {
|
|
204
|
+
configPath = path;
|
|
205
|
+
if (file.endsWith('.json')) {
|
|
206
|
+
config = JSON.parse(readFileSync(path, 'utf8'));
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!config) {
|
|
213
|
+
log.warn('No swoff.config.json found. Run "swoff init" first.');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
log.info(`Using config: ${configPath}`);
|
|
218
|
+
|
|
219
|
+
// Generate service worker
|
|
220
|
+
if (!filesOnly) {
|
|
221
|
+
log.info('Generating service worker...');
|
|
222
|
+
try {
|
|
223
|
+
await runGenerator('sw-generator.js');
|
|
224
|
+
} catch (err) {
|
|
225
|
+
log.error(`Service worker generation failed: ${err.message}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Generate supporting files
|
|
230
|
+
if (!swOnly) {
|
|
231
|
+
log.info('Generating supporting files...');
|
|
232
|
+
try {
|
|
233
|
+
await runGenerator('swoff-files-generator.js', [
|
|
234
|
+
'--project-root', projectRoot,
|
|
235
|
+
'--package-dir', packageDir
|
|
236
|
+
]);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
log.error(`File generation failed: ${err.message}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
log.success('Generation complete!');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Helper to run generators
|
|
246
|
+
function runGenerator(generatorName, extraArgs = []) {
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
const generatorPath = join(packageDir, 'src/lib/generators', generatorName);
|
|
249
|
+
|
|
250
|
+
// Check if generator exists
|
|
251
|
+
if (!existsSync(generatorPath)) {
|
|
252
|
+
reject(new Error(`Generator not found: ${generatorPath}`));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const proc = spawn('node', [generatorPath, ...extraArgs], {
|
|
257
|
+
cwd: projectRoot,
|
|
258
|
+
stdio: 'inherit'
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
proc.on('close', (code) => {
|
|
262
|
+
if (code === 0) resolve();
|
|
263
|
+
else reject(new Error(`Generator exited with code ${code}`));
|
|
264
|
+
});
|
|
265
|
+
proc.on('error', reject);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Validate command - Validate config file
|
|
270
|
+
async function validateCommand() {
|
|
271
|
+
log.header('Validating Swoff Configuration');
|
|
272
|
+
|
|
273
|
+
const configFiles = ['swoff.config.json', 'swoff.config.js'];
|
|
274
|
+
let config = null;
|
|
275
|
+
let configPath = null;
|
|
276
|
+
|
|
277
|
+
for (const file of configFiles) {
|
|
278
|
+
const path = join(projectRoot, file);
|
|
279
|
+
if (existsSync(path)) {
|
|
280
|
+
configPath = path;
|
|
281
|
+
if (file.endsWith('.json')) {
|
|
282
|
+
try {
|
|
283
|
+
config = JSON.parse(readFileSync(path, 'utf8'));
|
|
284
|
+
} catch (err) {
|
|
285
|
+
log.error(`Invalid JSON in ${file}: ${err.message}`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!config) {
|
|
294
|
+
log.warn('No swoff.config.json found. Run "swoff init" first.');
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
log.info(`Validating ${configPath}...`);
|
|
299
|
+
|
|
300
|
+
// Validate required fields
|
|
301
|
+
const requiredFields = ['enabled', 'version', 'serviceWorker', 'features', 'build'];
|
|
302
|
+
const missingFields = requiredFields.filter(field => !config[field]);
|
|
303
|
+
|
|
304
|
+
if (missingFields.length > 0) {
|
|
305
|
+
log.error(`Missing required fields: ${missingFields.join(', ')}`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Validate service worker config
|
|
310
|
+
const swRequired = ['defaultStrategy', 'autoUpdate'];
|
|
311
|
+
const swMissing = swRequired.filter(field => !config.serviceWorker[field]);
|
|
312
|
+
|
|
313
|
+
if (swMissing.length > 0) {
|
|
314
|
+
log.error(`Missing serviceWorker fields: ${swMissing.join(', ')}`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Validate features
|
|
319
|
+
const featureDefaults = ['versionedSw', 'offlineReads', 'pwa'];
|
|
320
|
+
featureDefaults.forEach(feature => {
|
|
321
|
+
if (config.features[feature] === undefined) {
|
|
322
|
+
log.warn(`Feature "${feature}" not set, using default: false`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Validate cache strategies
|
|
327
|
+
const validStrategies = ['cache-first', 'network-first', 'stale-while-revalidate', 'cache-only', 'network-only'];
|
|
328
|
+
if (config.serviceWorker.strategies) {
|
|
329
|
+
for (const [pattern, strategy] of Object.entries(config.serviceWorker.strategies)) {
|
|
330
|
+
if (!validStrategies.includes(strategy)) {
|
|
331
|
+
log.error(`Invalid strategy "${strategy}" for pattern "${pattern}". Valid: ${validStrategies.join(', ')}`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
log.success('Configuration is valid!');
|
|
338
|
+
log.info(`\nConfig summary:`);
|
|
339
|
+
log.help(`Version: ${config.version}`);
|
|
340
|
+
log.help(`Default strategy: ${config.serviceWorker.defaultStrategy}`);
|
|
341
|
+
log.help(`Features enabled: ${Object.entries(config.features).filter(([_, v]) => v).map(([k]) => k).join(', ')}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Add command - Add specific feature files
|
|
345
|
+
async function addCommand(feature) {
|
|
346
|
+
log.header(`Adding ${feature} feature`);
|
|
347
|
+
|
|
348
|
+
// Map feature names to config updates
|
|
349
|
+
const featureMap = {
|
|
350
|
+
'offline': { offlineReads: true },
|
|
351
|
+
'mutation-queue': { mutationQueue: true },
|
|
352
|
+
'mutationqueue': { mutationQueue: true },
|
|
353
|
+
'pwa': { pwa: true },
|
|
354
|
+
'cross-tab': { crossTabSync: true },
|
|
355
|
+
'crosstab': { crossTabSync: true },
|
|
356
|
+
'auth': { auth: true }
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const configUpdate = featureMap[feature.toLowerCase()];
|
|
360
|
+
|
|
361
|
+
if (!configUpdate) {
|
|
362
|
+
log.error(`Unknown feature: ${feature}`);
|
|
363
|
+
log.info(`Available features: offline, mutation-queue, pwa, cross-tab, auth`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Load or create config
|
|
368
|
+
let config = null;
|
|
369
|
+
let configPath = null;
|
|
370
|
+
|
|
371
|
+
for (const file of ['swoff.config.json', 'swoff.config.js']) {
|
|
372
|
+
const path = join(projectRoot, file);
|
|
373
|
+
if (existsSync(path)) {
|
|
374
|
+
configPath = path;
|
|
375
|
+
if (file.endsWith('.json')) {
|
|
376
|
+
config = JSON.parse(readFileSync(path, 'utf8'));
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!config) {
|
|
383
|
+
log.warn('No config found. Creating new config with feature...');
|
|
384
|
+
config = {
|
|
385
|
+
"$schema": "https://swoff.netlify.app/schema/v1.json",
|
|
386
|
+
"enabled": true,
|
|
387
|
+
"version": "from-package",
|
|
388
|
+
"minSupportedVersion": "1.0.0",
|
|
389
|
+
"serviceWorker": {
|
|
390
|
+
"autoUpdate": false,
|
|
391
|
+
"defaultStrategy": "cache-first",
|
|
392
|
+
"strategies": {}
|
|
393
|
+
},
|
|
394
|
+
"features": {
|
|
395
|
+
"versionedSw": true,
|
|
396
|
+
"offlineReads": false,
|
|
397
|
+
"mutationQueue": false,
|
|
398
|
+
"backgroundSync": false,
|
|
399
|
+
"pwa": false,
|
|
400
|
+
"auth": false,
|
|
401
|
+
"crossTabSync": false,
|
|
402
|
+
"tagInvalidation": true
|
|
403
|
+
},
|
|
404
|
+
"build": {
|
|
405
|
+
"outputDir": "dist",
|
|
406
|
+
"swFilename": "sw"
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
configPath = join(projectRoot, 'swoff.config.json');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Update config with feature
|
|
413
|
+
config.features = { ...config.features, ...configUpdate };
|
|
414
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
415
|
+
log.success(`Updated swoff.config.json with ${feature} feature`);
|
|
416
|
+
|
|
417
|
+
// Generate files
|
|
418
|
+
await generateCommand({ swOnly: false, filesOnly: false });
|
|
419
|
+
|
|
420
|
+
log.success(`${feature} feature added successfully!`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Main entry point
|
|
424
|
+
async function main() {
|
|
425
|
+
if (!command) {
|
|
426
|
+
showHelp();
|
|
427
|
+
process.exit(0);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
switch (command) {
|
|
431
|
+
case 'init':
|
|
432
|
+
const framework = options.includes('--framework')
|
|
433
|
+
? options[options.indexOf('--framework') + 1]
|
|
434
|
+
: null;
|
|
435
|
+
await initCommand(framework);
|
|
436
|
+
break;
|
|
437
|
+
|
|
438
|
+
case 'generate':
|
|
439
|
+
const swOnly = options.includes('--sw-only');
|
|
440
|
+
const filesOnly = options.includes('--files-only');
|
|
441
|
+
await generateCommand({ swOnly, filesOnly });
|
|
442
|
+
break;
|
|
443
|
+
|
|
444
|
+
case 'validate':
|
|
445
|
+
await validateCommand();
|
|
446
|
+
break;
|
|
447
|
+
|
|
448
|
+
case 'add':
|
|
449
|
+
const feature = options[0];
|
|
450
|
+
if (!feature) {
|
|
451
|
+
log.error('Please specify a feature to add');
|
|
452
|
+
log.info('Usage: swoff add <feature>');
|
|
453
|
+
log.info('Features: offline, mutation-queue, pwa, cross-tab, auth');
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
await addCommand(feature);
|
|
457
|
+
break;
|
|
458
|
+
|
|
459
|
+
case 'help':
|
|
460
|
+
case '--help':
|
|
461
|
+
case '-h':
|
|
462
|
+
showHelp(options[0]);
|
|
463
|
+
break;
|
|
464
|
+
|
|
465
|
+
default:
|
|
466
|
+
log.error(`Unknown command: ${command}`);
|
|
467
|
+
log.info(`Run "swoff help" for available commands`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
main().catch(err => {
|
|
473
|
+
log.error(`Error: ${err.message}`);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
});
|