generatesaas 2.3.0 → 3.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.
|
@@ -103,6 +103,11 @@ key once and saves it), then re-run the update. For a network error, ask them to
|
|
|
103
103
|
> Note: this command also rewrites the skill scripts to their latest version. That is
|
|
104
104
|
> expected - the scripts you run in the following steps are the freshly-installed ones.
|
|
105
105
|
|
|
106
|
+
**Re-read this SKILL.md now**, from `__SKILL_ROOT__/generatesaas-update/SKILL.md`. The command
|
|
107
|
+
just overwrote it along with the scripts, so the copy in your context is the PREVIOUS release's
|
|
108
|
+
instructions while every script you are about to run is the new one. Follow the file on disk
|
|
109
|
+
wherever the two disagree.
|
|
110
|
+
|
|
106
111
|
**1b. Prepare the diff/classification data:**
|
|
107
112
|
|
|
108
113
|
```bash
|
|
@@ -117,8 +122,8 @@ Creates:
|
|
|
117
122
|
`added` files; `unmodified`, `deleted`, and `removed` files have none.
|
|
118
123
|
- `references/update-manifest.json` - lists of added, modified, and removed files,
|
|
119
124
|
plus `renamed` (upstream renames detected by identical content: `[{from, to}]`)
|
|
120
|
-
and `sensitive` (database schema
|
|
121
|
-
follow-up work: migrations, new env vars)
|
|
125
|
+
and `sensitive` (database schema, `packages/database/drizzle/`, and
|
|
126
|
+
`.env.example` changes that carry follow-up work: migrations, new env vars)
|
|
122
127
|
|
|
123
128
|
### Step 2: Present Changelog
|
|
124
129
|
|
|
@@ -129,7 +134,7 @@ Each release's notes may be one of two shapes:
|
|
|
129
134
|
- **Curated** - contains explicit `## Breaking`, `## Migration`, `## Features`, `## Fixes` (or similar) sections. Use those sections verbatim; they are authoritative.
|
|
130
135
|
- **Raw** - an auto-generated flat list of change titles (one line per commit or PR, no sections). This is the common case. You can group the titles into features/fixes by reading them, but you **cannot** reliably infer breaking changes or migration steps from titles alone.
|
|
131
136
|
|
|
132
|
-
Also read `sensitive` in `references/update-manifest.json`: database schema changes mean a migration is likely required, and `.env.example` changes mean new or changed environment variables. Mention
|
|
137
|
+
Also read `sensitive` in `references/update-manifest.json`: database schema changes mean a migration is likely required, anything under `packages/database/drizzle/` means upstream migrations that must be regenerated into the project's own history before they will run (see [Database](#database-schema-migrations)), and `.env.example` changes mean new or changed environment variables. Mention all three in the summary and again in the final post-update steps.
|
|
133
138
|
|
|
134
139
|
Present a clear, organized summary:
|
|
135
140
|
|
|
@@ -154,7 +159,7 @@ it stays complete). Read `newOptions` from `.generatesaas/staging.json`. **If it
|
|
|
154
159
|
empty, skip this step entirely.**
|
|
155
160
|
|
|
156
161
|
Otherwise, present each option so the user can decide whether to adopt it. Each entry has
|
|
157
|
-
`{ key, label, hint, default, impact, requiresLabel? }`. For each one:
|
|
162
|
+
`{ key, label, hint, default, impact, choices?, requiresLabel? }`. For each one:
|
|
158
163
|
|
|
159
164
|
```
|
|
160
165
|
### New since your version: {label}
|
|
@@ -172,14 +177,32 @@ it is a large surface you didn't originally choose" or "Safe to adopt - it is pu
|
|
|
172
177
|
> 2. **Adopt it** - turn this feature on
|
|
173
178
|
```
|
|
174
179
|
|
|
180
|
+
**An entry with `choices` is not a yes/no.** It takes one of several values, so the two-way block
|
|
181
|
+
above cannot express it - offer the values as a numbered pick list instead, one line per choice,
|
|
182
|
+
with the entry's `default` marked as the current behavior:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
**Default:** `{default}` (your project's current behavior is unchanged).
|
|
186
|
+
**My recommendation:** {your recommendation, naming one of the choices}
|
|
187
|
+
|
|
188
|
+
> **Which would you like?**
|
|
189
|
+
> 1. **`{choices[0]}`** {"(current)" on the one equal to default}
|
|
190
|
+
> 2. **`{choices[1]}`**
|
|
191
|
+
> 3. **`{choices[2]}`** ...one line per choice, however many there are
|
|
192
|
+
```
|
|
193
|
+
|
|
175
194
|
**Ask the user about every new option, in every mode - including Automatic.** Surfacing a new
|
|
176
195
|
feature is exactly the kind of decision the update must never assume. Give your recommendation,
|
|
177
196
|
but the user decides.
|
|
178
197
|
|
|
179
198
|
**Applying the decisions:**
|
|
180
199
|
|
|
181
|
-
1. For each option the user **adopts**, write
|
|
182
|
-
|
|
200
|
+
1. For each option the user **adopts**, write the value they picked into
|
|
201
|
+
`.generatesaas/manifest.json` under the entry's `key`. For an option with no `choices` that value
|
|
202
|
+
is `true` (e.g. set `"desktop": true`); for one with `choices` it is the choice they named,
|
|
203
|
+
verbatim (e.g. set `"errorTracking": "sentry"`). Never write `true` for an option that has
|
|
204
|
+
`choices` - the CLI refuses any value outside them and stages nothing at all. The manifest is the
|
|
205
|
+
single shaping input.
|
|
183
206
|
2. **If the user adopted at least one option, re-stage once** so re-shaping produces the
|
|
184
207
|
feature's files. Re-run the CLI (append `auto` if you are in Automatic mode, so the staged
|
|
185
208
|
metadata keeps the unattended signal):
|
|
@@ -318,13 +341,15 @@ Preview first (especially useful in Careful mode or when unsure), then apply:
|
|
|
318
341
|
node __SKILL_ROOT__/generatesaas-update/scripts/apply-auto.js --dry-run # lists every file it would update/create, changes nothing
|
|
319
342
|
node __SKILL_ROOT__/generatesaas-update/scripts/apply-auto.js
|
|
320
343
|
```
|
|
321
|
-
`apply-auto.js` copies only `unmodified` (safe replace) + `new` (
|
|
344
|
+
`apply-auto.js` copies only `unmodified` (safe replace) + `new` (any upstream-added file whose tree the buyer has not deleted). It will **never** touch `newInDeletedTree` files, and it aborts without writing anything if any source is missing from staging (no half-applied tree). Report what was applied. Mark all auto-update and new file items as `[x]` in the plan - **except any file the script reports as held back**, which was never applied and moves to **Skipped** with the regenerate reason. Marking a held-back migration `[x]` records a table that does not exist as delivered, in the one artifact that outlives the session.
|
|
345
|
+
|
|
346
|
+
It also holds back everything under `packages/database/drizzle/` and lists what it skipped. Those are NOT pending work you can copy in afterwards - migrations must be regenerated into the project's own history or they never run. See [Database](#database-schema-migrations).
|
|
322
347
|
|
|
323
348
|
**Careful mode:**
|
|
324
349
|
Present the list of safe auto-updates and new files to the user:
|
|
325
350
|
> These {N} files haven't been customized and can be safely replaced with the new version. {N} new files will be created. Want me to apply all of these, or would you like to review any specific ones first?
|
|
326
351
|
|
|
327
|
-
If the user wants to review specific files, show them the diff for each requested file. Apply only after approval.
|
|
352
|
+
If the user wants to review specific files, show them the diff for each requested file. Apply only after approval. Do not count held-back migrations in either number - they are not being created, and presenting them as applied is the same error as marking them `[x]`.
|
|
328
353
|
|
|
329
354
|
### Step 7: Review Modified Files
|
|
330
355
|
|
|
@@ -476,7 +501,7 @@ After all files are processed:
|
|
|
476
501
|
**Do NOT include:**
|
|
477
502
|
- Files where the user accepted a merge or chose "Use upstream version" (template changes were applied)
|
|
478
503
|
- Removed files (not in staging - template-hashes handles these automatically)
|
|
479
|
-
- New files, whether created or rejected
|
|
504
|
+
- New files, whether created or rejected. NB: that is only right for `newInDeletedTree`, where declining is a standing choice. A new file rejected for any other reason also stops being offered once the baseline advances, so say so when you record it as skipped
|
|
480
505
|
- Any file where the template's intended change was successfully applied
|
|
481
506
|
|
|
482
507
|
```json
|
|
@@ -554,9 +579,26 @@ Config files are the most commonly customized.
|
|
|
554
579
|
### Database (schema, migrations)
|
|
555
580
|
|
|
556
581
|
- NEVER auto-merge schema files - always present for user review
|
|
557
|
-
- New migration files can be added directly
|
|
558
582
|
- Warn about schema changes that may require new migrations
|
|
559
583
|
|
|
584
|
+
**Never copy an upstream migration into `packages/database/drizzle/` as-is, and never overwrite the project's `drizzle/meta/_journal.json` or `drizzle/meta/*_snapshot.json` with the upstream versions.** A project's migration history is its own: upstream changes get re-derived into it, never pasted over it. Regenerate instead:
|
|
585
|
+
|
|
586
|
+
1. Merge the upstream schema change into `packages/database/src/db/*.ts` (user review first, as above).
|
|
587
|
+
2. Run `pnpm --filter @repo/database generate`. Drizzle diffs the project's own latest snapshot against the merged schema and writes a fresh migration carrying a current timestamp, the next `idx`, a correct `prevId`, and a snapshot that describes the project's real schema.
|
|
588
|
+
3. Open the upstream `.sql` under `.generatesaas/staging/packages/database/drizzle/` and copy any hand-written statements into the generated file: seeds, backfills, data migrations, `ON CONFLICT` guards. `generate` infers structure only, so those are lost otherwise.
|
|
589
|
+
4. Run `pnpm --filter @repo/database migrate`.
|
|
590
|
+
|
|
591
|
+
**Why the upstream file cannot just be added.** Drizzle applies a migration only when its journal `when` value is greater than the `created_at` of the last migration already applied to the database. A project that generated its own migration after the upstream one was authored sits above that mark, so a copied-in upstream file is **skipped silently and permanently**: no error, no warning, no log line, and `migrate` still exits 0. The table or column it was supposed to create never exists, and the miss surfaces later as runtime failures on whatever code path needs it. Taking the upstream `_journal.json` and snapshots instead of the project's own is the second half of the same trap: the final snapshot then knows nothing about the project's own tables, so the next `generate` re-emits `CREATE TABLE` for tables that already exist.
|
|
592
|
+
|
|
593
|
+
**If step 2 reports no schema changes**, the upstream migration is data-only and there is nothing to regenerate. This is the ONLY case where the `.sql` is copied in by hand. Copying the file is not enough on its own: drizzle reads `_journal.json` and runs only the entries listed there, so a `.sql` sitting in the folder with no entry is never opened. Do all four, in this order:
|
|
594
|
+
|
|
595
|
+
1. **Append an entry** to the project's own `_journal.json`, copied from the upstream entry. This is the load-bearing step - without it nothing else matters.
|
|
596
|
+
2. Set that entry's `when` **greater than the largest `when` already applied** to the database, never the upstream value. Drizzle takes the highest `created_at` in `drizzle.__drizzle_migrations` as its floor and skips everything at or below it.
|
|
597
|
+
3. Set that entry's `idx` to continue the project's own sequence (last `idx` + 1).
|
|
598
|
+
4. Rename the `.sql` file so its name matches the entry's `tag`, or set the `tag` to the file's existing name. Drizzle loads the file **by tag** and throws if the two disagree.
|
|
599
|
+
|
|
600
|
+
Still do not bring the upstream `meta/*_snapshot.json`, here least of all. Nothing needs it: `migrate` never reads snapshots, and `generate` needs only ONE previous snapshot, which must be the project's own. Adding an upstream one breaks `generate` whichever way it sorts - `meta/` is read in filename order, so an upstream snapshot that sorts LAST silently becomes the diff base (re-emitting `CREATE TABLE` for the project's own tables, and `DROP TABLE` for anything upstream carries that the project removed), while one that sorts earlier collides on `prevId` with the project's own, which prints a warning, writes no migration, and still exits 0.
|
|
601
|
+
|
|
560
602
|
### Package Dependencies (`package.json`)
|
|
561
603
|
|
|
562
604
|
- Add new dependencies from upstream
|
|
@@ -46,7 +46,7 @@ function hashFile(filePath) {
|
|
|
46
46
|
// asserts this set equality and the root-only matching below.
|
|
47
47
|
const WALK_EXCLUSIONS = new Set([
|
|
48
48
|
".git", "node_modules", ".pnpm-store", ".env", ".env.test",
|
|
49
|
-
".turbo", ".
|
|
49
|
+
".turbo", ".data", "dist",
|
|
50
50
|
".next", ".svelte-kit", ".wrangler",
|
|
51
51
|
".devcontainer", "playwright-report", "test-results",
|
|
52
52
|
"pnpm-lock.yaml",
|
|
@@ -10,6 +10,9 @@ const fs = require("node:fs");
|
|
|
10
10
|
const path = require("node:path");
|
|
11
11
|
const { findProjectRoot, ensureDir, STAGING_DIR } = require("./_helpers.js");
|
|
12
12
|
|
|
13
|
+
/** Drizzle migration history. Held back from every auto-copy - see filesToCopy. */
|
|
14
|
+
const MIGRATION_DIR = "packages/database/drizzle/";
|
|
15
|
+
|
|
13
16
|
function main() {
|
|
14
17
|
const root = findProjectRoot();
|
|
15
18
|
if (!root) {
|
|
@@ -36,12 +39,24 @@ function main() {
|
|
|
36
39
|
process.exit(1);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
// `new`
|
|
40
|
-
// trees land in `newInDeletedTree`, which the AI
|
|
41
|
-
|
|
42
|
+
// `new` is any upstream-added file whose tree the buyer has NOT deleted -
|
|
43
|
+
// files inside buyer-deleted trees land in `newInDeletedTree`, which the AI
|
|
44
|
+
// handles by prompt (never here).
|
|
45
|
+
const candidates = [...classification.unmodified, ...classification.new];
|
|
46
|
+
|
|
47
|
+
// Migrations are NEVER auto-applied, and both failure modes are silent.
|
|
48
|
+
// Drizzle runs a migration only when its journal `when` beats the newest one
|
|
49
|
+
// already applied, so an upstream .sql copied in below that mark never runs
|
|
50
|
+
// and `migrate` still exits 0; and an upstream meta snapshot becomes the diff
|
|
51
|
+
// base for the next `generate`, which then re-emits the buyer's own tables.
|
|
52
|
+
// The skill's Database section regenerates them into the project's own
|
|
53
|
+
// history instead, which is only possible if this step leaves them alone.
|
|
54
|
+
const heldMigrations = candidates.filter((f) => f.startsWith(MIGRATION_DIR));
|
|
55
|
+
const filesToCopy = candidates.filter((f) => !f.startsWith(MIGRATION_DIR));
|
|
42
56
|
|
|
43
57
|
if (filesToCopy.length === 0) {
|
|
44
58
|
console.log("No files to auto-update. All changed files need manual merge.");
|
|
59
|
+
reportHeldMigrations(heldMigrations);
|
|
45
60
|
return;
|
|
46
61
|
}
|
|
47
62
|
|
|
@@ -69,6 +84,7 @@ function main() {
|
|
|
69
84
|
console.log(` ${unmodifiedSet.has(filePath) ? "update" : "create"} ${filePath}`);
|
|
70
85
|
}
|
|
71
86
|
console.log("\n[dry run] No files were modified.");
|
|
87
|
+
reportHeldMigrations(heldMigrations);
|
|
72
88
|
return;
|
|
73
89
|
}
|
|
74
90
|
|
|
@@ -102,9 +118,37 @@ function main() {
|
|
|
102
118
|
}
|
|
103
119
|
}
|
|
104
120
|
|
|
121
|
+
reportHeldMigrations(heldMigrations);
|
|
122
|
+
|
|
105
123
|
console.log("\nAfter merging all files, run complete-update.js to finalize.");
|
|
106
124
|
}
|
|
107
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Report migration files deliberately NOT copied, so the AI knows they are
|
|
128
|
+
* waiting rather than assuming the tree is settled. Silent when there are none.
|
|
129
|
+
*
|
|
130
|
+
* States the whole procedure rather than pointing at SKILL.md. The AI loads
|
|
131
|
+
* SKILL.md at the start of the update, BEFORE step 1a refreshes the bundle, so
|
|
132
|
+
* for one release after this ships it is following the previous release's prose
|
|
133
|
+
* - which says migrations can be added directly. This output has to stand alone
|
|
134
|
+
* against that reader, or the fix reads as an obstacle to work around.
|
|
135
|
+
*/
|
|
136
|
+
function reportHeldMigrations(heldMigrations) {
|
|
137
|
+
if (heldMigrations.length === 0) return;
|
|
138
|
+
console.log(`\n${heldMigrations.length} migration file(s) held back - NOT copied into the project:`);
|
|
139
|
+
for (const f of heldMigrations) console.log(` - ${f}`);
|
|
140
|
+
console.log("\nDo NOT copy these in by hand, and do not take the upstream meta/ snapshots either.");
|
|
141
|
+
console.log("Drizzle runs a migration only when its journal entry is newer than the newest one");
|
|
142
|
+
console.log("already applied, so a pasted-in file never runs, `migrate` still exits 0, and the");
|
|
143
|
+
console.log("table it should have created silently does not exist. Regenerate instead:");
|
|
144
|
+
console.log(" 1. Merge the upstream schema change into packages/database/src/db/");
|
|
145
|
+
console.log(" 2. Run: pnpm --filter @repo/database generate");
|
|
146
|
+
console.log(" 3. Copy any hand-written statements (seeds, backfills, data migrations) out of the");
|
|
147
|
+
console.log(" staged .sql at .generatesaas/staging/<path above> into the file step 2 wrote -");
|
|
148
|
+
console.log(" `generate` infers structure only, so those statements are lost otherwise");
|
|
149
|
+
console.log(" 4. Run: pnpm --filter @repo/database migrate");
|
|
150
|
+
}
|
|
151
|
+
|
|
108
152
|
try {
|
|
109
153
|
main();
|
|
110
154
|
} catch (err) {
|
|
@@ -12,10 +12,13 @@ const { execFileSync } = require("node:child_process");
|
|
|
12
12
|
const { findProjectRoot, ensureDir, hashFile, walkDir, shouldExcludeWalk, TEMPLATE_HASHES_FILE, TEMPLATE_DIR, STAGING_DIR, STAGING_META_FILE } = require("./_helpers.js");
|
|
13
13
|
|
|
14
14
|
// Paths whose upstream changes always carry follow-up work for the user:
|
|
15
|
-
// database schema changes may need a migration,
|
|
16
|
-
//
|
|
15
|
+
// database schema changes may need a migration, incoming migrations must be
|
|
16
|
+
// regenerated into the project's own history before drizzle will run them, and
|
|
17
|
+
// .env.example changes may need new environment variables. Surfaced as
|
|
18
|
+
// `sensitive` in update-manifest.json.
|
|
17
19
|
const SENSITIVE_PATTERNS = [
|
|
18
20
|
/^packages\/database\/src\/db\//,
|
|
21
|
+
/^packages\/database\/drizzle\//,
|
|
19
22
|
/(^|\/)\.env\.example$/,
|
|
20
23
|
];
|
|
21
24
|
|
|
@@ -105,7 +108,7 @@ function main() {
|
|
|
105
108
|
console.log(` Renamed: ${renamed.length} (see update-manifest.json - port customizations before deleting the old paths)`);
|
|
106
109
|
}
|
|
107
110
|
if (sensitive.length > 0) {
|
|
108
|
-
console.log(` Sensitive (schema / env example): ${sensitive.length}`);
|
|
111
|
+
console.log(` Sensitive (schema / migrations / env example): ${sensitive.length}`);
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
// Determine output directory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "generatesaas",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for scaffolding and managing GenerateSaaS projects",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@repo/tsconfig": "1.0.0"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=22"
|
|
29
|
+
"node": ">=22.18"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup",
|