generatesaas 3.7.0 → 3.8.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.
@@ -9,7 +9,13 @@
9
9
 
10
10
  const fs = require("node:fs");
11
11
  const path = require("node:path");
12
- const { findProjectRoot, hashFile, TEMPLATE_HASHES_FILE, HASHES_FILE, STAGING_DIR } = require("./_helpers.js");
12
+ const {
13
+ findProjectRoot,
14
+ hashFile,
15
+ TEMPLATE_HASHES_FILE,
16
+ HASHES_FILE,
17
+ STAGING_DIR
18
+ } = require("./_helpers.js");
13
19
 
14
20
  // ── Import Scanning ──
15
21
 
@@ -28,16 +34,17 @@ function extractImports(content, filePath) {
28
34
 
29
35
  const patterns = [
30
36
  /(?:import|export)\s[\s\S]*?from\s+['"]([^'"]+)['"]/g,
31
- /(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
37
+ /(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g
32
38
  ];
33
39
 
34
40
  for (const pattern of patterns) {
35
- let m;
36
- while ((m = pattern.exec(content)) !== null) {
41
+ let m = pattern.exec(content);
42
+ while (m !== null) {
37
43
  const p = m[1];
38
44
  if (p.startsWith(".") || p.startsWith("~/") || p.startsWith("@/") || p.startsWith("#")) {
39
45
  imports.add(p);
40
46
  }
47
+ m = pattern.exec(content);
41
48
  }
42
49
  }
43
50
 
@@ -53,7 +60,11 @@ function matchImportToFile(importPath, fromFilePath, knownFileSet) {
53
60
 
54
61
  if (importPath.startsWith("./") || importPath.startsWith("../")) {
55
62
  basePath = path.normalize(path.join(path.dirname(fromFilePath), importPath));
56
- } else if (importPath.startsWith("~/") || importPath.startsWith("@/") || importPath.startsWith("#")) {
63
+ } else if (
64
+ importPath.startsWith("~/") ||
65
+ importPath.startsWith("@/") ||
66
+ importPath.startsWith("#")
67
+ ) {
57
68
  // Alias - strip prefix. ~ and @ typically resolve from the app root.
58
69
  basePath = importPath.replace(/^[~@#]\//, "");
59
70
  } else {
@@ -64,7 +75,19 @@ function matchImportToFile(importPath, fromFilePath, knownFileSet) {
64
75
  basePath = basePath.split(path.sep).join("/");
65
76
  if (basePath.startsWith("./")) basePath = basePath.slice(2);
66
77
 
67
- const extensions = ["", ".ts", ".js", ".vue", ".tsx", ".jsx", "/index.ts", "/index.js", "/index.vue"];
78
+ const extensions = [
79
+ "",
80
+ ".ts",
81
+ ".js",
82
+ ".vue",
83
+ ".tsx",
84
+ ".jsx",
85
+ "/index.ts",
86
+ "/index.js",
87
+ "/index.tsx",
88
+ "/index.jsx",
89
+ "/index.vue"
90
+ ];
68
91
  for (const ext of extensions) {
69
92
  const candidate = basePath + ext;
70
93
  if (knownFileSet.has(candidate)) return candidate;
@@ -72,7 +95,7 @@ function matchImportToFile(importPath, fromFilePath, knownFileSet) {
72
95
 
73
96
  // For alias imports, also try matching as a suffix of known files
74
97
  if (!importPath.startsWith("./") && !importPath.startsWith("../")) {
75
- const suffix = "/" + basePath;
98
+ const suffix = `/${basePath}`;
76
99
  for (const ext of extensions) {
77
100
  const needle = suffix + ext;
78
101
  for (const known of knownFileSet) {
@@ -101,7 +124,7 @@ function deletedTreeAncestor(filePath, root, baselineFiles) {
101
124
  const dir = parts.slice(0, i).join("/");
102
125
  if (fs.existsSync(path.join(root, dir))) continue;
103
126
  // First missing ancestor. Did it exist in the old template?
104
- const existedInTemplate = baselineFiles.some((f) => f === dir || f.startsWith(dir + "/"));
127
+ const existedInTemplate = baselineFiles.some((f) => f === dir || f.startsWith(`${dir}/`));
105
128
  return existedInTemplate ? dir : null;
106
129
  }
107
130
  return null;
@@ -121,13 +144,13 @@ function buildCrossDependencies(root, classification, stagingDir) {
121
144
  ...classification.new,
122
145
  ...classification.newInDeletedTree,
123
146
  ...classification.deleted,
124
- ...classification.removed,
147
+ ...classification.removed
125
148
  ]);
126
149
 
127
150
  const crossDeps = {
128
151
  modifiedImportsUnmodified: {},
129
152
  unmodifiedImportsModified: {},
130
- newImportsModified: {},
153
+ newImportsModified: {}
131
154
  };
132
155
 
133
156
  function addDep(bucket, key, value) {
@@ -154,7 +177,11 @@ function buildCrossDependencies(root, classification, stagingDir) {
154
177
  }
155
178
 
156
179
  function readSafe(fullPath) {
157
- try { return fs.readFileSync(fullPath, "utf-8"); } catch { return null; }
180
+ try {
181
+ return fs.readFileSync(fullPath, "utf-8");
182
+ } catch {
183
+ return null;
184
+ }
158
185
  }
159
186
 
160
187
  // Scan modified files (user's current versions)
@@ -192,7 +219,9 @@ function buildCrossDependencies(root, classification, stagingDir) {
192
219
  function main() {
193
220
  const root = findProjectRoot();
194
221
  if (!root) {
195
- console.error("Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project.");
222
+ console.error(
223
+ "Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project."
224
+ );
196
225
  process.exit(1);
197
226
  }
198
227
 
@@ -237,7 +266,7 @@ function main() {
237
266
  deleted: [],
238
267
  new: [],
239
268
  newInDeletedTree: [],
240
- removed: [],
269
+ removed: []
241
270
  };
242
271
 
243
272
  const baselineFiles = Object.keys(baselineHashes);
@@ -296,7 +325,7 @@ function main() {
296
325
  // Write classification
297
326
  fs.writeFileSync(
298
327
  path.join(refsDir, "classification.json"),
299
- JSON.stringify(classification, null, "\t") + "\n",
328
+ `${JSON.stringify(classification, null, "\t")}\n`,
300
329
  "utf-8"
301
330
  );
302
331
 
@@ -13,7 +13,19 @@
13
13
 
14
14
  const fs = require("node:fs");
15
15
  const path = require("node:path");
16
- const { findProjectRoot, hashFile, walkDir, ensureDir, MANIFEST_FILE, HASHES_FILE, TEMPLATE_HASHES_FILE, TEMPLATE_DIR, STAGING_DIR, STAGING_META_FILE, INTERNAL_DIR } = require("./_helpers.js");
16
+ const {
17
+ findProjectRoot,
18
+ hashFile,
19
+ walkDir,
20
+ ensureDir,
21
+ MANIFEST_FILE,
22
+ HASHES_FILE,
23
+ TEMPLATE_HASHES_FILE,
24
+ TEMPLATE_DIR,
25
+ STAGING_DIR,
26
+ STAGING_META_FILE,
27
+ INTERNAL_DIR
28
+ } = require("./_helpers.js");
17
29
 
18
30
  /**
19
31
  * Safety net for the held-back list. An upstream change was demonstrably never
@@ -94,7 +106,9 @@ function archivePlanFile(root) {
94
106
  function main() {
95
107
  const root = findProjectRoot();
96
108
  if (!root) {
97
- console.error("Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project.");
109
+ console.error(
110
+ "Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project."
111
+ );
98
112
  process.exit(1);
99
113
  }
100
114
 
@@ -116,9 +130,7 @@ function main() {
116
130
  // Read held-back files list (created by the AI for skipped/held-back files)
117
131
  const heldBackPath = path.join(root, INTERNAL_DIR, "held-back.json");
118
132
  const heldBack = new Set(
119
- fs.existsSync(heldBackPath)
120
- ? JSON.parse(fs.readFileSync(heldBackPath, "utf-8"))
121
- : []
133
+ fs.existsSync(heldBackPath) ? JSON.parse(fs.readFileSync(heldBackPath, "utf-8")) : []
122
134
  );
123
135
 
124
136
  if (heldBack.size > 0) {
@@ -137,7 +149,9 @@ function main() {
137
149
  // never applied but are missing from held-back.json.
138
150
  const netted = applyHeldBackSafetyNet(root, stagingDir, stagingFiles, heldBack);
139
151
  if (netted.length > 0) {
140
- console.warn(`Safety net: ${netted.length} upstream change(s) were never applied and were missing from held-back.json.`);
152
+ console.warn(
153
+ `Safety net: ${netted.length} upstream change(s) were never applied and were missing from held-back.json.`
154
+ );
141
155
  console.warn("Holding them back so the next update re-surfaces them:");
142
156
  for (const rel of netted) console.warn(` - ${rel}`);
143
157
  }
@@ -150,12 +164,19 @@ function main() {
150
164
  // A wrong held-back entry silently corrupts the next update's diff baseline.
151
165
  for (const rel of heldBack) {
152
166
  if (!stagingFiles.has(rel)) {
153
- console.warn(` Warning: held-back "${rel}" is not in staging - nothing to hold back. Check .generatesaas/held-back.json.`);
167
+ console.warn(
168
+ ` Warning: held-back "${rel}" is not in staging - nothing to hold back. Check .generatesaas/held-back.json.`
169
+ );
154
170
  continue;
155
171
  }
156
172
  const oldTemplateFile = path.join(templateDir, rel);
157
- if (fs.existsSync(oldTemplateFile) && hashFile(oldTemplateFile) === hashFile(path.join(stagingDir, rel))) {
158
- console.warn(` Warning: held-back "${rel}" is identical in the old template and staging - no upstream change to preserve.`);
173
+ if (
174
+ fs.existsSync(oldTemplateFile) &&
175
+ hashFile(oldTemplateFile) === hashFile(path.join(stagingDir, rel))
176
+ ) {
177
+ console.warn(
178
+ ` Warning: held-back "${rel}" is identical in the old template and staging - no upstream change to preserve.`
179
+ );
159
180
  }
160
181
  }
161
182
 
@@ -199,8 +220,14 @@ function main() {
199
220
  templateHashes[rel] = hashFile(path.join(templateDir, rel));
200
221
  }
201
222
  fs.mkdirSync(path.dirname(templateHashesPath), { recursive: true });
202
- fs.writeFileSync(templateHashesPath, JSON.stringify(templateHashes, null, "\t") + "\n", "utf-8");
203
- console.log(`Tracked ${Object.keys(templateHashes).length} template hashes in ${TEMPLATE_HASHES_FILE}`);
223
+ fs.writeFileSync(
224
+ templateHashesPath,
225
+ `${JSON.stringify(templateHashes, null, "\t")}\n`,
226
+ "utf-8"
227
+ );
228
+ console.log(
229
+ `Tracked ${Object.keys(templateHashes).length} template hashes in ${TEMPLATE_HASHES_FILE}`
230
+ );
204
231
  }
205
232
 
206
233
  // Clean up staging
@@ -239,13 +266,13 @@ function main() {
239
266
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
240
267
  manifest.version = targetVersion;
241
268
 
242
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, "\t") + "\n", "utf-8");
269
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`, "utf-8");
243
270
  console.log(`Updated manifest to version ${targetVersion}`);
244
271
 
245
272
  // Write hashes to separate file
246
273
  const hashesPath = path.join(root, HASHES_FILE);
247
274
  fs.mkdirSync(path.dirname(hashesPath), { recursive: true });
248
- fs.writeFileSync(hashesPath, JSON.stringify(fileHashes, null, "\t") + "\n", "utf-8");
275
+ fs.writeFileSync(hashesPath, `${JSON.stringify(fileHashes, null, "\t")}\n`, "utf-8");
249
276
  console.log(`Tracked ${Object.keys(fileHashes).length} file hashes in ${HASHES_FILE}`);
250
277
 
251
278
  // Archive the finished plan file so the next update's pre-flight check doesn't
@@ -9,7 +9,17 @@
9
9
  const fs = require("node:fs");
10
10
  const path = require("node:path");
11
11
  const { execFileSync } = require("node:child_process");
12
- const { findProjectRoot, ensureDir, hashFile, walkDir, shouldExcludeWalk, TEMPLATE_HASHES_FILE, TEMPLATE_DIR, STAGING_DIR, STAGING_META_FILE } = require("./_helpers.js");
12
+ const {
13
+ findProjectRoot,
14
+ ensureDir,
15
+ hashFile,
16
+ walkDir,
17
+ shouldExcludeWalk,
18
+ TEMPLATE_HASHES_FILE,
19
+ TEMPLATE_DIR,
20
+ STAGING_DIR,
21
+ STAGING_META_FILE
22
+ } = require("./_helpers.js");
13
23
 
14
24
  // Paths whose upstream changes always carry follow-up work for the user:
15
25
  // database schema changes may need a migration, incoming migrations must be
@@ -19,13 +29,15 @@ const { findProjectRoot, ensureDir, hashFile, walkDir, shouldExcludeWalk, TEMPLA
19
29
  const SENSITIVE_PATTERNS = [
20
30
  /^packages\/database\/src\/db\//,
21
31
  /^packages\/database\/drizzle\//,
22
- /(^|\/)\.env\.example$/,
32
+ /(^|\/)\.env\.example$/
23
33
  ];
24
34
 
25
35
  function main() {
26
36
  const root = findProjectRoot();
27
37
  if (!root) {
28
- console.error("Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project.");
38
+ console.error(
39
+ "Error: .generatesaas/manifest.json not found. Run this from a GenerateSaaS project."
40
+ );
29
41
  process.exit(1);
30
42
  }
31
43
 
@@ -105,7 +117,9 @@ function main() {
105
117
 
106
118
  console.log(` Added: ${added.length}, Modified: ${modified.length}, Removed: ${removed.length}`);
107
119
  if (renamed.length > 0) {
108
- console.log(` Renamed: ${renamed.length} (see update-manifest.json - port customizations before deleting the old paths)`);
120
+ console.log(
121
+ ` Renamed: ${renamed.length} (see update-manifest.json - port customizations before deleting the old paths)`
122
+ );
109
123
  }
110
124
  if (sensitive.length > 0) {
111
125
  console.log(` Sensitive (schema / migrations / env example): ${sensitive.length}`);
@@ -151,7 +165,7 @@ function main() {
151
165
  }
152
166
 
153
167
  if (diff) {
154
- const diffPath = path.join(diffsDir, filePath + ".diff");
168
+ const diffPath = path.join(diffsDir, `${filePath}.diff`);
155
169
  ensureDir(path.dirname(diffPath));
156
170
  fs.writeFileSync(diffPath, diff, "utf-8");
157
171
  diffCount++;
@@ -167,11 +181,11 @@ function main() {
167
181
  modified,
168
182
  removed,
169
183
  renamed,
170
- sensitive,
184
+ sensitive
171
185
  };
172
186
  fs.writeFileSync(
173
187
  path.join(refsDir, "update-manifest.json"),
174
- JSON.stringify(updateManifest, null, "\t") + "\n",
188
+ `${JSON.stringify(updateManifest, null, "\t")}\n`,
175
189
  "utf-8"
176
190
  );
177
191
  console.log("Written: references/update-manifest.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generatesaas",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "license": "UNLICENSED",
@@ -19,8 +19,10 @@
19
19
  "tar": "^7.5.22"
20
20
  },
21
21
  "devDependencies": {
22
+ "@repo/eslint-config": "0.0.0",
22
23
  "@repo/tsconfig": "1.0.0",
23
24
  "@types/node": "26.2.0",
25
+ "eslint": "^9.39.5",
24
26
  "tsup": "^8.5.1",
25
27
  "typescript": "6.0.3",
26
28
  "vitest": "4.1.10"
@@ -33,6 +35,7 @@
33
35
  "postbuild": "node -e \"const fs=require('node:fs');fs.rmSync('dist/skill/content',{recursive:true,force:true});fs.mkdirSync('dist/skill',{recursive:true});fs.cpSync('src/skill/content','dist/skill/content',{recursive:true});fs.rmSync('dist/generators/content',{recursive:true,force:true});fs.mkdirSync('dist/generators',{recursive:true});fs.cpSync('src/generators/content','dist/generators/content',{recursive:true})\"",
34
36
  "dev": "tsup --watch",
35
37
  "check-types": "tsc --noEmit",
38
+ "lint": "eslint --cache .",
36
39
  "test": "vitest run",
37
40
  "test:playground": "vitest run --config vitest.playground.config.ts"
38
41
  }