scaffoldrite 2.0.2 → 2.0.4

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,129 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ai = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const data_1 = require("../../data");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const generateStructure_1 = require("./generateStructure");
12
+ // ─────────────────────────────────────────────
13
+ // HELPER: readline wrapper
14
+ // ─────────────────────────────────────────────
15
+ function createAsk() {
16
+ const rl = require("readline").createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout,
19
+ });
20
+ const ask = (q) => new Promise((res) => rl.question(q, res));
21
+ return { ask, close: () => rl.close() };
22
+ }
23
+ // ─────────────────────────────────────────────
24
+ // HELPER: sanitize AI output for ScaffoldRite
25
+ // ─────────────────────────────────────────────
26
+ function sanitizeStructureSR(input) {
27
+ return input
28
+ .split("\n")
29
+ .filter((line) => !line.match(/^\s*(STRUCTURE|FORMAT|SYNTAX|RULES)/i))
30
+ .join("\n")
31
+ .trim();
32
+ }
33
+ // ─────────────────────────────────────────────
34
+ // MAIN AI FUNCTION
35
+ // ─────────────────────────────────────────────
36
+ const ai = async () => {
37
+ try {
38
+ // ─────────────────────────────────────────────
39
+ // 1️⃣ INITIAL QUESTIONS
40
+ // ─────────────────────────────────────────────
41
+ let { ask, close } = createAsk();
42
+ const projectName = await ask("Project name: ");
43
+ const framework = await ask("Framework (react, vue, vanilla): ");
44
+ const language = await ask("Language (js, ts): ");
45
+ close(); // ❗ CLOSE BEFORE execSync
46
+ // ─────────────────────────────────────────────
47
+ // 2️⃣ CREATE VITE PROJECT
48
+ // ─────────────────────────────────────────────
49
+ let template = framework.toLowerCase();
50
+ if (framework === "react" && language === "ts")
51
+ template = "react-ts";
52
+ if (framework === "vue" && language === "ts")
53
+ template = "vue-ts";
54
+ if (framework === "vanilla" && language === "ts")
55
+ template = "vanilla-ts";
56
+ (0, child_process_1.execSync)(`npx create-vite@latest "${projectName}" --template ${template} --no-rolldown --no-immediate`, {
57
+ stdio: "inherit",
58
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh",
59
+ });
60
+ const projectPath = path_1.default.resolve(process.cwd(), projectName);
61
+ // ─────────────────────────────────────────────
62
+ // 3️⃣ INIT SCAFFOLDRITE
63
+ // ─────────────────────────────────────────────
64
+ (0, child_process_1.execSync)("sr init --from-fs .", {
65
+ cwd: projectPath,
66
+ stdio: "inherit",
67
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh",
68
+ });
69
+ // ─────────────────────────────────────────────
70
+ // 4️⃣ ASK FOR AI ASSISTANCE
71
+ // ─────────────────────────────────────────────
72
+ ({ ask, close } = createAsk());
73
+ const wantAI = await ask("\n🤖 Do you want AI assistance in scaffolding the structure of your app? (yes/no): ");
74
+ if (wantAI.toLowerCase() === "yes") {
75
+ while (true) {
76
+ const description = await ask("\n📝 Describe your project or what you want to add/change:\n");
77
+ const structurePath = path_1.default.join(projectPath, ".scaffoldrite", "structure.sr");
78
+ const existingStructure = fs_1.default.readFileSync(structurePath, "utf-8");
79
+ const result = await (0, generateStructure_1.generateStructure)({
80
+ existingStructure,
81
+ description,
82
+ });
83
+ // 🧠 CLARIFICATION MODE
84
+ if (result.startsWith("CLARIFICATION_REQUIRED")) {
85
+ console.log("\n🤖 I need clarification:\n");
86
+ console.log(result);
87
+ const confirm = await ask("\nIs this what you meant? (yes/no): ");
88
+ if (confirm.toLowerCase() === "yes") {
89
+ await ask("\n✏️ Please rephrase clearly what you want:\n");
90
+ continue;
91
+ }
92
+ else {
93
+ console.log("\n🔁 Okay, please describe what you want again.\n");
94
+ continue;
95
+ }
96
+ }
97
+ // ✅ STRUCTURE MODE
98
+ const clean = sanitizeStructureSR(result);
99
+ fs_1.default.writeFileSync(structurePath, clean);
100
+ // Generate the project structure
101
+ (0, child_process_1.execSync)("sr generate .", {
102
+ cwd: projectPath,
103
+ stdio: "inherit",
104
+ });
105
+ (0, child_process_1.execSync)("sr list --sr --with-icon", {
106
+ cwd: projectPath,
107
+ stdio: "inherit",
108
+ });
109
+ // Ask if satisfied
110
+ ({ ask, close } = createAsk());
111
+ const satisfied = await ask("\n✅ Are you satisfied with the structure? (yes/no): ");
112
+ if (satisfied.toLowerCase() === "yes")
113
+ break;
114
+ }
115
+ }
116
+ close();
117
+ // ─────────────────────────────────────────────
118
+ // 5️⃣ FINISH
119
+ // ─────────────────────────────────────────────
120
+ console.log(data_1.theme.success(`\n🎉 Project ${projectName} is ready!\n`));
121
+ console.log(data_1.theme.muted(` cd ${projectName}`));
122
+ console.log(data_1.theme.muted(" npm install"));
123
+ console.log(data_1.theme.muted(" npm run dev"));
124
+ }
125
+ catch (err) {
126
+ console.error(data_1.theme.error(`❌ Failed: ${err.message}`));
127
+ }
128
+ };
129
+ exports.ai = ai;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateStructure = generateStructure;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ async function generateStructure(params) {
9
+ try {
10
+ const response = await (0, node_fetch_1.default)("http://localhost:3000/generate-structure", {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify(params),
14
+ });
15
+ if (!response.ok) {
16
+ const text = await response.text();
17
+ throw new Error(`Backend error: ${text}`);
18
+ }
19
+ const data = await response.json();
20
+ if (typeof data?.result !== "string") {
21
+ throw new Error("Invalid backend response: result missing");
22
+ }
23
+ return data.result;
24
+ }
25
+ catch (err) {
26
+ throw new Error(`Failed to generate structure: ${err.message}`);
27
+ }
28
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateStructureWithGroq = generateStructureWithGroq;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ async function generateStructureWithGroq(params) {
9
+ const { existingStructure, description } = params;
10
+ const GROQ_API_KEY = process.env.GROQ_API_KEY;
11
+ if (!GROQ_API_KEY) {
12
+ console.error("❌ GROQ_API_KEY is missing.");
13
+ console.error("➡️ Add it to your .env file:");
14
+ console.error(" GROQ_API_KEY=your_key_here");
15
+ process.exit(1);
16
+ }
17
+ const systemPrompt = `
18
+ You are a frontend project structure generator for Scaffoldrite.
19
+
20
+ STRICT RULES:
21
+ - Output ONLY valid structure.sr syntax
22
+ - DO NOT include markdown
23
+ - DO NOT explain anything
24
+ - DO NOT add comments
25
+ - DO NOT wrap output in code blocks
26
+ - Preserve all existing files and folders
27
+ - Only ADD or EXTEND structure where necessary
28
+ - NEVER delete existing entries
29
+ - Frontend-only structure
30
+ - Ignore backend, database, server, API implementation details
31
+ - Backend mentions should be interpreted as frontend needs
32
+ - Valid entities: folder, file
33
+ - Maintain correct indentation
34
+ `;
35
+ const userPrompt = `
36
+ EXISTING STRUCTURE:
37
+ ${existingStructure}
38
+
39
+ PROJECT DESCRIPTION:
40
+ ${description}
41
+
42
+ TASK:
43
+ Update the existing structure to satisfy the description.
44
+ `;
45
+ const response = await (0, node_fetch_1.default)("https://api.groq.com/openai/v1/chat/completions", {
46
+ method: "POST",
47
+ headers: {
48
+ "Authorization": `Bearer ${GROQ_API_KEY}`,
49
+ "Content-Type": "application/json",
50
+ },
51
+ body: JSON.stringify({
52
+ model: "llama-3.1-70b-versatile",
53
+ temperature: 0.2,
54
+ max_tokens: 1500,
55
+ messages: [
56
+ { role: "system", content: systemPrompt },
57
+ { role: "user", content: userPrompt },
58
+ ],
59
+ }),
60
+ });
61
+ if (!response.ok) {
62
+ const text = await response.text();
63
+ throw new Error(`Groq API error: ${text}`);
64
+ }
65
+ const data = (await response.json());
66
+ const output = data.choices?.[0]?.message?.content;
67
+ if (!output || typeof output !== "string") {
68
+ throw new Error("Groq returned empty structure");
69
+ }
70
+ // 🔒 Final safety check
71
+ if (!output.includes("folder") && !output.includes("file")) {
72
+ throw new Error("Invalid structure.sr output from Groq");
73
+ }
74
+ return output.trim();
75
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateStructure = generateStructure;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ async function generateStructure(params) {
9
+ try {
10
+ const response = await (0, node_fetch_1.default)("https://your-backend.com/generate-structure", {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify(params),
14
+ });
15
+ if (!response.ok) {
16
+ const text = await response.text();
17
+ throw new Error(`Backend error: ${text}`);
18
+ }
19
+ const data = await response.json();
20
+ return data.structure;
21
+ }
22
+ catch (err) {
23
+ throw new Error(`Failed to generate structure: ${err.message}`);
24
+ }
25
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkAndReportPackages = checkAndReportPackages;
7
+ // ./checkpage/checkpackage.ts
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const depcheck_1 = __importDefault(require("depcheck"));
11
+ const data_1 = require("../../data");
12
+ const TS_TYPE_REGEX = /^@types\//;
13
+ async function checkAndReportPackages() {
14
+ console.log(data_1.theme.primary.bold(`${data_1.icons.folder} Checking project dependencies...\n`));
15
+ const packageJsonPath = path_1.default.join(process.cwd(), "package.json");
16
+ if (!fs_1.default.existsSync(packageJsonPath)) {
17
+ console.error(data_1.theme.error(`${data_1.icons.error} package.json was not found in current directory.`));
18
+ return;
19
+ }
20
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf-8"));
21
+ const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
22
+ const missingDeps = [];
23
+ // 1️⃣ Check runtime-resolvable deps
24
+ for (const dep of Object.keys(allDeps)) {
25
+ try {
26
+ require.resolve(dep);
27
+ }
28
+ catch {
29
+ missingDeps.push(dep);
30
+ }
31
+ }
32
+ // 2️⃣ Run depcheck to catch imported-but-not-listed packages
33
+ const options = { ignoreDirs: ["dist", "build", "node_modules"] };
34
+ const result = await (0, depcheck_1.default)(process.cwd(), options);
35
+ // Combine depcheck missing deps (filter duplicates)
36
+ const depcheckMissing = Object.keys(result.missing || {}).filter((dep) => !missingDeps.includes(dep));
37
+ const finalMissing = [...missingDeps, ...depcheckMissing];
38
+ if (finalMissing.length === 0) {
39
+ console.log(data_1.theme.success.bold(`${data_1.icons.check} All packages are installed!`));
40
+ return;
41
+ }
42
+ // Separate runtime deps vs TypeScript types
43
+ const runtimeDeps = [];
44
+ const typeDeps = [];
45
+ finalMissing.forEach((pkg) => {
46
+ if (TS_TYPE_REGEX.test(pkg))
47
+ typeDeps.push(pkg);
48
+ else
49
+ runtimeDeps.push(pkg);
50
+ });
51
+ console.log(data_1.theme.error.bold(`${data_1.icons.error} Missing packages:`));
52
+ runtimeDeps.forEach((pkg) => console.log(data_1.theme.warning(` - ${pkg} (runtime)`)));
53
+ typeDeps.forEach((pkg) => console.log(data_1.theme.warning(` - ${pkg} (TypeScript type)`)));
54
+ console.log(); // newline
55
+ if (runtimeDeps.length)
56
+ console.log(data_1.theme.info(`Install runtime packages: npm install ${runtimeDeps.join(" ")}`));
57
+ if (typeDeps.length)
58
+ console.log(data_1.theme.info(`Install TypeScript types as dev deps: npm install -D ${typeDeps.join(" ")}`));
59
+ }
@@ -28,6 +28,7 @@ const index_7 = require("../utils/index");
28
28
  const index_8 = require("../utils/index");
29
29
  const index_9 = require("../utils/index");
30
30
  const index_10 = require("../utils/index");
31
+ const checkpackage_1 = require("./checkpage/checkpackage");
31
32
  const args = process.argv.slice(3).filter((a) => !a.startsWith("--"));
32
33
  const arg3 = args[0];
33
34
  const arg4 = args[1];
@@ -64,9 +65,11 @@ if (!index_9.command || index_9.command === "--help" || index_9.command === "-h"
64
65
  (0, index_3.printUsage)();
65
66
  (0, index_8.exit)(0);
66
67
  }
67
- if (!allowedFlags) {
68
- console.error(index_4.theme.error.bold(`${index_4.icons.error} Unknown command: ${index_9.command}`));
69
- (0, index_8.exit)(1);
68
+ if (!index_3.ALLOWED_FLAGS.hasOwnProperty(index_9.command)) {
69
+ console.log(allowedFlags, 'wati');
70
+ console.error(`✗ Unknown command: ${index_9.command}`);
71
+ (0, index_3.printUsage)();
72
+ process.exit(1);
70
73
  }
71
74
  const invalidFlags = passedFlags?.filter((flag) => !allowedFlags.includes(flag));
72
75
  if (invalidFlags?.length > 0) {
@@ -128,6 +131,7 @@ exports.commandHandlers = {
128
131
  // return;
129
132
  // }
130
133
  // },
134
+ "check-packages": checkpackage_1.checkAndReportPackages,
131
135
  init: async () => {
132
136
  const shouldOverwrite = force;
133
137
  const sDir = path_1.default.join(index_7.baseDir, ".scaffoldrite");
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -240,6 +240,7 @@ exports.ALLOWED_FLAGS = {
240
240
  rename: ["--yes", "--dry-run", "--verbose", "--summary"],
241
241
  list: ["--structure", "--sr", "--fs", "--diff", "--with-icon"],
242
242
  version: [],
243
+ "check-packages": ["--validate"]
243
244
  };
244
245
  function printUsage(cmd) {
245
246
  if (cmd && exports.ALLOWED_FLAGS[cmd]) {
@@ -254,6 +255,7 @@ function printUsage(cmd) {
254
255
  delete: "<path> [--yes | -y] [--dry-run] [--verbose | --summary]",
255
256
  rename: "<path> <newName> [--yes | -y] [--dry-run] [--verbose | --summary]",
256
257
  version: "",
258
+ "check-packages": "[--validate]"
257
259
  };
258
260
  const args = argsMap[cmd] ? ` ${argsMap[cmd]}` : "";
259
261
  console.log(data_1.theme.primary.bold(`Usage for '${cmd}':`) +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scaffoldrite",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "A project structure validator and generator CLI tool.",
5
5
  "author": "Isaac Anasonye",
6
6
  "license": "MIT",
@@ -51,5 +51,11 @@
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=17"
54
+ },
55
+ "dependencies": {
56
+ "chalk": "^5.6.2",
57
+ "depcheck": "^1.4.7",
58
+ "dotenv": "^17.2.3",
59
+ "node-fetch": "^3.3.2"
54
60
  }
55
61
  }
package/readme.md CHANGED
@@ -621,7 +621,6 @@ Sometimes you need exceptions. That's where `.scaffoldignore` comes in:
621
621
  node_modules/ # Ignore dependencies
622
622
  dist/ # Ignore build output
623
623
  .temp/ # Ignore temporary files
624
-
625
624
  ```
626
625
 
627
626
  **Used when:**
@@ -819,17 +818,64 @@ When generating to **output directories**, you can use the `--copy` flag to pres
819
818
  # Generate structure with empty files (default)
820
819
  sr generate ./output
821
820
 
822
- # Generate structure AND copy file contents
821
+
822
+ ## 💾 Preserving Content & Mitigating Risks
823
+
824
+ Scaffoldrite focuses on **structure, not file content**. By default, `sr generate` creates missing files or folders **without preserving existing file content**. To make this safer, follow these best practices:
825
+
826
+ ### 1️⃣ Use `--copy` when generating to a different directory
827
+
828
+ ```bash
823
829
  sr generate ./output --copy
824
830
  ```
825
831
 
826
- **Use `--copy` when:**
827
- - Creating project templates from existing codebases
828
- - Distributing complete starter kits
829
- - Preserving file contents in generated output
830
- - Building project generators
832
+ * Copies existing file contents from source to output
833
+ * Maintains templates or boilerplate if defined
834
+ * Great for creating project templates or starter kits
835
+
836
+ **Notes:**
837
+
838
+ * `--copy` **does not work in-place**; use Git or manual backup for regenerating in the same directory.
839
+ * Cannot be combined with `--ignore-tooling`.
840
+
841
+ ### 2️⃣ Commit changes before regenerating
842
+
843
+ Always commit your work before running `sr generate`:
844
+
845
+ ```bash
846
+ git add .
847
+ git commit -m "Save work before sr generate"
848
+ ```
849
+
850
+ This ensures you can **restore deleted or modified files** if anything goes wrong.
851
+
852
+ ### 3️⃣ Validate first
853
+
854
+ Preview what Scaffoldrite would change without affecting your files:
855
+
856
+ ```bash
857
+ sr validate --allow-extra
858
+ ```
859
+
860
+ This shows missing, extra, or misaligned files, so you can make informed decisions.
861
+
862
+ ### 4️⃣ Rename carefully
863
+
864
+ Instead of renaming in `structure.sr` first:
865
+
866
+ 1. Rename the file in your filesystem.
867
+ 2. Sync your `structure.sr`:
868
+
869
+ ```bash
870
+ sr update --from-fs .
871
+ ```
872
+
873
+ This preserves content because the filesystem rename happens before Scaffoldrite updates the structure.
874
+
875
+ ### Warning
876
+
877
+ > ⚠ Renaming a file in `structure.sr` **will delete the old file in the filesystem**. Commit or back up your work first.
831
878
 
832
- **Note:** `--copy` cannot be used with `--ignore-tooling` as they're mutually exclusive flags.
833
879
 
834
880
  ### Real-World Examples
835
881