generatesaas 0.1.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.
@@ -0,0 +1,306 @@
1
+ ---
2
+ name: generatesaas-update
3
+ description: Update GenerateSaaS project to latest boilerplate version. Handles version checks, file classification, generates an update plan for user review, and applies changes while preserving user customizations.
4
+ ---
5
+
6
+ # GenerateSaaS Update
7
+
8
+ Update your project to the latest GenerateSaaS boilerplate version while preserving all your customizations.
9
+
10
+ ## Core Principles
11
+
12
+ 1. **User changes always come first** — Every update must preserve the user's functionality, business logic, and intentional modifications. If an upstream change conflicts with something the user built, the user's version wins.
13
+ 2. **Never break user code** — If applying an upstream change would break the user's customizations or doesn't make sense in the context of their modifications, skip it and document why.
14
+ 3. **Plan before acting** — Generate a full update plan. The user reviews and approves before anything is applied.
15
+ 4. **Track everything** — Every change is tracked in a checklist. Mark items as completed, skipped, or failed so the user knows exactly what happened.
16
+
17
+ ## Workflow
18
+
19
+ ### Step 1: Prepare Update Data
20
+
21
+ ```bash
22
+ node .claude/skills/generatesaas-update/scripts/prepare-update.js
23
+ ```
24
+
25
+ Reads the staged update from `.generatesaas/staging/` and computes local diffs. Creates:
26
+ - `references/changelog.md` — what changed and why
27
+ - `references/diffs/*.diff` — unified diffs for each modified file
28
+ - `references/update-manifest.json` — lists of added, modified, and removed files
29
+
30
+ ### Step 2: Review Changelog
31
+
32
+ Read `references/changelog.md` to understand:
33
+ - Breaking changes that need attention
34
+ - New features being added
35
+ - Bug fixes included
36
+ - Migration steps if any
37
+
38
+ ### Step 3: Classify Files
39
+
40
+ ```bash
41
+ node .claude/skills/generatesaas-update/scripts/classify-files.js
42
+ ```
43
+
44
+ Compares local file hashes (from `.generatesaas/hashes.json`) against current files to determine which the user has customized. Outputs `references/classification.json` with categories:
45
+
46
+ - **unmodified** — file matches the original hash, safe to auto-update
47
+ - **modified** — user has customized this file, needs careful merge
48
+ - **deleted** — user deleted this file, skip update
49
+ - **new** — file doesn't exist locally, safe to create
50
+ - **removed** — file was removed upstream, review before deleting
51
+
52
+ ### Step 4: Generate Update Plan
53
+
54
+ **This is a critical step.** After classification, generate the update plan file at:
55
+
56
+ ```
57
+ .generatesaas/updates/update-{currentVersion}-to-{targetVersion}.md
58
+ ```
59
+
60
+ The plan must follow this format:
61
+
62
+ ```markdown
63
+ # Update: {currentVersion} → {targetVersion}
64
+
65
+ Date: {YYYY-MM-DD}
66
+
67
+ ## Summary
68
+
69
+ {Brief description of what this update includes based on the changelog}
70
+
71
+ ## Auto-Updates (Unmodified Files)
72
+
73
+ These files haven't been modified and will be updated automatically:
74
+
75
+ - [x] `path/to/file.ts` — {brief description of change}
76
+ - [x] `path/to/other.ts` — {brief description of change}
77
+
78
+ ## New Files
79
+
80
+ These files are new in this version and will be created:
81
+
82
+ - [x] `path/to/new-file.ts` — {what this file does}
83
+
84
+ ## Manual Merges (Modified Files)
85
+
86
+ These files have been customized. Changes will be merged preserving your modifications:
87
+
88
+ - [ ] `path/to/modified.ts` — {what upstream changed vs what you changed}
89
+ - [ ] `path/to/config.ts` — {description}
90
+
91
+ ## Removed Upstream
92
+
93
+ These files were removed in the new version. Review before deleting:
94
+
95
+ - [ ] `path/to/old-file.ts` — {why it was removed}
96
+
97
+ ## Skipped
98
+
99
+ Changes that were not applied (with reasons):
100
+
101
+ {empty until items are skipped during execution}
102
+ ```
103
+
104
+ **Present this plan to the user.** Explain the key changes and ask if they want to exclude anything. Wait for confirmation before proceeding.
105
+
106
+ If the user wants to exclude items, move them to the **Skipped** section with the reason "Excluded by user".
107
+
108
+ ### Step 5: Apply Auto-Updates
109
+
110
+ ```bash
111
+ node .claude/skills/generatesaas-update/scripts/apply-auto.js
112
+ ```
113
+
114
+ Automatically updates `unmodified` files and creates `new` files. These are safe because:
115
+ - Unmodified files have no user changes to preserve
116
+ - New files don't conflict with anything
117
+
118
+ After this completes, mark all auto-update and new file items as `[x]` in the plan.
119
+
120
+ ### Step 6: Manual Merge (Modified Files)
121
+
122
+ For each file in the `modified` category, work through the plan checklist one by one:
123
+
124
+ 1. Read the corresponding diff at `references/diffs/<path>.diff`
125
+ 2. Read the current local file
126
+ 3. Understand what the user changed and why
127
+ 4. Apply upstream changes that are compatible with the user's modifications
128
+ 5. **Skip changes that would break user functionality** — move to Skipped section with explanation
129
+ 6. Mark the item as `[x]` in the plan when done
130
+
131
+ For `removed` files:
132
+ 1. Check if the user depends on the file
133
+ 2. If the user modified it or other files import it, move to Skipped with explanation
134
+ 3. If truly unused, delete it and mark as `[x]`
135
+
136
+ **After each file, update the plan file** so progress is tracked in real-time.
137
+
138
+ ### Step 7: Complete Update
139
+
140
+ After all items are processed:
141
+
142
+ 1. Review the plan — verify every item is either `[x]` (completed) or listed in Skipped with a reason
143
+ 2. If any items are still unchecked, address them or move to Skipped with explanation
144
+ 3. Run the completion script:
145
+
146
+ ```bash
147
+ node .claude/skills/generatesaas-update/scripts/complete-update.js
148
+ ```
149
+
150
+ This regenerates `.generatesaas/hashes.json` with fresh file hashes and updates the version in `.generatesaas/manifest.json`. Cleans up the `references/` directory.
151
+
152
+ 4. Add a final status to the plan file:
153
+
154
+ ```markdown
155
+ ## Result
156
+
157
+ - **Status**: Completed
158
+ - **Files updated**: {count}
159
+ - **Files created**: {count}
160
+ - **Files merged**: {count}
161
+ - **Items skipped**: {count}
162
+ - **Post-update steps**: {e.g., "Run pnpm install", "Run migrations", etc.}
163
+ ```
164
+
165
+ ## Merge Strategies
166
+
167
+ ### Config Files (`*.config.ts`, `config/index.ts`, `config/pricing.ts`)
168
+
169
+ Config files are the most commonly customized. The upstream diff usually adds new options or changes defaults.
170
+
171
+ - Preserve all user-set values
172
+ - Add new config keys with their default values
173
+ - If a key was renamed upstream, migrate the user's value to the new key
174
+ - Keep user's formatting preferences if they differ
175
+
176
+ ### Components (`.vue`, `.tsx`)
177
+
178
+ - Preserve user-added props, slots, and event handlers
179
+ - Apply upstream structural changes (new elements, changed layouts)
180
+ - Keep user's custom CSS classes and styles
181
+ - If the component was significantly restructured upstream, present both versions and ask
182
+
183
+ ### API Routes (`routes/*.ts`)
184
+
185
+ - Preserve user-added endpoints and middleware
186
+ - Apply upstream changes to existing endpoints (bug fixes, new validations)
187
+ - Keep user-added business logic intact
188
+ - Watch for new imports or dependency changes
189
+
190
+ ### Database (schema, migrations)
191
+
192
+ - NEVER auto-merge schema files — always present changes for review
193
+ - New migration files can be added directly
194
+ - Schema changes may require new migrations the user needs to create
195
+
196
+ ### Package Dependencies (`package.json`)
197
+
198
+ - Add new dependencies from upstream
199
+ - Update version ranges for existing dependencies only if the upstream change is intentional (security fix, breaking change requirement)
200
+ - Preserve user-added dependencies
201
+ - Never modify `pnpm-lock.yaml` — user runs `pnpm install` after
202
+
203
+ ### Translation Files (`en.json`)
204
+
205
+ - Add new translation keys from upstream
206
+ - Preserve user-modified translation values
207
+ - Remove keys that were deleted upstream (they're unused)
208
+ - Maintain alphabetical ordering within sections
209
+
210
+ ### Environment Files (`.env.example`)
211
+
212
+ - Add new environment variables with their example values
213
+ - Preserve user-added variables
214
+ - Update comments for changed variables
215
+ - Never touch actual `.env` files
216
+
217
+ ## When to Skip a Change
218
+
219
+ Move the item to the **Skipped** section of the plan with a clear reason when:
220
+
221
+ - The upstream change modifies code the user has significantly rewritten — applying it would undo their work
222
+ - The upstream fix addresses a bug the user already fixed differently
223
+ - A new feature conflicts with the user's custom implementation of the same thing
224
+ - Applying the change would create invalid code, type errors, or runtime failures
225
+ - The user explicitly asked to exclude it
226
+
227
+ When skipping, always explain:
228
+ - What the upstream change was trying to do
229
+ - Why it was skipped
230
+ - Whether the user should manually review it later
231
+
232
+ ## When to Ask the User
233
+
234
+ - Schema changes that might need migrations
235
+ - Removed files or features the user might depend on
236
+ - Config changes where the user's value conflicts with a new required format
237
+ - Component restructures where preserving user changes is ambiguous
238
+ - Any change where applying both upstream and user modifications would create invalid code
239
+ - When you're unsure whether a user's modification was intentional
240
+
241
+ ## Examples
242
+
243
+ ### Config merge
244
+
245
+ Upstream diff adds a new `analytics` field:
246
+ ```diff
247
+ export default {
248
+ siteName: "My SaaS",
249
+ + analytics: {
250
+ + enabled: true,
251
+ + provider: "plausible",
252
+ + },
253
+ billing: {
254
+ scope: "organization",
255
+ },
256
+ };
257
+ ```
258
+
259
+ User has customized `siteName` and `billing.scope`. Result:
260
+ ```typescript
261
+ export default {
262
+ siteName: "Acme Corp", // user's value preserved
263
+ analytics: { // new field added with defaults
264
+ enabled: true,
265
+ provider: "plausible",
266
+ },
267
+ billing: {
268
+ scope: "user", // user's value preserved
269
+ },
270
+ };
271
+ ```
272
+
273
+ ### Translation merge
274
+
275
+ Upstream diff:
276
+ ```diff
277
+ {
278
+ "dashboard": {
279
+ "title": "Dashboard",
280
+ - "old_metric": "Old Metric",
281
+ + "new_metric": "New Metric",
282
+ + "analytics": "Analytics"
283
+ }
284
+ }
285
+ ```
286
+
287
+ User changed `dashboard.title` to `"Control Panel"`. Result:
288
+ ```json
289
+ {
290
+ "dashboard": {
291
+ "title": "Control Panel",
292
+ "new_metric": "New Metric",
293
+ "analytics": "Analytics"
294
+ }
295
+ }
296
+ ```
297
+ `old_metric` removed (upstream deleted it), `title` preserved (user customized it), new keys added.
298
+
299
+ ### Skipped change example
300
+
301
+ Plan entry:
302
+ ```markdown
303
+ ## Skipped
304
+
305
+ - `app/components/PricingTable.vue` — Upstream restructured the pricing grid layout from CSS Grid to Flexbox. User has heavily customized this component with custom tier cards and comparison features. Applying the layout change would break the custom tier rendering. **User should review the diff manually if they want the new layout.**
306
+ ```
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared helpers for update scripts.
3
+ * CommonJS module — used by prepare-update.js, apply-auto.js, classify-files.js, complete-update.js.
4
+ */
5
+
6
+ const fs = require("node:fs");
7
+ const path = require("node:path");
8
+
9
+ const INTERNAL_DIR = ".generatesaas";
10
+ const MANIFEST_FILE = path.join(INTERNAL_DIR, "manifest.json");
11
+ const HASHES_FILE = path.join(INTERNAL_DIR, "hashes.json");
12
+ const TEMPLATE_HASHES_FILE = path.join(INTERNAL_DIR, "template-hashes.json");
13
+ const STAGING_DIR = path.join(INTERNAL_DIR, "staging");
14
+ const STAGING_META_FILE = path.join(INTERNAL_DIR, "staging.json");
15
+
16
+ // ── Shared Utilities ──
17
+
18
+ /** Walk up from cwd to find .generatesaas/manifest.json. */
19
+ function findProjectRoot() {
20
+ let dir = process.cwd();
21
+ while (dir !== path.dirname(dir)) {
22
+ if (fs.existsSync(path.join(dir, MANIFEST_FILE))) return dir;
23
+ dir = path.dirname(dir);
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /** Ensure a directory exists. */
29
+ function ensureDir(dir) {
30
+ fs.mkdirSync(dir, { recursive: true });
31
+ }
32
+
33
+ /** Compute SHA-256 hash of a file. */
34
+ function hashFile(filePath) {
35
+ const content = fs.readFileSync(filePath);
36
+ return require("node:crypto").createHash("sha256").update(content).digest("hex");
37
+ }
38
+
39
+ // ── File Walking ──
40
+
41
+ const WALK_EXCLUSIONS = new Set([".git", "node_modules", ".pnpm-store", ".env", INTERNAL_DIR]);
42
+
43
+ /** Check if a relative path should be excluded from walking. */
44
+ function shouldExcludeWalk(relativePath) {
45
+ const parts = relativePath.split("/");
46
+ for (const part of parts) {
47
+ if (WALK_EXCLUSIONS.has(part)) return true;
48
+ if (part.startsWith(".env") && !part.includes("example")) return true;
49
+ }
50
+ return false;
51
+ }
52
+
53
+ /** Recursively collect all file paths in a directory. Returns relative paths. */
54
+ function walkDir(dir, baseDir) {
55
+ const files = [];
56
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
57
+
58
+ for (const entry of entries) {
59
+ const fullPath = path.join(dir, entry.name);
60
+ const rel = path.relative(baseDir, fullPath);
61
+
62
+ if (shouldExcludeWalk(rel)) continue;
63
+
64
+ if (entry.isDirectory()) {
65
+ files.push(...walkDir(fullPath, baseDir));
66
+ } else if (entry.isFile()) {
67
+ files.push(rel);
68
+ }
69
+ }
70
+
71
+ return files;
72
+ }
73
+
74
+ module.exports = {
75
+ findProjectRoot,
76
+ ensureDir,
77
+ hashFile,
78
+ walkDir,
79
+ INTERNAL_DIR,
80
+ MANIFEST_FILE,
81
+ HASHES_FILE,
82
+ TEMPLATE_HASHES_FILE,
83
+ STAGING_DIR,
84
+ STAGING_META_FILE,
85
+ };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Auto-apply safe updates.
5
+ * Copies unmodified and new files from the staging directory to the project.
6
+ * Uses fs.copyFileSync for binary-safe file operations.
7
+ */
8
+
9
+ const fs = require("node:fs");
10
+ const path = require("node:path");
11
+ const { findProjectRoot, ensureDir, STAGING_DIR } = require("./_helpers.js");
12
+
13
+ function main() {
14
+ const root = findProjectRoot();
15
+ if (!root) {
16
+ console.error("Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project.");
17
+ process.exit(1);
18
+ }
19
+
20
+ // Determine the skill root
21
+ const scriptDir = __dirname;
22
+ const skillDir = path.dirname(scriptDir);
23
+ const refsDir = path.join(skillDir, "references");
24
+
25
+ const classificationPath = path.join(refsDir, "classification.json");
26
+ if (!fs.existsSync(classificationPath)) {
27
+ console.error("Error: references/classification.json not found. Run classify-files.js first.");
28
+ process.exit(1);
29
+ }
30
+
31
+ const classification = JSON.parse(fs.readFileSync(classificationPath, "utf-8"));
32
+ const stagingDir = path.join(root, STAGING_DIR);
33
+
34
+ if (!fs.existsSync(stagingDir)) {
35
+ console.error("Error: Staging directory not found. Run 'generatesaas update' first.");
36
+ process.exit(1);
37
+ }
38
+
39
+ const filesToCopy = [...classification.unmodified, ...classification.new];
40
+
41
+ if (filesToCopy.length === 0) {
42
+ console.log("No files to auto-update. All changed files need manual merge.");
43
+ return;
44
+ }
45
+
46
+ console.log(`Copying ${filesToCopy.length} files from staging...`);
47
+
48
+ const unmodifiedSet = new Set(classification.unmodified);
49
+ let updated = 0;
50
+ let created = 0;
51
+ const failed = [];
52
+
53
+ for (const filePath of filesToCopy) {
54
+ const stagingPath = path.join(stagingDir, filePath);
55
+ const fullPath = path.join(root, filePath);
56
+
57
+ if (!fs.existsSync(stagingPath)) {
58
+ failed.push(filePath);
59
+ continue;
60
+ }
61
+
62
+ ensureDir(path.dirname(fullPath));
63
+ fs.copyFileSync(stagingPath, fullPath);
64
+
65
+ if (unmodifiedSet.has(filePath)) {
66
+ updated++;
67
+ } else {
68
+ created++;
69
+ }
70
+ }
71
+
72
+ console.log(`\nUpdated ${updated} files, created ${created} new files.`);
73
+
74
+ if (failed.length > 0) {
75
+ console.log(`\nWarning: ${failed.length} files not found in staging:`);
76
+ for (const f of failed) {
77
+ console.log(` - ${f}`);
78
+ }
79
+ }
80
+
81
+ if (classification.modified.length > 0) {
82
+ console.log(`\n${classification.modified.length} files still need manual merge.`);
83
+ console.log("Review diffs in references/diffs/ and apply changes preserving your customizations.");
84
+ }
85
+
86
+ if (classification.removed.length > 0) {
87
+ console.log(`\n${classification.removed.length} files were removed upstream. Review before deleting:`);
88
+ for (const f of classification.removed) {
89
+ console.log(` - ${f}`);
90
+ }
91
+ }
92
+
93
+ console.log("\nAfter merging all files, run complete-update.js to finalize.");
94
+ }
95
+
96
+ try {
97
+ main();
98
+ } catch (err) {
99
+ console.error("Error:", err.message);
100
+ process.exit(1);
101
+ }
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Classify project files for update.
5
+ * Compares local file hashes against the manifest to determine which files
6
+ * the user has customized and which are safe to auto-update.
7
+ */
8
+
9
+ const fs = require("node:fs");
10
+ const path = require("node:path");
11
+ const { findProjectRoot, hashFile, HASHES_FILE } = require("./_helpers.js");
12
+
13
+ function main() {
14
+ const root = findProjectRoot();
15
+ if (!root) {
16
+ console.error("Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project.");
17
+ process.exit(1);
18
+ }
19
+
20
+ // Determine the skill root
21
+ const scriptDir = __dirname;
22
+ const skillDir = path.dirname(scriptDir);
23
+ const refsDir = path.join(skillDir, "references");
24
+
25
+ const manifestPath = path.join(refsDir, "update-manifest.json");
26
+ if (!fs.existsSync(manifestPath)) {
27
+ console.error("Error: references/update-manifest.json not found. Run prepare-update.js first.");
28
+ process.exit(1);
29
+ }
30
+
31
+ const updateManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
32
+ const hashesPath = path.join(root, HASHES_FILE);
33
+ const fileHashes = fs.existsSync(hashesPath)
34
+ ? JSON.parse(fs.readFileSync(hashesPath, "utf-8"))
35
+ : {};
36
+
37
+ const classification = {
38
+ targetVersion: updateManifest.targetVersion,
39
+ unmodified: [],
40
+ modified: [],
41
+ deleted: [],
42
+ new: [],
43
+ removed: [],
44
+ };
45
+
46
+ // Classify modified files (upstream changed, check if user also changed)
47
+ for (const filePath of updateManifest.modified) {
48
+ const fullPath = path.join(root, filePath);
49
+ const originalHash = fileHashes[filePath];
50
+
51
+ if (!fs.existsSync(fullPath)) {
52
+ // User deleted this file
53
+ classification.deleted.push(filePath);
54
+ } else if (!originalHash) {
55
+ // No original hash — treat as modified to be safe
56
+ classification.modified.push(filePath);
57
+ } else {
58
+ const currentHash = hashFile(fullPath);
59
+ if (currentHash === originalHash) {
60
+ // File unchanged by user — safe to auto-update
61
+ classification.unmodified.push(filePath);
62
+ } else {
63
+ // User has customized this file — needs manual merge
64
+ classification.modified.push(filePath);
65
+ }
66
+ }
67
+ }
68
+
69
+ // Classify added files (new in upstream)
70
+ for (const filePath of updateManifest.added) {
71
+ const fullPath = path.join(root, filePath);
72
+ if (fs.existsSync(fullPath)) {
73
+ // File already exists locally — treat as modified for safety
74
+ classification.modified.push(filePath);
75
+ } else {
76
+ classification.new.push(filePath);
77
+ }
78
+ }
79
+
80
+ // Classify removed files (deleted in upstream)
81
+ for (const filePath of updateManifest.removed) {
82
+ const fullPath = path.join(root, filePath);
83
+ if (fs.existsSync(fullPath)) {
84
+ classification.removed.push(filePath);
85
+ }
86
+ // If already gone locally, nothing to do
87
+ }
88
+
89
+ // Write classification
90
+ fs.writeFileSync(
91
+ path.join(refsDir, "classification.json"),
92
+ JSON.stringify(classification, null, "\t") + "\n",
93
+ "utf-8"
94
+ );
95
+
96
+ // Print summary
97
+ console.log("File Classification Summary");
98
+ console.log("===========================");
99
+ console.log(`Target version: ${classification.targetVersion}`);
100
+ console.log(` Unmodified (auto-update): ${classification.unmodified.length}`);
101
+ console.log(` Modified (manual merge): ${classification.modified.length}`);
102
+ console.log(` Deleted by user (skip): ${classification.deleted.length}`);
103
+ console.log(` New files (auto-create): ${classification.new.length}`);
104
+ console.log(` Removed upstream: ${classification.removed.length}`);
105
+ console.log("");
106
+
107
+ if (classification.modified.length > 0) {
108
+ console.log("Files needing manual merge:");
109
+ for (const f of classification.modified) {
110
+ console.log(` - ${f}`);
111
+ }
112
+ console.log("");
113
+ }
114
+
115
+ if (classification.removed.length > 0) {
116
+ console.log("Files removed upstream (review before deleting):");
117
+ for (const f of classification.removed) {
118
+ console.log(` - ${f}`);
119
+ }
120
+ console.log("");
121
+ }
122
+
123
+ console.log("Next: Run apply-auto.js to update safe files, then manually merge modified files.");
124
+ }
125
+
126
+ try {
127
+ main();
128
+ } catch (err) {
129
+ console.error("Error:", err.message);
130
+ process.exit(1);
131
+ }