scaffoldrite 1.0.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/ast.js +2 -0
- package/dist/cli.js +311 -0
- package/dist/constraints.js +173 -0
- package/dist/data/flags.js +7 -0
- package/dist/data/index.js +18 -0
- package/dist/fsToAst.js +73 -0
- package/dist/generator.js +51 -0
- package/dist/parser.js +101 -0
- package/dist/structure.js +84 -0
- package/dist/validateFS.js +86 -0
- package/dist/validator.js +247 -0
- package/dist/visitor.js +23 -0
- package/package.json +51 -0
- package/readme.md +568 -0
package/dist/ast.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const readline_1 = __importDefault(require("readline"));
|
|
10
|
+
const validator_js_1 = require("./validator.js");
|
|
11
|
+
const parser_js_1 = require("./parser.js");
|
|
12
|
+
const generator_js_1 = require("./generator.js");
|
|
13
|
+
const structure_js_1 = require("./structure.js");
|
|
14
|
+
const validateFS_js_1 = require("./validateFS.js");
|
|
15
|
+
const fsToAst_js_1 = require("./fsToAst.js");
|
|
16
|
+
const index_js_1 = require("./data/index.js");
|
|
17
|
+
const structurePath = "./structure.sr";
|
|
18
|
+
function hasFlag(flag) {
|
|
19
|
+
return process.argv.includes(flag);
|
|
20
|
+
}
|
|
21
|
+
function getFlagValue(flagPrefix) {
|
|
22
|
+
return process.argv
|
|
23
|
+
.filter((arg) => arg.startsWith(flagPrefix))
|
|
24
|
+
.map((arg) => arg.split("=")[1])
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
function parseCSVFlag(flagName) {
|
|
28
|
+
return getFlagValue(flagName)
|
|
29
|
+
.flatMap((v) => v.split(","))
|
|
30
|
+
.map((v) => v.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
function getPassedFlags() {
|
|
34
|
+
return process.argv.filter((arg) => arg.startsWith("--"));
|
|
35
|
+
}
|
|
36
|
+
/* ===================== HELPERS ===================== */
|
|
37
|
+
function confirmProceed(dir) {
|
|
38
|
+
// Skip prompt if user asked for it
|
|
39
|
+
if (hasFlag("--yes") || hasFlag("-y"))
|
|
40
|
+
return Promise.resolve(true);
|
|
41
|
+
if (!fs_1.default.existsSync(dir))
|
|
42
|
+
return Promise.resolve(true);
|
|
43
|
+
if (fs_1.default.readdirSync(dir).length === 0)
|
|
44
|
+
return Promise.resolve(true);
|
|
45
|
+
console.warn("Output directory is not empty:", dir);
|
|
46
|
+
const rl = readline_1.default.createInterface({
|
|
47
|
+
input: process.stdin,
|
|
48
|
+
output: process.stdout,
|
|
49
|
+
});
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
rl.question("Proceed and apply changes? (y/N): ", (answer) => {
|
|
52
|
+
rl.close();
|
|
53
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/* ===================== STRUCTURE IO ===================== */
|
|
58
|
+
function saveStructure(root, rawConstraints, filePath) {
|
|
59
|
+
const lines = [];
|
|
60
|
+
function writeFolder(folder, indent = "") {
|
|
61
|
+
lines.push(`${indent}folder ${folder.name} {`);
|
|
62
|
+
for (const child of folder.children) {
|
|
63
|
+
if (child.type === "folder")
|
|
64
|
+
writeFolder(child, indent + " ");
|
|
65
|
+
else
|
|
66
|
+
lines.push(`${indent} file ${child.name}`);
|
|
67
|
+
}
|
|
68
|
+
lines.push(`${indent}}`);
|
|
69
|
+
}
|
|
70
|
+
for (const child of root.children) {
|
|
71
|
+
if (child.type === "folder")
|
|
72
|
+
writeFolder(child);
|
|
73
|
+
else
|
|
74
|
+
lines.push(`file ${child.name}`);
|
|
75
|
+
}
|
|
76
|
+
if (rawConstraints.length > 0) {
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push("constraints {");
|
|
79
|
+
for (const c of rawConstraints) {
|
|
80
|
+
lines.push(` ${c}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push("}");
|
|
83
|
+
}
|
|
84
|
+
fs_1.default.writeFileSync(filePath, lines.join("\n"));
|
|
85
|
+
}
|
|
86
|
+
function loadAST() {
|
|
87
|
+
const content = fs_1.default.readFileSync(structurePath, "utf-8");
|
|
88
|
+
return (0, parser_js_1.parseStructure)(content);
|
|
89
|
+
}
|
|
90
|
+
function printTree(root, indent = "") {
|
|
91
|
+
for (const child of root.children) {
|
|
92
|
+
if (child.type === "folder") {
|
|
93
|
+
console.log(`${indent} ${child.name}`);
|
|
94
|
+
printTree(child, indent + " ");
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(`${indent} ${child.name}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/* ===================== CLI ===================== */
|
|
102
|
+
const ALLOWED_FLAGS = {
|
|
103
|
+
init: ["--force", "--empty", "--from-fs"],
|
|
104
|
+
validate: ["--allow-extra"],
|
|
105
|
+
generate: ["--yes"],
|
|
106
|
+
create: ["--force", "--if-not-exists", "--yes"],
|
|
107
|
+
delete: ["--yes"],
|
|
108
|
+
rename: ["--yes"],
|
|
109
|
+
list: [],
|
|
110
|
+
};
|
|
111
|
+
const command = process.argv[2];
|
|
112
|
+
const passedFlags = getPassedFlags();
|
|
113
|
+
const allowedFlags = ALLOWED_FLAGS[command];
|
|
114
|
+
if (!allowedFlags) {
|
|
115
|
+
console.error(`Unknown command: ${command}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const invalidFlags = passedFlags.filter((flag) => !allowedFlags.includes(flag));
|
|
119
|
+
if (invalidFlags.length > 0) {
|
|
120
|
+
console.error(`Unknown flag(s) for '${command}': ${invalidFlags.join(", ")}\n` +
|
|
121
|
+
`Run 'scaffoldrite ${command} --help' to see available options.`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (!command) {
|
|
125
|
+
console.log(`
|
|
126
|
+
Usage:
|
|
127
|
+
scaffoldrite init [--force] [--empty] [--from-fs <dir>]
|
|
128
|
+
scaffoldrite validate [dir] [--allow-extra] [--allow-extra <path1> <path2> ...]
|
|
129
|
+
scaffoldrite generate [dir] [--yes]
|
|
130
|
+
scaffoldrite list
|
|
131
|
+
scaffoldrite create <path> <file|folder> [dir] [--force] [--if-not-exists]
|
|
132
|
+
scaffoldrite delete <path> [dir] [--yes]
|
|
133
|
+
scaffoldrite rename <path> <newName> [dir] [--yes]
|
|
134
|
+
`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
function getFlagValuesAfter(flag) {
|
|
138
|
+
const index = process.argv.indexOf(flag);
|
|
139
|
+
if (index === -1)
|
|
140
|
+
return [];
|
|
141
|
+
const values = [];
|
|
142
|
+
for (let i = index + 1; i < process.argv.length; i++) {
|
|
143
|
+
const arg = process.argv[i];
|
|
144
|
+
if (arg.startsWith("--"))
|
|
145
|
+
break;
|
|
146
|
+
values.push(arg);
|
|
147
|
+
}
|
|
148
|
+
return values;
|
|
149
|
+
}
|
|
150
|
+
const force = hasFlag("--force");
|
|
151
|
+
const ifNotExists = hasFlag("--if-not-exists");
|
|
152
|
+
const allowExtraPaths = getFlagValuesAfter("--allow-extra");
|
|
153
|
+
const allowExtra = hasFlag("--allow-extra");
|
|
154
|
+
const args = process.argv.slice(3).filter((a) => !a.startsWith("--"));
|
|
155
|
+
const arg3 = args[0];
|
|
156
|
+
const arg4 = args[1];
|
|
157
|
+
const arg5 = args[2];
|
|
158
|
+
(async () => {
|
|
159
|
+
/* ===== INIT ===== */
|
|
160
|
+
if (command === "init") {
|
|
161
|
+
const empty = hasFlag("--empty");
|
|
162
|
+
const fromFs = hasFlag("--from-fs");
|
|
163
|
+
const ignorePath = "./.scaffoldignore";
|
|
164
|
+
// Prevent overwriting structure.sr unless --force
|
|
165
|
+
if (fs_1.default.existsSync(structurePath) && !force) {
|
|
166
|
+
console.error("structure.sr already exists. Use --force to overwrite.");
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
// Prevent overwriting scaffoldignore ALWAYS
|
|
170
|
+
if (fs_1.default.existsSync(ignorePath)) {
|
|
171
|
+
console.log(".scaffoldignore already exists. It will not be overwritten.");
|
|
172
|
+
}
|
|
173
|
+
// 1 Empty init
|
|
174
|
+
if (empty) {
|
|
175
|
+
fs_1.default.writeFileSync(structurePath, "constraints {\n}\n");
|
|
176
|
+
if (!fs_1.default.existsSync(ignorePath)) {
|
|
177
|
+
fs_1.default.writeFileSync(ignorePath, index_js_1.DEFAULT_IGNORE_TEMPLATE);
|
|
178
|
+
}
|
|
179
|
+
console.log("Empty structure.sr created");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// 2 Init from filesystem
|
|
183
|
+
if (fromFs) {
|
|
184
|
+
const targetDir = path_1.default.resolve(args[0] ?? process.cwd());
|
|
185
|
+
const ignoreList = (0, fsToAst_js_1.getIgnoreList)();
|
|
186
|
+
const ast = (0, fsToAst_js_1.buildASTFromFS)(targetDir, ignoreList);
|
|
187
|
+
saveStructure(ast, [], structurePath);
|
|
188
|
+
if (!fs_1.default.existsSync(ignorePath)) {
|
|
189
|
+
fs_1.default.writeFileSync(ignorePath, index_js_1.DEFAULT_IGNORE_TEMPLATE);
|
|
190
|
+
}
|
|
191
|
+
console.log(`structure.sr generated from filesystem: ${targetDir}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Default init
|
|
195
|
+
fs_1.default.writeFileSync(structurePath, index_js_1.DEFAULT_TEMPLATE);
|
|
196
|
+
if (!fs_1.default.existsSync(ignorePath)) {
|
|
197
|
+
fs_1.default.writeFileSync(ignorePath, index_js_1.DEFAULT_IGNORE_TEMPLATE);
|
|
198
|
+
}
|
|
199
|
+
console.log("structure.sr created");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
/* ===== LIST ===== */
|
|
203
|
+
if (command === "list") {
|
|
204
|
+
const structure = loadAST();
|
|
205
|
+
console.log("Current structure:");
|
|
206
|
+
printTree(structure.root);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
/* ===== VALIDATE ===== */
|
|
210
|
+
if (command === "validate") {
|
|
211
|
+
const structure = loadAST();
|
|
212
|
+
// Parse allow-extra here only
|
|
213
|
+
const allowExtraPaths = getFlagValuesAfter("--allow-extra");
|
|
214
|
+
const allowExtra = hasFlag("--allow-extra") && allowExtraPaths.length === 0;
|
|
215
|
+
// Find real output dir
|
|
216
|
+
const outputDirArg = args.find((a) => {
|
|
217
|
+
if (a.startsWith("--"))
|
|
218
|
+
return false;
|
|
219
|
+
if (allowExtraPaths.includes(a))
|
|
220
|
+
return false;
|
|
221
|
+
return true;
|
|
222
|
+
});
|
|
223
|
+
const outputDir = path_1.default.resolve(outputDirArg ?? process.cwd());
|
|
224
|
+
try {
|
|
225
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
226
|
+
(0, validateFS_js_1.validateFS)(structure.root, outputDir, allowExtra, allowExtraPaths);
|
|
227
|
+
console.log("All constraints and filesystem structure are valid");
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
console.error("Validation failed:", err.message);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
/* ===== GENERATE ===== */
|
|
235
|
+
if (command === "generate") {
|
|
236
|
+
const structure = loadAST();
|
|
237
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
238
|
+
const outputDir = path_1.default.resolve(arg3 ?? process.cwd());
|
|
239
|
+
if (!(await confirmProceed(outputDir))) {
|
|
240
|
+
console.log("Generation cancelled.");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
(0, generator_js_1.generateFS)(structure.root, outputDir);
|
|
244
|
+
console.log("Generated filesystem at:", outputDir);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
/* ===== CREATE ===== */
|
|
248
|
+
if (command === "create") {
|
|
249
|
+
if (!arg3 || !arg4) {
|
|
250
|
+
console.error("Usage: scaffoldrite create <path> <file|folder> [outputDir] [--force] [--if-not-exists] [--yes]");
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
const structure = loadAST();
|
|
254
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
255
|
+
(0, structure_js_1.addNode)(structure.root, arg3, arg4, {
|
|
256
|
+
force,
|
|
257
|
+
ifNotExists,
|
|
258
|
+
});
|
|
259
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
260
|
+
const outputDir = path_1.default.resolve(arg5 ?? process.cwd());
|
|
261
|
+
if (!(await confirmProceed(outputDir))) {
|
|
262
|
+
console.log("Creation cancelled.");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
saveStructure(structure.root, structure.rawConstraints, structurePath);
|
|
266
|
+
(0, generator_js_1.generateFS)(structure.root, outputDir);
|
|
267
|
+
console.log("Created successfully.");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
/* ===== DELETE ===== */
|
|
271
|
+
if (command === "delete") {
|
|
272
|
+
if (!arg3) {
|
|
273
|
+
console.error("Usage: scaffoldrite delete <path> [outputDir] [--yes]");
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
const structure = loadAST();
|
|
277
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
278
|
+
(0, structure_js_1.deleteNode)(structure.root, arg3);
|
|
279
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
280
|
+
const outputDir = path_1.default.resolve(arg4 ?? process.cwd());
|
|
281
|
+
if (!(await confirmProceed(outputDir))) {
|
|
282
|
+
console.log("Deletion cancelled.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
saveStructure(structure.root, structure.rawConstraints, structurePath);
|
|
286
|
+
(0, generator_js_1.generateFS)(structure.root, outputDir);
|
|
287
|
+
console.log("Deleted successfully.");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
/* ===== RENAME ===== */
|
|
291
|
+
if (command === "rename") {
|
|
292
|
+
if (!arg3 || !arg4) {
|
|
293
|
+
console.error("Usage: scaffoldrite rename <path> <newName> [outputDir] [--yes]");
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
const structure = loadAST();
|
|
297
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
298
|
+
(0, structure_js_1.renameNode)(structure.root, arg3, arg4);
|
|
299
|
+
(0, validator_js_1.validateConstraints)(structure.root, structure.constraints);
|
|
300
|
+
const outputDir = path_1.default.resolve(arg5 ?? process.cwd());
|
|
301
|
+
if (!(await confirmProceed(outputDir))) {
|
|
302
|
+
console.log("Rename cancelled.");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
saveStructure(structure.root, structure.rawConstraints, structurePath);
|
|
306
|
+
(0, generator_js_1.generateFS)(structure.root, outputDir);
|
|
307
|
+
console.log("Renamed successfully.");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
console.error(`Unknown command: ${command}`);
|
|
311
|
+
})();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseConstraints = parseConstraints;
|
|
4
|
+
function splitArgs(line) {
|
|
5
|
+
const args = [];
|
|
6
|
+
let current = "";
|
|
7
|
+
let inQuotes = false;
|
|
8
|
+
for (let i = 0; i < line.length; i++) {
|
|
9
|
+
const ch = line[i];
|
|
10
|
+
if (ch === '"' || ch === "'") {
|
|
11
|
+
inQuotes = !inQuotes;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (ch === " " && !inQuotes) {
|
|
15
|
+
if (current.length > 0) {
|
|
16
|
+
args.push(current);
|
|
17
|
+
current = "";
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
current += ch;
|
|
22
|
+
}
|
|
23
|
+
if (current.length > 0)
|
|
24
|
+
args.push(current);
|
|
25
|
+
return args;
|
|
26
|
+
}
|
|
27
|
+
function parseConstraints(input) {
|
|
28
|
+
const lines = input.split("\n");
|
|
29
|
+
const constraints = [];
|
|
30
|
+
for (let line of lines) {
|
|
31
|
+
line = line.trim();
|
|
32
|
+
if (!line)
|
|
33
|
+
continue;
|
|
34
|
+
if (line.startsWith("require ")) {
|
|
35
|
+
const path = line.replace("require ", "").trim();
|
|
36
|
+
constraints.push({ type: "require", path });
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (line.startsWith("forbid ")) {
|
|
40
|
+
const path = line.replace("forbid ", "").trim();
|
|
41
|
+
constraints.push({ type: "forbid", path });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (line.startsWith("maxFilesByExtRecursive ")) {
|
|
45
|
+
const parts = splitArgs(line);
|
|
46
|
+
const ext = parts[1];
|
|
47
|
+
const value = Number(parts[2]);
|
|
48
|
+
const path = parts[3];
|
|
49
|
+
constraints.push({ type: "maxFilesByExtRecursive", path, value, ext });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (line.startsWith("maxFilesRecursive ")) {
|
|
53
|
+
const parts = splitArgs(line);
|
|
54
|
+
const value = Number(parts[1]);
|
|
55
|
+
const path = parts[2];
|
|
56
|
+
constraints.push({ type: "maxFilesRecursive", path, value });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (line.startsWith("maxFilesByExt ")) {
|
|
60
|
+
const parts = splitArgs(line);
|
|
61
|
+
const ext = parts[1];
|
|
62
|
+
const value = Number(parts[2]);
|
|
63
|
+
const path = parts[3];
|
|
64
|
+
constraints.push({ type: "maxFilesByExt", path, value, ext });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (line.startsWith("maxFiles ")) {
|
|
68
|
+
const parts = splitArgs(line);
|
|
69
|
+
const value = Number(parts[1]);
|
|
70
|
+
const path = parts[2];
|
|
71
|
+
constraints.push({ type: "maxFiles", path, value });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (line.startsWith("maxFoldersRecursive ")) {
|
|
75
|
+
const parts = splitArgs(line);
|
|
76
|
+
const value = Number(parts[1]);
|
|
77
|
+
const path = parts[2];
|
|
78
|
+
constraints.push({ type: "maxFoldersRecursive", path, value });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (line.startsWith("maxFolders ")) {
|
|
82
|
+
const parts = splitArgs(line);
|
|
83
|
+
const value = Number(parts[1]);
|
|
84
|
+
const path = parts[2];
|
|
85
|
+
constraints.push({ type: "maxFolders", path, value });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (line.startsWith("minFiles ")) {
|
|
89
|
+
const parts = splitArgs(line);
|
|
90
|
+
const value = Number(parts[1]);
|
|
91
|
+
const path = parts[2];
|
|
92
|
+
constraints.push({ type: "minFiles", path, value });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (line.startsWith("minFolders ")) {
|
|
96
|
+
const parts = splitArgs(line);
|
|
97
|
+
const value = Number(parts[1]);
|
|
98
|
+
const path = parts[2];
|
|
99
|
+
constraints.push({ type: "minFolders", path, value });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// NEW RULES PARSING (WITH SCOPE)
|
|
103
|
+
if (line.startsWith("eachFolderMustContain ")) {
|
|
104
|
+
const parts = splitArgs(line);
|
|
105
|
+
const scope = parts[1];
|
|
106
|
+
// if only 3 parts, then path is root
|
|
107
|
+
const hasPath = parts.length === 4;
|
|
108
|
+
const path = hasPath ? parts[2] : "";
|
|
109
|
+
const value = hasPath ? parts[3] : parts[2];
|
|
110
|
+
constraints.push({ type: "eachFolderMustContain", path, value, scope });
|
|
111
|
+
console.log({ type: "eachFolderMustContain", path, value, scope });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (line.startsWith("eachFolderMustContainFile ")) {
|
|
115
|
+
const parts = splitArgs(line);
|
|
116
|
+
const scope = parts[1];
|
|
117
|
+
const hasPath = parts.length === 4;
|
|
118
|
+
const path = hasPath ? parts[2] : "";
|
|
119
|
+
const value = hasPath ? parts[3] : parts[2];
|
|
120
|
+
constraints.push({ type: "eachFolderMustContainFile", path, value, scope });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (line.startsWith("eachFolderMustContainFolder ")) {
|
|
124
|
+
const parts = splitArgs(line);
|
|
125
|
+
const scope = parts[1];
|
|
126
|
+
const hasPath = parts.length === 4;
|
|
127
|
+
const path = hasPath ? parts[2] : "";
|
|
128
|
+
const value = hasPath ? parts[3] : parts[2];
|
|
129
|
+
constraints.push({ type: "eachFolderMustContainFolder", path, value, scope });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (line.startsWith("eachFolderMustHaveExt ")) {
|
|
133
|
+
const parts = splitArgs(line);
|
|
134
|
+
const scope = parts[1];
|
|
135
|
+
const hasPath = parts.length === 4;
|
|
136
|
+
const path = hasPath ? parts[2] : "";
|
|
137
|
+
const ext = hasPath ? parts[3] : parts[2];
|
|
138
|
+
constraints.push({ type: "eachFolderMustHaveExt", path, ext, scope });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// EXISTING RULES
|
|
142
|
+
if (line.startsWith("mustContain ")) {
|
|
143
|
+
const parts = splitArgs(line);
|
|
144
|
+
const path = parts[1];
|
|
145
|
+
const value = parts[2];
|
|
146
|
+
constraints.push({ type: "mustContain", path, value });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (line.startsWith("fileNameRegex ")) {
|
|
150
|
+
const parts = splitArgs(line);
|
|
151
|
+
const path = parts[1];
|
|
152
|
+
const regex = parts[2];
|
|
153
|
+
constraints.push({ type: "fileNameRegex", path, regex });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (line.startsWith("maxDepth ")) {
|
|
157
|
+
const parts = splitArgs(line);
|
|
158
|
+
const value = Number(parts[1]);
|
|
159
|
+
const path = parts[2];
|
|
160
|
+
constraints.push({ type: "maxDepth", path, value });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (line.startsWith("mustHaveFile ")) {
|
|
164
|
+
const parts = splitArgs(line);
|
|
165
|
+
const path = parts[1];
|
|
166
|
+
const value = parts[2];
|
|
167
|
+
constraints.push({ type: "mustHaveFile", path, value });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`Unknown constraint: ${line}`);
|
|
171
|
+
}
|
|
172
|
+
return constraints;
|
|
173
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_IGNORE_TEMPLATE = exports.DEFAULT_TEMPLATE = void 0;
|
|
4
|
+
exports.DEFAULT_TEMPLATE = `folder src {
|
|
5
|
+
file index.ts
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
constraints {
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
exports.DEFAULT_IGNORE_TEMPLATE = `node_modules
|
|
12
|
+
.git
|
|
13
|
+
.next
|
|
14
|
+
dist
|
|
15
|
+
build
|
|
16
|
+
coverage
|
|
17
|
+
.turbo
|
|
18
|
+
`;
|
package/dist/fsToAst.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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.DEFAULT_IGNORES = void 0;
|
|
7
|
+
exports.loadIgnoreList = loadIgnoreList;
|
|
8
|
+
exports.getIgnoreList = getIgnoreList;
|
|
9
|
+
exports.buildASTFromFS = buildASTFromFS;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const ignoreFilePath = "./.scaffoldignore";
|
|
13
|
+
exports.DEFAULT_IGNORES = [
|
|
14
|
+
"node_modules",
|
|
15
|
+
".git",
|
|
16
|
+
".next",
|
|
17
|
+
"dist",
|
|
18
|
+
"build",
|
|
19
|
+
"coverage",
|
|
20
|
+
".turbo",
|
|
21
|
+
];
|
|
22
|
+
function loadIgnoreList(filePath) {
|
|
23
|
+
if (!fs_1.default.existsSync(filePath))
|
|
24
|
+
return [];
|
|
25
|
+
const content = fs_1.default.readFileSync(filePath, "utf-8");
|
|
26
|
+
return content
|
|
27
|
+
.split("\n")
|
|
28
|
+
.map((x) => x.trim())
|
|
29
|
+
.map((x) => x.split("#")[0].trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
function getIgnoreList() {
|
|
33
|
+
return fs_1.default.existsSync(ignoreFilePath)
|
|
34
|
+
? loadIgnoreList(ignoreFilePath)
|
|
35
|
+
: exports.DEFAULT_IGNORES;
|
|
36
|
+
}
|
|
37
|
+
function buildASTFromFS(dir, ignoreList = []) {
|
|
38
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
39
|
+
throw new Error(`Directory does not exist: ${dir}`);
|
|
40
|
+
}
|
|
41
|
+
const root = {
|
|
42
|
+
type: "folder",
|
|
43
|
+
name: path_1.default.basename(dir),
|
|
44
|
+
children: [],
|
|
45
|
+
};
|
|
46
|
+
function scan(folderPath, node) {
|
|
47
|
+
const items = fs_1.default.readdirSync(folderPath);
|
|
48
|
+
for (const item of items) {
|
|
49
|
+
if (ignoreList.includes(item))
|
|
50
|
+
continue;
|
|
51
|
+
const itemPath = path_1.default.join(folderPath, item);
|
|
52
|
+
const stat = fs_1.default.statSync(itemPath);
|
|
53
|
+
if (stat.isDirectory()) {
|
|
54
|
+
const childFolder = {
|
|
55
|
+
type: "folder",
|
|
56
|
+
name: item,
|
|
57
|
+
children: [],
|
|
58
|
+
};
|
|
59
|
+
node.children.push(childFolder);
|
|
60
|
+
scan(itemPath, childFolder);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const childFile = {
|
|
64
|
+
type: "file",
|
|
65
|
+
name: item,
|
|
66
|
+
};
|
|
67
|
+
node.children.push(childFile);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
scan(dir, root);
|
|
72
|
+
return root;
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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.generateFS = generateFS;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const visitor_js_1 = require("./visitor.js");
|
|
10
|
+
function generateFS(ast, outputDir) {
|
|
11
|
+
const root = path_1.default.resolve(outputDir);
|
|
12
|
+
const expected = new Set();
|
|
13
|
+
function ensureFolder(folderPath) {
|
|
14
|
+
if (!fs_1.default.existsSync(folderPath)) {
|
|
15
|
+
fs_1.default.mkdirSync(folderPath, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function ensureFile(filePath) {
|
|
19
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
20
|
+
fs_1.default.writeFileSync(filePath, "");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
ensureFolder(root);
|
|
24
|
+
expected.add(root);
|
|
25
|
+
(0, visitor_js_1.visit)(ast, {
|
|
26
|
+
folder: (_, nodePath) => {
|
|
27
|
+
const fullPath = path_1.default.join(root, nodePath);
|
|
28
|
+
expected.add(fullPath);
|
|
29
|
+
ensureFolder(fullPath);
|
|
30
|
+
},
|
|
31
|
+
file: (_, nodePath) => {
|
|
32
|
+
const fullPath = path_1.default.join(root, nodePath);
|
|
33
|
+
expected.add(fullPath);
|
|
34
|
+
ensureFile(fullPath);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
// 2️⃣ Remove extras (never root)
|
|
38
|
+
function clean(dir) {
|
|
39
|
+
for (const entry of fs_1.default.readdirSync(dir)) {
|
|
40
|
+
const fullPath = path_1.default.join(dir, entry);
|
|
41
|
+
if (!expected.has(fullPath)) {
|
|
42
|
+
fs_1.default.rmSync(fullPath, { recursive: true, force: true });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (fs_1.default.statSync(fullPath).isDirectory()) {
|
|
46
|
+
clean(fullPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
clean(root);
|
|
51
|
+
}
|