@ldgerrits/rogen 1.4.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/tree.js ADDED
@@ -0,0 +1,182 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ function hasPathPrefix(p, dir) {
4
+ return p === dir || p.startsWith(dir + "/");
5
+ }
6
+ export const toPosix = (p) => p.split(path.sep).join("/");
7
+ export function applyCasing(value, casing) {
8
+ if (value.length === 0)
9
+ return value;
10
+ const firstCharacter = casing === "PascalCase"
11
+ ? value[0].toUpperCase()
12
+ : value[0].toLowerCase();
13
+ return firstCharacter + value.slice(1);
14
+ }
15
+ export function getOrCreateNode(parent, key, className) {
16
+ if (!parent[key]) {
17
+ parent[key] = className == null ? {} : { $className: className };
18
+ }
19
+ return parent[key];
20
+ }
21
+ export function pruneObject(node, buildDir, outputDir, removed = [], treePath = "") {
22
+ for (const key in node) {
23
+ const val = node[key];
24
+ if (typeof val !== "object" || val === null)
25
+ continue;
26
+ const childTreePath = treePath ? `${treePath}.${key}` : key;
27
+ const childNode = val;
28
+ if (childNode.$path) {
29
+ if (hasPathPrefix(childNode.$path, buildDir))
30
+ continue;
31
+ const absolutePath = path.resolve(outputDir, childNode.$path);
32
+ if (!fs.existsSync(absolutePath)) {
33
+ delete node[key];
34
+ removed.push({
35
+ treePath: childTreePath,
36
+ rojoPath: childNode.$path,
37
+ });
38
+ continue;
39
+ }
40
+ }
41
+ pruneObject(childNode, buildDir, outputDir, removed, childTreePath);
42
+ }
43
+ return node;
44
+ }
45
+ export function sortObject(obj) {
46
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
47
+ return obj;
48
+ }
49
+ const record = obj;
50
+ return Object.keys(record)
51
+ .sort()
52
+ .reduce((acc, key) => {
53
+ acc[key] = sortObject(record[key]);
54
+ return acc;
55
+ }, {});
56
+ }
57
+ export function findMissingPaths(node, buildDir, outputDir, missing = [], treePath = "") {
58
+ for (const key in node) {
59
+ const val = node[key];
60
+ if (typeof val !== "object" || val === null)
61
+ continue;
62
+ const childTreePath = treePath ? `${treePath}.${key}` : key;
63
+ const childNode = val;
64
+ if (childNode.$path && hasPathPrefix(childNode.$path, buildDir)) {
65
+ const absolutePath = path.resolve(outputDir, childNode.$path);
66
+ if (!fs.existsSync(absolutePath)) {
67
+ missing.push({
68
+ parent: node,
69
+ key,
70
+ treePath: childTreePath,
71
+ path: childNode.$path,
72
+ absolutePath,
73
+ });
74
+ }
75
+ }
76
+ findMissingPaths(childNode, buildDir, outputDir, missing, childTreePath);
77
+ }
78
+ return missing;
79
+ }
80
+ const isScript = (filename) => /\.(tsx?|luau|lua)$/i.test(filename) &&
81
+ !filename.toLowerCase().endsWith(".d.ts");
82
+ const isModel = (filename) => /\.(rbxm|rbxmx)$/i.test(filename);
83
+ export function isData(filename) {
84
+ if (isScript(filename) || isModel(filename))
85
+ return false;
86
+ if (filename.startsWith("."))
87
+ return false;
88
+ return /\.[a-z0-9]+$/i.test(filename);
89
+ }
90
+ export const isValidSource = (filename) => isScript(filename) || isModel(filename) || isData(filename);
91
+ export const isInitFile = (filename) => isScript(filename) && /^(index|init)([.-][a-z0-9_]+)?\./i.test(filename);
92
+ function getRojoBaseName(filename) {
93
+ if (isScript(filename)) {
94
+ return filename
95
+ .replace(/\.(server|client)\.(luau?|lua|tsx?)$/i, "")
96
+ .replace(/\.(luau?|lua|tsx?)$/i, "");
97
+ }
98
+ if (isModel(filename)) {
99
+ return filename.replace(/\.(rbxmx?)$/i, "");
100
+ }
101
+ if (isData(filename)) {
102
+ return filename.replace(/\.[a-z0-9]+$/i, "");
103
+ }
104
+ return filename;
105
+ }
106
+ export function collapseFolders(node, buildDir, outputDir) {
107
+ let childCount = 0;
108
+ let canCollapse = true;
109
+ let commonDir = null;
110
+ for (const key in node) {
111
+ if (key.startsWith("$"))
112
+ continue;
113
+ const val = node[key];
114
+ if (typeof val !== "object" || val === null)
115
+ continue;
116
+ const childNode = val;
117
+ // Process deepest nested children first
118
+ collapseFolders(childNode, buildDir, outputDir);
119
+ childCount++;
120
+ if (!childNode.$path) {
121
+ canCollapse = false;
122
+ }
123
+ else {
124
+ const childAbsPath = path.resolve(outputDir, childNode.$path);
125
+ const parentDir = path.dirname(childAbsPath);
126
+ // All children should share the same directory
127
+ if (commonDir === null) {
128
+ commonDir = parentDir;
129
+ }
130
+ else if (commonDir !== parentDir) {
131
+ canCollapse = false;
132
+ }
133
+ const fileName = path.basename(childAbsPath);
134
+ if (getRojoBaseName(fileName) !== key) {
135
+ canCollapse = false;
136
+ }
137
+ }
138
+ }
139
+ if (childCount === 0 || !canCollapse || commonDir === null) {
140
+ return;
141
+ }
142
+ const absoluteBuildDir = path.resolve(outputDir, buildDir);
143
+ if (!hasPathPrefix(toPosix(commonDir), toPosix(absoluteBuildDir))) {
144
+ return;
145
+ }
146
+ try {
147
+ const diskItems = fs.readdirSync(commonDir);
148
+ // filter out marker files so they do not prevent folders from collapsing
149
+ const visibleDiskItems = diskItems.filter((item) => !item.startsWith("."));
150
+ if (visibleDiskItems.length !== childCount) {
151
+ return;
152
+ }
153
+ }
154
+ catch {
155
+ return;
156
+ }
157
+ // Replace all child files with a single folder $path
158
+ const relativeCommonDir = toPosix(path.relative(outputDir, commonDir));
159
+ for (const key in node) {
160
+ if (!key.startsWith("$")) {
161
+ delete node[key];
162
+ }
163
+ }
164
+ node.$path = relativeCommonDir;
165
+ delete node.$className;
166
+ }
167
+ export function findExposedDataFiles(node, exposed = []) {
168
+ for (const key in node) {
169
+ if (key.startsWith("$"))
170
+ continue;
171
+ const val = node[key];
172
+ if (typeof val !== "object" || val === null)
173
+ continue;
174
+ const childNode = val;
175
+ // Check if the current node exposes a raw data file path
176
+ if (childNode.$path && isData(childNode.$path)) {
177
+ exposed.push(childNode.$path);
178
+ }
179
+ findExposedDataFiles(childNode, exposed);
180
+ }
181
+ return exposed;
182
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@ldgerrits/rogen",
3
+ "version": "1.4.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Feature-based folder routing for Roblox",
8
+ "author": "ldgerrits",
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "workspaces": [
12
+ "docs"
13
+ ],
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE.md"
18
+ ],
19
+ "scripts": {
20
+ "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
21
+ "build": "tsc",
22
+ "bundle": "esbuild bin/rogen.ts --bundle --platform=node --format=cjs --external:*.node --outfile=dist/bundle.cjs",
23
+ "prepublishOnly": "npm run clean && npm run build && npm run bundle",
24
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
25
+ "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
26
+ "lint": "eslint .",
27
+ "lint:fix": "eslint . --fix",
28
+ "release:local": "npm run bundle && npx pkg dist/bundle.cjs --targets node18-win-x64,node18-linux-x64,node18-macos-x64 --out-path releases/"
29
+ },
30
+ "main": "dist/index.js",
31
+ "bin": {
32
+ "rogen": "dist/bundle.cjs"
33
+ },
34
+ "devDependencies": {
35
+ "@eslint/js": "^10.0.1",
36
+ "@types/jest": "^30.0.0",
37
+ "@types/node": "^26.0.1",
38
+ "@types/picomatch": "^4.0.3",
39
+ "esbuild": "^0.28.1",
40
+ "eslint": "^10.6.0",
41
+ "eslint-plugin-jest": "^29.15.4",
42
+ "jest": "^30.4.2",
43
+ "ts-jest": "^29.4.11",
44
+ "typescript": "^6.0.3",
45
+ "typescript-eslint": "^8.62.1"
46
+ },
47
+ "dependencies": {
48
+ "chokidar": "^3.6.0",
49
+ "picomatch": "^4.0.5"
50
+ },
51
+ "allowScripts": {
52
+ "esbuild@0.28.1": true,
53
+ "sharp@0.34.5": true,
54
+ "unrs-resolver@1.12.2": true
55
+ }
56
+ }