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.
- package/dist/index.js +551 -0
- package/dist/skill/content/SKILL.md +306 -0
- package/dist/skill/content/scripts/_helpers.js +85 -0
- package/dist/skill/content/scripts/apply-auto.js +101 -0
- package/dist/skill/content/scripts/classify-files.js +131 -0
- package/dist/skill/content/scripts/complete-update.js +134 -0
- package/dist/skill/content/scripts/prepare-update.js +155 -0
- package/package.json +35 -0
|
@@ -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
|
+
}
|