@swoff/cli 0.0.2 → 0.2.0

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.
Files changed (100) hide show
  1. package/README.md +49 -0
  2. package/bin/swoff +0 -0
  3. package/dist/__tests__/assemble-sw.test.js +145 -0
  4. package/dist/__tests__/assemble-sw.test.js.map +1 -0
  5. package/dist/__tests__/cli-integration.test.js +128 -0
  6. package/dist/__tests__/cli-integration.test.js.map +1 -0
  7. package/dist/__tests__/config-types.test.js +82 -0
  8. package/dist/__tests__/config-types.test.js.map +1 -0
  9. package/dist/__tests__/detect-language.test.js +51 -0
  10. package/dist/__tests__/detect-language.test.js.map +1 -0
  11. package/dist/__tests__/file-generators.test.js +271 -0
  12. package/dist/__tests__/file-generators.test.js.map +1 -0
  13. package/dist/__tests__/invalidation-tags.test.js +99 -0
  14. package/dist/__tests__/invalidation-tags.test.js.map +1 -0
  15. package/dist/__tests__/loader.test.js +77 -0
  16. package/dist/__tests__/loader.test.js.map +1 -0
  17. package/dist/__tests__/validator.test.js +232 -0
  18. package/dist/__tests__/validator.test.js.map +1 -0
  19. package/dist/index.js +36 -348
  20. package/dist/index.js.map +1 -1
  21. package/dist/lib/cli/colors.js +14 -0
  22. package/dist/lib/cli/colors.js.map +1 -0
  23. package/dist/lib/cli/help.js +63 -0
  24. package/dist/lib/cli/help.js.map +1 -0
  25. package/dist/lib/cli/logger.js +13 -0
  26. package/dist/lib/cli/logger.js.map +1 -0
  27. package/dist/lib/commands/add.js +58 -0
  28. package/dist/lib/commands/add.js.map +1 -0
  29. package/dist/lib/commands/clean.js +39 -0
  30. package/dist/lib/commands/clean.js.map +1 -0
  31. package/dist/lib/commands/generate.js +84 -0
  32. package/dist/lib/commands/generate.js.map +1 -0
  33. package/dist/lib/commands/info.js +44 -0
  34. package/dist/lib/commands/info.js.map +1 -0
  35. package/dist/lib/commands/init.js +46 -0
  36. package/dist/lib/commands/init.js.map +1 -0
  37. package/dist/lib/commands/validate.js +30 -0
  38. package/dist/lib/commands/validate.js.map +1 -0
  39. package/dist/lib/config/loader.js +80 -0
  40. package/dist/lib/config/loader.js.map +1 -0
  41. package/dist/lib/config/validator.js +102 -0
  42. package/dist/lib/config/validator.js.map +1 -0
  43. package/dist/lib/generators/file-generators/background-sync.js +59 -0
  44. package/dist/lib/generators/file-generators/background-sync.js.map +1 -0
  45. package/dist/lib/generators/file-generators/cache.js +55 -0
  46. package/dist/lib/generators/file-generators/cache.js.map +1 -0
  47. package/dist/lib/generators/file-generators/context.js +16 -0
  48. package/dist/lib/generators/file-generators/context.js.map +1 -0
  49. package/dist/lib/generators/file-generators/fetch-wrapper.js +80 -0
  50. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -0
  51. package/dist/lib/generators/file-generators/indexeddb.js +73 -0
  52. package/dist/lib/generators/file-generators/indexeddb.js.map +1 -0
  53. package/dist/lib/generators/file-generators/invalidation-tags.js +91 -0
  54. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -0
  55. package/dist/lib/generators/file-generators/manifest.js +26 -0
  56. package/dist/lib/generators/file-generators/manifest.js.map +1 -0
  57. package/dist/lib/generators/file-generators/mutation-queue.js +226 -0
  58. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -0
  59. package/dist/lib/generators/file-generators/pwa-install.js +102 -0
  60. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -0
  61. package/dist/lib/generators/file-generators/reconcile.js +54 -0
  62. package/dist/lib/generators/file-generators/reconcile.js.map +1 -0
  63. package/dist/lib/generators/file-generators/store.js +75 -0
  64. package/dist/lib/generators/file-generators/store.js.map +1 -0
  65. package/dist/lib/generators/file-generators/sw-generator-build.js +70 -0
  66. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -0
  67. package/dist/lib/generators/file-generators/sw-injector.js +195 -0
  68. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -0
  69. package/dist/lib/generators/file-generators/sw-template.js +143 -0
  70. package/dist/lib/generators/file-generators/sw-template.js.map +1 -0
  71. package/dist/lib/generators/file-generators/type-definitions.js +69 -0
  72. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -0
  73. package/dist/lib/generators/sw-generator.js +37 -239
  74. package/dist/lib/generators/sw-generator.js.map +1 -1
  75. package/dist/lib/generators/sw-sections/activate-handler.js +16 -0
  76. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -0
  77. package/dist/lib/generators/sw-sections/assemble-sw.js +38 -0
  78. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -0
  79. package/dist/lib/generators/sw-sections/background-sync-handler.js +106 -0
  80. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -0
  81. package/dist/lib/generators/sw-sections/config-header.js +15 -0
  82. package/dist/lib/generators/sw-sections/config-header.js.map +1 -0
  83. package/dist/lib/generators/sw-sections/default-template.js +51 -0
  84. package/dist/lib/generators/sw-sections/default-template.js.map +1 -0
  85. package/dist/lib/generators/sw-sections/fetch-handler.js +210 -0
  86. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -0
  87. package/dist/lib/generators/sw-sections/install-handler.js +35 -0
  88. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -0
  89. package/dist/lib/generators/sw-sections/message-handler.js +20 -0
  90. package/dist/lib/generators/sw-sections/message-handler.js.map +1 -0
  91. package/dist/lib/generators/sw-sections/tag-management.js +69 -0
  92. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -0
  93. package/dist/lib/generators/swoff-files-generator.js +80 -1043
  94. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  95. package/dist/lib/shared/config-types.js +87 -0
  96. package/dist/lib/shared/config-types.js.map +1 -0
  97. package/dist/lib/utils/detect-language.js +33 -0
  98. package/dist/lib/utils/detect-language.js.map +1 -0
  99. package/package.json +6 -2
  100. package/src/lib/templates/sw-template.js +23 -6
@@ -0,0 +1,232 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { validateConfig } from "../lib/config/validator.js";
3
+ describe("validateConfig", () => {
4
+ const validConfig = {
5
+ enabled: true,
6
+ version: "from-package",
7
+ minSupportedVersion: "1.0.0",
8
+ serviceWorker: {
9
+ autoRegister: true,
10
+ autoUpdate: false,
11
+ defaultStrategy: "cache-first",
12
+ strategies: {},
13
+ },
14
+ features: {
15
+ versionedSw: true,
16
+ offlineReads: true,
17
+ mutationQueue: false,
18
+ backgroundSync: false,
19
+ pwa: true,
20
+ auth: false,
21
+ crossTabSync: true,
22
+ tagInvalidation: true,
23
+ clientRegistration: true,
24
+ indexeddb: false,
25
+ },
26
+ pwa: { preventDefaultInstall: false },
27
+ build: { outputDir: "dist", swFilename: "sw" },
28
+ };
29
+ describe("required fields", () => {
30
+ it("passes valid config", () => {
31
+ const errors = validateConfig(validConfig);
32
+ expect(errors).toEqual([]);
33
+ });
34
+ it("fails when enabled is missing", () => {
35
+ const { enabled, ...rest } = validConfig;
36
+ const errors = validateConfig(rest);
37
+ expect(errors).toHaveLength(1);
38
+ expect(errors[0]).toContain("Missing required fields");
39
+ });
40
+ it("fails when version is missing", () => {
41
+ const { version, ...rest } = validConfig;
42
+ const errors = validateConfig(rest);
43
+ expect(errors[0]).toContain("Missing required fields");
44
+ });
45
+ it("fails when serviceWorker is missing", () => {
46
+ const { serviceWorker, ...rest } = validConfig;
47
+ const errors = validateConfig(rest);
48
+ expect(errors[0]).toContain("Missing required fields");
49
+ });
50
+ it("fails when features is missing", () => {
51
+ const { features, ...rest } = validConfig;
52
+ const errors = validateConfig(rest);
53
+ expect(errors[0]).toContain("Missing required fields");
54
+ });
55
+ it("fails when build is missing", () => {
56
+ const { build, ...rest } = validConfig;
57
+ const errors = validateConfig(rest);
58
+ expect(errors[0]).toContain("Missing required fields");
59
+ });
60
+ });
61
+ describe("serviceWorker validation", () => {
62
+ it("rejects invalid autoRegister type", () => {
63
+ const config = {
64
+ ...validConfig,
65
+ serviceWorker: { ...validConfig.serviceWorker, autoRegister: "yes" },
66
+ };
67
+ const errors = validateConfig(config);
68
+ expect(errors).toContain("serviceWorker.autoRegister must be a boolean");
69
+ });
70
+ it("rejects invalid autoUpdate type", () => {
71
+ const config = {
72
+ ...validConfig,
73
+ serviceWorker: { ...validConfig.serviceWorker, autoUpdate: 1 },
74
+ };
75
+ const errors = validateConfig(config);
76
+ expect(errors).toContain("serviceWorker.autoUpdate must be a boolean");
77
+ });
78
+ it("rejects invalid defaultStrategy", () => {
79
+ const config = {
80
+ ...validConfig,
81
+ serviceWorker: { ...validConfig.serviceWorker, defaultStrategy: "magic-cache" },
82
+ };
83
+ const errors = validateConfig(config);
84
+ expect(errors[0]).toContain("Invalid defaultStrategy");
85
+ });
86
+ it("rejects invalid strategy in strategies", () => {
87
+ const config = {
88
+ ...validConfig,
89
+ serviceWorker: {
90
+ ...validConfig.serviceWorker,
91
+ strategies: { "/api/*": "invalid" },
92
+ },
93
+ };
94
+ const errors = validateConfig(config);
95
+ expect(errors[0]).toContain("Invalid strategy");
96
+ });
97
+ it("accepts all valid strategies", () => {
98
+ const strategies = ["cache-first", "network-first", "stale-while-revalidate", "cache-only", "network-only"];
99
+ for (const strategy of strategies) {
100
+ const config = {
101
+ ...validConfig,
102
+ serviceWorker: { ...validConfig.serviceWorker, defaultStrategy: strategy },
103
+ };
104
+ const errors = validateConfig(config);
105
+ expect(errors).toEqual([]);
106
+ }
107
+ });
108
+ });
109
+ describe("features validation", () => {
110
+ it("rejects unknown features", () => {
111
+ const config = {
112
+ ...validConfig,
113
+ features: { ...validConfig.features, unknownFeature: true },
114
+ };
115
+ const errors = validateConfig(config);
116
+ expect(errors).toContain('Unknown feature "unknownFeature"');
117
+ });
118
+ it("rejects non-boolean feature values", () => {
119
+ const config = {
120
+ ...validConfig,
121
+ features: { ...validConfig.features, pwa: "yes" },
122
+ };
123
+ const errors = validateConfig(config);
124
+ expect(errors[0]).toContain('Feature "pwa" must be a boolean');
125
+ });
126
+ });
127
+ describe("version validation", () => {
128
+ it("accepts from-package", () => {
129
+ const config = { ...validConfig, version: "from-package" };
130
+ expect(validateConfig(config)).toEqual([]);
131
+ });
132
+ it("accepts valid semver", () => {
133
+ const config = { ...validConfig, version: "1.2.3" };
134
+ expect(validateConfig(config)).toEqual([]);
135
+ });
136
+ it("rejects invalid version format", () => {
137
+ const config = { ...validConfig, version: "latest" };
138
+ const errors = validateConfig(config);
139
+ expect(errors[0]).toContain("Invalid version");
140
+ });
141
+ it("rejects partial semver", () => {
142
+ const config = { ...validConfig, version: "1.0" };
143
+ const errors = validateConfig(config);
144
+ expect(errors[0]).toContain("Invalid version");
145
+ });
146
+ });
147
+ describe("minSupportedVersion validation", () => {
148
+ it("accepts valid semver", () => {
149
+ const config = { ...validConfig, minSupportedVersion: "0.1.0" };
150
+ expect(validateConfig(config)).toEqual([]);
151
+ });
152
+ it("rejects invalid format", () => {
153
+ const config = { ...validConfig, minSupportedVersion: "beta" };
154
+ const errors = validateConfig(config);
155
+ expect(errors[0]).toContain("Invalid minSupportedVersion");
156
+ });
157
+ });
158
+ describe("build validation", () => {
159
+ it("rejects non-string outputDir", () => {
160
+ const config = {
161
+ ...validConfig,
162
+ build: { ...validConfig.build, outputDir: 123 },
163
+ };
164
+ const errors = validateConfig(config);
165
+ expect(errors).toContain("build.outputDir must be a string");
166
+ });
167
+ it("rejects non-string swFilename", () => {
168
+ const config = {
169
+ ...validConfig,
170
+ build: { ...validConfig.build, swFilename: true },
171
+ };
172
+ const errors = validateConfig(config);
173
+ expect(errors).toContain("build.swFilename must be a string");
174
+ });
175
+ });
176
+ describe("pwa validation", () => {
177
+ it("rejects non-boolean preventDefaultInstall", () => {
178
+ const config = {
179
+ ...validConfig,
180
+ pwa: { preventDefaultInstall: "true" },
181
+ };
182
+ const errors = validateConfig(config);
183
+ expect(errors).toContain("pwa.preventDefaultInstall must be a boolean");
184
+ });
185
+ it("accepts boolean preventDefaultInstall", () => {
186
+ const config = { ...validConfig, pwa: { preventDefaultInstall: true } };
187
+ expect(validateConfig(config)).toEqual([]);
188
+ });
189
+ });
190
+ describe("database validation", () => {
191
+ it("rejects non-string database name", () => {
192
+ const config = {
193
+ ...validConfig,
194
+ database: { name: 123, stores: [] },
195
+ };
196
+ const errors = validateConfig(config);
197
+ expect(errors).toContain("database.name must be a string");
198
+ });
199
+ it("rejects invalid database name pattern", () => {
200
+ const config = {
201
+ ...validConfig,
202
+ database: { name: "my db", stores: [] },
203
+ };
204
+ const errors = validateConfig(config);
205
+ expect(errors[0]).toContain("must match pattern");
206
+ });
207
+ it("rejects non-array stores", () => {
208
+ const config = {
209
+ ...validConfig,
210
+ database: { name: "app-db", stores: "todos" },
211
+ };
212
+ const errors = validateConfig(config);
213
+ expect(errors).toContain("database.stores must be an array");
214
+ });
215
+ it("rejects invalid store names", () => {
216
+ const config = {
217
+ ...validConfig,
218
+ database: { name: "app-db", stores: ["my store"] },
219
+ };
220
+ const errors = validateConfig(config);
221
+ expect(errors[0]).toContain("must match pattern");
222
+ });
223
+ it("accepts valid database config", () => {
224
+ const config = {
225
+ ...validConfig,
226
+ database: { name: "app-db", stores: ["todos", "users"] },
227
+ };
228
+ expect(validateConfig(config)).toEqual([]);
229
+ });
230
+ });
231
+ });
232
+ //# sourceMappingURL=validator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.test.js","sourceRoot":"","sources":["../../src/__tests__/validator.test.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,364 +1,46 @@
1
1
  /**
2
2
  * Swoff CLI - Main Entry Point
3
3
  *
4
- * Command-line interface for managing Swoff in your project.
5
- *
6
4
  * Usage:
7
5
  * swoff init Initialize Swoff in current directory
8
6
  * swoff generate Generate service worker and files
9
7
  * swoff validate Validate swoff.config.json
10
8
  * swoff add <feature> Add specific feature files
9
+ * swoff info Show configuration summary
10
+ * swoff clean Remove old versioned SW files
11
11
  * swoff --help Show help
12
12
  */
13
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
13
+ import { readFileSync } from "fs";
14
14
  import { join, dirname } from "path";
15
15
  import { fileURLToPath } from "url";
16
- import { spawn } from "child_process";
16
+ import { log } from "./lib/cli/logger.js";
17
+ import { showHelp } from "./lib/cli/help.js";
18
+ import { initCommand } from "./lib/commands/init.js";
19
+ import { generateCommand } from "./lib/commands/generate.js";
20
+ import { validateCommand } from "./lib/commands/validate.js";
21
+ import { addCommand } from "./lib/commands/add.js";
22
+ import { infoCommand } from "./lib/commands/info.js";
23
+ import { cleanCommand } from "./lib/commands/clean.js";
17
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
25
  const packageDir = join(__dirname, "..");
19
26
  const projectRoot = process.cwd();
20
- const colors = {
21
- reset: "\x1b[0m",
22
- bright: "\x1b[1m",
23
- dim: "\x1b[2m",
24
- green: "\x1b[32m",
25
- yellow: "\x1b[33m",
26
- blue: "\x1b[34m",
27
- red: "\x1b[31m",
28
- cyan: "\x1b[36m",
29
- };
30
- const log = {
31
- info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
32
- success: (msg) => console.log(`${colors.green}✅${colors.reset} ${msg}`),
33
- warn: (msg) => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`),
34
- error: (msg) => console.log(`${colors.red}❌${colors.reset} ${msg}`),
35
- help: (msg) => console.log(` ${colors.cyan}${msg}${colors.reset}`),
36
- header: (msg) => console.log(`\n${colors.bright}${msg}${colors.reset}\n`),
37
- };
27
+ const cliVersion = (() => {
28
+ try {
29
+ const pkg = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
30
+ return pkg.version || "unknown";
31
+ }
32
+ catch {
33
+ return "unknown";
34
+ }
35
+ })();
38
36
  const args = process.argv.slice(2);
39
37
  const command = args[0];
40
38
  const options = args.slice(1);
41
- const commands = {
42
- init: {
43
- description: "Initialize Swoff in current directory",
44
- usage: "swoff init [--framework react-vite|nextjs|vue-vite]",
45
- examples: ["swoff init", "swoff init --framework react-vite"],
46
- },
47
- generate: {
48
- description: "Generate service worker and supporting files",
49
- usage: "swoff generate [--sw-only|--files-only]",
50
- examples: ["swoff generate", "swoff generate --sw-only", "swoff generate --files-only"],
51
- },
52
- validate: {
53
- description: "Validate swoff.config.json",
54
- usage: "swoff validate",
55
- examples: ["swoff validate"],
56
- },
57
- add: {
58
- description: "Add specific feature files",
59
- usage: "swoff add <feature>",
60
- examples: ["swoff add offline", "swoff add pwa", "swoff add mutation-queue"],
61
- },
62
- help: {
63
- description: "Show help information",
64
- usage: "swoff help [command]",
65
- examples: ["swoff help", "swoff help init"],
66
- },
67
- };
68
- function showHelp(commandName) {
69
- if (commandName && commands[commandName]) {
70
- const cmd = commands[commandName];
71
- log.header(`Swoff ${commandName} Command`);
72
- console.log(`Description: ${cmd.description}`);
73
- console.log(`\nUsage: ${cmd.usage}`);
74
- console.log(`\nExamples:`);
75
- cmd.examples.forEach((ex) => console.log(` ${ex}`));
76
- }
77
- else {
78
- log.header("Swoff CLI");
79
- console.log(`${colors.dim}Swoff${colors.reset} - Offline-first web apps made easy\n`);
80
- console.log(`Usage: ${colors.cyan}swoff <command> [options]${colors.reset}\n`);
81
- console.log("Commands:");
82
- Object.entries(commands).forEach(([name, cmd]) => {
83
- console.log(` ${colors.green}${name.padEnd(12)}${colors.reset} ${cmd.description}`);
84
- });
85
- console.log(`\nRun ${colors.cyan}swoff help <command>${colors.reset} for more details on a specific command.`);
86
- }
87
- }
88
- async function initCommand(framework) {
89
- log.header("Initializing Swoff");
90
- const configFiles = ["swoff.config.json", "swoff.config.js"];
91
- const existingConfig = configFiles.find((f) => existsSync(join(projectRoot, f)));
92
- if (existingConfig) {
93
- log.warn(`Found existing ${existingConfig}. Skipping init.`);
94
- log.info("To reinitialize, delete the config file first.");
95
- return;
96
- }
97
- const defaultConfig = {
98
- $schema: "https://swoff.netlify.app/schema/v1.json",
99
- enabled: true,
100
- version: "from-package",
101
- minSupportedVersion: "1.0.0",
102
- serviceWorker: {
103
- autoUpdate: false,
104
- defaultStrategy: "cache-first",
105
- strategies: {
106
- "/api/*": "network-first",
107
- "/static/*": "cache-first",
108
- },
109
- },
110
- features: {
111
- versionedSw: true,
112
- offlineReads: true,
113
- mutationQueue: false,
114
- backgroundSync: false,
115
- pwa: true,
116
- auth: false,
117
- crossTabSync: true,
118
- tagInvalidation: true,
119
- clientRegistration: true,
120
- },
121
- build: {
122
- outputDir: "dist",
123
- swFilename: "sw",
124
- },
125
- };
126
- if (framework === "react-vite" || framework === "react-nextjs") {
127
- defaultConfig.features.mutationQueue = true;
128
- defaultConfig.serviceWorker.strategies = {
129
- "/api/*": "network-first",
130
- "/static/*": "cache-first",
131
- "/assets/*": "cache-first",
132
- };
133
- }
134
- else if (framework === "vue-vite") {
135
- defaultConfig.features.mutationQueue = true;
136
- defaultConfig.serviceWorker.strategies = {
137
- "/api/*": "network-first",
138
- "/static/*": "cache-first",
139
- "/assets/*": "cache-first",
140
- };
141
- }
142
- const configPath = join(projectRoot, "swoff.config.json");
143
- writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
144
- log.success(`Created swoff.config.json`);
145
- const dirs = ["src/hooks", "src/components", "src/utils", "swoff"];
146
- for (const dir of dirs) {
147
- const dirPath = join(projectRoot, dir);
148
- if (!existsSync(dirPath)) {
149
- mkdirSync(dirPath, { recursive: true });
150
- log.info(`Created ${dir}/`);
151
- }
152
- }
153
- log.success("Swoff initialized successfully!");
154
- await generateCommand({ swOnly: false, filesOnly: false });
155
- log.info(`\nNext steps:`);
156
- log.help("1. Review swoff.config.json and customize as needed");
157
- log.help("2. Run: swoff generate");
158
- log.help("3. Read the docs: https://swoff.netlify.app/docs");
159
- }
160
- async function generateCommand(options = {}) {
161
- const { swOnly = false, filesOnly = false, language } = options;
162
- log.header("Generating Swoff Files");
163
- const configFiles = ["swoff.config.json", "swoff.config.js"];
164
- let config = null;
165
- let configPath = null;
166
- for (const file of configFiles) {
167
- const path = join(projectRoot, file);
168
- if (existsSync(path)) {
169
- configPath = path;
170
- if (file.endsWith(".json")) {
171
- config = JSON.parse(readFileSync(path, "utf8"));
172
- }
173
- break;
174
- }
175
- }
176
- if (!config) {
177
- log.warn('No swoff.config.json found. Run "swoff init" first.');
178
- return;
179
- }
180
- log.info(`Using config: ${configPath}`);
181
- const detectedLang = language ?? detectProjectLanguage();
182
- log.info(`Detected project language: ${detectedLang}`);
183
- if (!filesOnly) {
184
- log.info("Generating service worker...");
185
- try {
186
- await runGenerator("sw-generator.js");
187
- }
188
- catch (err) {
189
- log.error(`Service worker generation failed: ${err instanceof Error ? err.message : String(err)}`);
190
- }
191
- }
192
- if (!swOnly) {
193
- log.info("Generating supporting files...");
194
- try {
195
- await runGenerator("swoff-files-generator.js", [
196
- "--project-root", projectRoot,
197
- "--package-dir", packageDir,
198
- "--language", detectedLang,
199
- "--config-path", configPath,
200
- ]);
201
- }
202
- catch (err) {
203
- log.error(`File generation failed: ${err instanceof Error ? err.message : String(err)}`);
204
- }
205
- }
206
- log.success("Generation complete!");
207
- }
208
- function runGenerator(generatorName, extraArgs = []) {
209
- return new Promise((resolve, reject) => {
210
- const generatorPath = join(packageDir, "dist", "lib", "generators", generatorName);
211
- if (!existsSync(generatorPath)) {
212
- reject(new Error(`Generator not found: ${generatorPath}`));
213
- return;
214
- }
215
- const proc = spawn("node", [generatorPath, ...extraArgs], {
216
- cwd: projectRoot,
217
- stdio: "inherit",
218
- });
219
- proc.on("close", (code) => {
220
- if (code === 0)
221
- resolve();
222
- else
223
- reject(new Error(`Generator exited with code ${code}`));
224
- });
225
- proc.on("error", (err) => reject(err));
226
- });
227
- }
228
- async function validateCommand() {
229
- log.header("Validating Swoff Configuration");
230
- const configFiles = ["swoff.config.json", "swoff.config.js"];
231
- let config = null;
232
- let configPath = null;
233
- for (const file of configFiles) {
234
- const path = join(projectRoot, file);
235
- if (existsSync(path)) {
236
- configPath = path;
237
- if (file.endsWith(".json")) {
238
- try {
239
- config = JSON.parse(readFileSync(path, "utf8"));
240
- }
241
- catch (err) {
242
- log.error(`Invalid JSON in ${file}: ${err instanceof Error ? err.message : String(err)}`);
243
- return;
244
- }
245
- }
246
- break;
247
- }
248
- }
249
- if (!config) {
250
- log.warn('No swoff.config.json found. Run "swoff init" first.');
251
- return;
252
- }
253
- log.info(`Validating ${configPath}...`);
254
- const requiredFields = ["enabled", "version", "serviceWorker", "features", "build"];
255
- const missingFields = requiredFields.filter((field) => !config[field]);
256
- if (missingFields.length > 0) {
257
- log.error(`Missing required fields: ${missingFields.join(", ")}`);
258
- return;
259
- }
260
- log.success("Configuration is valid!");
261
- log.info(`\nConfig summary:`);
262
- log.help(`Version: ${config.version}`);
263
- log.help(`Default strategy: ${config.serviceWorker.defaultStrategy}`);
264
- log.help(`Features enabled: ${Object.entries(config.features)
265
- .filter(([_, v]) => v)
266
- .map(([k]) => k)
267
- .join(", ")}`);
268
- }
269
- async function addCommand(feature) {
270
- log.header(`Adding ${feature} feature`);
271
- const featureMap = {
272
- offline: { offlineReads: true },
273
- "mutation-queue": { mutationQueue: true },
274
- mutationqueue: { mutationQueue: true },
275
- pwa: { pwa: true },
276
- "cross-tab": { crossTabSync: true },
277
- crosstab: { crossTabSync: true },
278
- auth: { auth: true },
279
- };
280
- const configUpdate = featureMap[feature.toLowerCase()];
281
- if (!configUpdate) {
282
- log.error(`Unknown feature: ${feature}`);
283
- log.info(`Available features: offline, mutation-queue, pwa, cross-tab, auth`);
284
- return;
285
- }
286
- let config = null;
287
- let configPath = join(projectRoot, "swoff.config.json");
288
- for (const file of ["swoff.config.json", "swoff.config.js"]) {
289
- const path = join(projectRoot, file);
290
- if (existsSync(path)) {
291
- configPath = path;
292
- if (file.endsWith(".json")) {
293
- config = JSON.parse(readFileSync(path, "utf8"));
294
- }
295
- break;
296
- }
297
- }
298
- if (!config) {
299
- log.warn("No config found. Creating new config with feature...");
300
- config = {
301
- $schema: "https://swoff.netlify.app/schema/v1.json",
302
- enabled: true,
303
- version: "from-package",
304
- minSupportedVersion: "0.0.0",
305
- serviceWorker: {
306
- autoUpdate: false,
307
- defaultStrategy: "cache-first",
308
- strategies: {},
309
- },
310
- features: {
311
- versionedSw: true,
312
- offlineReads: false,
313
- mutationQueue: false,
314
- backgroundSync: false,
315
- pwa: false,
316
- auth: false,
317
- crossTabSync: false,
318
- tagInvalidation: true,
319
- clientRegistration: true,
320
- },
321
- build: {
322
- outputDir: "dist",
323
- swFilename: "sw",
324
- },
325
- };
326
- }
327
- config.features = { ...config.features, ...configUpdate };
328
- writeFileSync(configPath, JSON.stringify(config, null, 2));
329
- log.success(`Updated swoff.config.json with ${feature} feature`);
330
- await generateCommand({ swOnly: false, filesOnly: false });
331
- log.success(`${feature} feature added successfully!`);
332
- }
333
- function detectProjectLanguage() {
334
- if (existsSync(join(projectRoot, "tsconfig.json")))
335
- return "ts";
336
- const pkgPath = join(projectRoot, "package.json");
337
- if (existsSync(pkgPath)) {
338
- try {
339
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
340
- if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript)
341
- return "ts";
342
- }
343
- catch { }
344
- }
345
- const srcDir = join(projectRoot, "src");
346
- if (existsSync(srcDir)) {
347
- const tsFiles = ["ts", "tsx"].some((ext) => {
348
- try {
349
- const { readdirSync } = require("fs");
350
- return readdirSync(srcDir, { withFileTypes: true }).some((entry) => entry.isFile() && entry.name.endsWith(`.${ext}`));
351
- }
352
- catch {
353
- return false;
354
- }
355
- });
356
- if (tsFiles)
357
- return "ts";
358
- }
359
- return "js";
360
- }
361
39
  async function main() {
40
+ if (command === "--version" || command === "-v") {
41
+ console.log(cliVersion);
42
+ process.exit(0);
43
+ }
362
44
  if (!command) {
363
45
  showHelp();
364
46
  process.exit(0);
@@ -367,29 +49,35 @@ async function main() {
367
49
  case "init": {
368
50
  const frameworkIdx = options.indexOf("--framework");
369
51
  const framework = frameworkIdx !== -1 ? options[frameworkIdx + 1] : undefined;
370
- await initCommand(framework);
52
+ await initCommand(projectRoot, framework);
371
53
  break;
372
54
  }
373
55
  case "generate": {
374
56
  const swOnly = options.includes("--sw-only");
375
57
  const filesOnly = options.includes("--files-only");
376
- await generateCommand({ swOnly, filesOnly });
58
+ await generateCommand(projectRoot, { swOnly, filesOnly });
377
59
  break;
378
60
  }
379
61
  case "validate":
380
- await validateCommand();
62
+ await validateCommand(projectRoot);
381
63
  break;
382
64
  case "add": {
383
65
  const feature = options[0];
384
66
  if (!feature) {
385
67
  log.error("Please specify a feature to add");
386
68
  log.info("Usage: swoff add <feature>");
387
- log.info("Features: offline, mutation-queue, pwa, cross-tab, auth");
69
+ log.info("Features: offline, mutation-queue, pwa, cross-tab, auth, tag-invalidation, background-sync, indexeddb");
388
70
  process.exit(1);
389
71
  }
390
- await addCommand(feature);
72
+ await addCommand(projectRoot, feature);
391
73
  break;
392
74
  }
75
+ case "info":
76
+ await infoCommand(projectRoot);
77
+ break;
78
+ case "clean":
79
+ await cleanCommand(projectRoot);
80
+ break;
393
81
  case "help":
394
82
  case "--help":
395
83
  case "-h":
@@ -397,7 +85,7 @@ async function main() {
397
85
  break;
398
86
  default:
399
87
  log.error(`Unknown command: ${command}`);
400
- log.info(`Run "swoff help" for available commands`);
88
+ log.info('Run "swoff help" for available commands');
401
89
  process.exit(1);
402
90
  }
403
91
  }