@ldgerrits/rogen 1.4.4 → 2.0.0-beta.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.
package/package.json CHANGED
@@ -1,66 +1,103 @@
1
- {
2
- "name": "@ldgerrits/rogen",
3
- "version": "1.4.4",
4
- "description": "A CLI tool that enables feature-based architecture for Roblox",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
- "homepage": "https://rogen-playfully.vercel.app/",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/LDGerrits/rogen.git"
12
- },
13
- "keywords": [
14
- "roblox",
15
- "organization",
16
- "feature-based architecture"
17
- ],
18
- "author": "ldgerrits",
19
- "license": "MIT",
20
- "type": "module",
21
- "workspaces": [
22
- "docs"
23
- ],
24
- "files": [
25
- "dist",
26
- "README.md",
27
- "LICENSE.md"
28
- ],
29
- "scripts": {
30
- "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
31
- "build": "tsc",
32
- "bundle": "esbuild bin/rogen.ts --bundle --platform=node --format=cjs --external:*.node --outfile=dist/bundle.cjs",
33
- "prepublishOnly": "npm run clean && npm run build && npm run bundle",
34
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
35
- "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
36
- "lint": "eslint .",
37
- "lint:fix": "eslint . --fix",
38
- "release:local": "npm run bundle && npx pkg dist/bundle.cjs --targets node18-win-x64,node18-linux-x64,node18-macos-x64 --out-path releases/"
39
- },
40
- "main": "dist/index.js",
41
- "bin": {
42
- "rogen": "dist/bundle.cjs"
43
- },
44
- "devDependencies": {
45
- "@eslint/js": "^10.0.1",
46
- "@types/jest": "^30.0.0",
47
- "@types/node": "^26.0.1",
48
- "@types/picomatch": "^4.0.3",
49
- "esbuild": "^0.28.1",
50
- "eslint": "^10.6.0",
51
- "eslint-plugin-jest": "^29.15.4",
52
- "jest": "^30.4.2",
53
- "ts-jest": "^29.4.11",
54
- "typescript": "^6.0.3",
55
- "typescript-eslint": "^8.62.1"
56
- },
57
- "dependencies": {
58
- "chokidar": "^3.6.0",
59
- "picomatch": "^4.0.5"
60
- },
61
- "allowScripts": {
62
- "esbuild@0.28.1": true,
63
- "sharp@0.34.5": true,
64
- "unrs-resolver@1.12.2": true
65
- }
66
- }
1
+ {
2
+ "name": "@ldgerrits/rogen",
3
+ "version": "2.0.0-beta.1",
4
+ "description": "A superset of Rojo that generates Rojo project files",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "homepage": "https://rogen-roblox.vercel.app/",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/LDGerrits/rogen.git"
12
+ },
13
+ "keywords": [
14
+ "roblox",
15
+ "rojo",
16
+ "luau",
17
+ "roblox-ts",
18
+ "roblox-studio",
19
+ "feature-based-architecture",
20
+ "vertical-slice-architecture",
21
+ "folder-structure",
22
+ "file-based-routing",
23
+ "sync",
24
+ "cli"
25
+ ],
26
+ "author": "ldgerrits",
27
+ "license": "MIT",
28
+ "type": "module",
29
+ "workspaces": [
30
+ "docs"
31
+ ],
32
+ "files": [
33
+ "dist/bundle.cjs",
34
+ "README.md",
35
+ "LICENSE.md"
36
+ ],
37
+ "scripts": {
38
+ "prepare": "node scripts/prepare.ts",
39
+ "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
40
+ "prebuild": "npm run generate",
41
+ "build": "tsc",
42
+ "prebundle": "npm run generate",
43
+ "bundle": "node scripts/bundle.ts",
44
+ "prebundle:watch": "npm run generate",
45
+ "bundle:watch": "esbuild bin/rogen.ts --bundle --platform=node --format=cjs --external:*.node --main-fields=module,main --define:import.meta.dirname=__dirname --outfile=dist/bundle.cjs --watch",
46
+ "prepublishOnly": "npm run clean && npm run build && npm run bundle",
47
+ "pretest": "npm run generate",
48
+ "test": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js",
49
+ "pretest:watch": "npm run generate",
50
+ "test:watch": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js --watch",
51
+ "pretest:unit": "npm run generate",
52
+ "test:unit": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js --config jest.unit.config.js",
53
+ "pretest:changed": "npm run generate",
54
+ "test:changed": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js --config jest.unit.config.js --onlyChanged --passWithNoTests",
55
+ "pretest:e2e": "npm run generate",
56
+ "test:e2e": "node --experimental-vm-modules --disable-warning=ExperimentalWarning node_modules/jest/bin/jest.js --config jest.e2e.config.js",
57
+ "test:e2e:update": "node scripts/summarize-transcripts.ts --update",
58
+ "test:e2e:diff": "node scripts/summarize-transcripts.ts",
59
+ "eval:agents": "node evals/agents/run.mjs",
60
+ "pretypecheck": "npm run generate",
61
+ "typecheck": "tsc -p e2e && tsc -p scripts",
62
+ "check": "npm run lint && npm run build && npm run typecheck && npm test",
63
+ "prelint": "npm run generate",
64
+ "lint": "eslint .",
65
+ "generate": "tsx scripts/generate.ts",
66
+ "generate:templates": "tsx scripts/generate.ts",
67
+ "generate:services": "tsx scripts/generate-services.ts",
68
+ "lint:fix": "eslint . --fix",
69
+ "release:local": "npm run bundle && node scripts/make-binary.ts releases/rogen",
70
+ "version": "tsx scripts/sync-version.ts"
71
+ },
72
+ "bin": {
73
+ "rogen": "dist/bundle.cjs"
74
+ },
75
+ "devDependencies": {
76
+ "@eslint/js": "^10.0.1",
77
+ "@types/jest": "^30.0.0",
78
+ "@types/node": "^26.0.1",
79
+ "@types/picomatch": "^4.0.3",
80
+ "ajv": "^8.20.0",
81
+ "esbuild": "^0.28.1",
82
+ "eslint": "^10.6.0",
83
+ "eslint-plugin-jest": "^29.15.4",
84
+ "jest": "^30.4.2",
85
+ "ts-jest": "^29.4.11",
86
+ "tsx": "^4.23.15",
87
+ "typescript": "^6.0.3",
88
+ "typescript-eslint": "^8.62.1"
89
+ },
90
+ "dependencies": {
91
+ "@clack/core": "^1.5.1",
92
+ "@clack/prompts": "^1.8.1",
93
+ "@msgpack/msgpack": "^3.1.3",
94
+ "chokidar": "^3.6.0",
95
+ "jsonc-parser": "^3.3.1",
96
+ "picomatch": "^4.0.5"
97
+ },
98
+ "allowScripts": {
99
+ "esbuild@0.28.1": true,
100
+ "sharp@0.34.5": true,
101
+ "unrs-resolver@1.12.2": true
102
+ }
103
+ }
package/dist/build.js DELETED
@@ -1,256 +0,0 @@
1
- import { promises as fsp } from "fs";
2
- import path from "path";
3
- import picomatch from "picomatch";
4
- import { applyCasing, getOrCreateNode, pruneObject, sortObject, findMissingPaths, toPosix, collapseFolders, isInitFile, isValidSource, findExposedDataFiles, } from "./tree.js";
5
- import { resolveRoute, } from "./route.js";
6
- import { serviceParents, generateRoutingMaps, defaultConfig, services, } from "./constants.js";
7
- const SYSTEM_MARKERS = {
8
- structure: true,
9
- verbatim: true,
10
- unwrap: true,
11
- };
12
- function buildSubPath(sourceRel) {
13
- const segments = sourceRel.split(/[\\/]/).filter(Boolean);
14
- let rootIndex = 0;
15
- while (rootIndex < segments.length &&
16
- (segments[rootIndex] === ".." || segments[rootIndex] === ".")) {
17
- rootIndex++;
18
- }
19
- if (rootIndex + 1 >= segments.length) {
20
- return "";
21
- }
22
- return segments.slice(rootIndex + 1).join("/");
23
- }
24
- async function listTree(dir) {
25
- const listings = new Map();
26
- async function scan(currentDir) {
27
- try {
28
- const entries = await fsp.readdir(currentDir, {
29
- withFileTypes: true,
30
- });
31
- listings.set(currentDir, entries);
32
- const hasInit = entries.some((e) => e.isFile() && isInitFile(e.name));
33
- const hasSync = entries.some((e) => e.isFile() && e.name === ".sync");
34
- if (hasInit || hasSync)
35
- return;
36
- const subdirs = entries
37
- .filter((e) => e.isDirectory())
38
- .map((e) => path.join(currentDir, e.name));
39
- await Promise.all(subdirs.map(scan));
40
- }
41
- catch (error) {
42
- if (error instanceof Error &&
43
- "code" in error &&
44
- error.code === "ENOENT")
45
- return;
46
- throw error;
47
- }
48
- }
49
- await scan(dir);
50
- return listings;
51
- }
52
- function compileTagRegexes(activeEnvs) {
53
- return Array.from(activeEnvs).map((env) => ({
54
- suffix: new RegExp(`[\\.\\-_\\+]${env}$`, "i"),
55
- prefix: new RegExp(`^${env}[\\.\\-_\\+]`, "i"),
56
- middle: new RegExp(`[\\.\\-_\\+]${env}(?=[\\.\\-_\\+])`, "i"),
57
- }));
58
- }
59
- function buildDirectoryMarkers(sourcePath, listings, routingMaps, knownFlags) {
60
- const directoryMarkers = {};
61
- for (const [dir, entries] of listings.entries()) {
62
- const markers = [];
63
- for (const entry of entries) {
64
- if (entry.isFile() && entry.name.startsWith(".")) {
65
- const possibleMarker = entry.name.slice(1).toLowerCase();
66
- if (possibleMarker in SYSTEM_MARKERS ||
67
- routingMaps.lowerCaseMap[possibleMarker] ||
68
- knownFlags.has(possibleMarker)) {
69
- markers.push(possibleMarker);
70
- }
71
- }
72
- }
73
- if (markers.length > 0) {
74
- let relDir = path.relative(sourcePath, dir);
75
- relDir = relDir.split(path.sep).join("/");
76
- directoryMarkers[relDir] = markers;
77
- }
78
- }
79
- return directoryMarkers;
80
- }
81
- function walkSource(dir, sourcePath, listings, callback) {
82
- const entries = listings.get(dir);
83
- if (!entries)
84
- return;
85
- // Rojo expects a specific structure for folders with an init.luau file that we cannot deviate from.
86
- // Because of this, we must return early if an initialization file has been found.
87
- const syncFile = entries.find((e) => e.isFile() && e.name === ".sync");
88
- if (syncFile) {
89
- callback(dir, false, true);
90
- return;
91
- }
92
- const initFile = entries.find((e) => e.isFile() && isInitFile(e.name));
93
- if (initFile) {
94
- callback(path.join(dir, initFile.name), true, false);
95
- return;
96
- }
97
- for (const entry of entries) {
98
- const fullPath = path.join(dir, entry.name);
99
- if (entry.isDirectory()) {
100
- walkSource(fullPath, sourcePath, listings, callback);
101
- }
102
- else if (isValidSource(entry.name)) {
103
- callback(fullPath, false, false);
104
- }
105
- }
106
- }
107
- export async function build(targetConfig, baseProjectTree, config, env, sourcePaths, cliArgs, anchor) {
108
- const modeCopy = { ...targetConfig };
109
- if (cliArgs.output) {
110
- modeCopy.output = path.resolve(process.cwd(), cliArgs.output);
111
- }
112
- else {
113
- modeCopy.output = path.resolve(anchor, targetConfig.output);
114
- }
115
- if (cliArgs.build) {
116
- modeCopy.build = cliArgs.build;
117
- }
118
- const rojoTree = structuredClone(baseProjectTree);
119
- // Pre-populate all routing containers to prevent desync issues
120
- for (const targetService of Object.values(services)) {
121
- let current = rojoTree.tree;
122
- if (serviceParents[targetService]) {
123
- current = getOrCreateNode(current, serviceParents[targetService]);
124
- }
125
- getOrCreateNode(current, targetService);
126
- }
127
- if (config.aliases) {
128
- for (const aliasPath of Object.values(config.aliases)) {
129
- if (typeof aliasPath === "string") {
130
- const parts = aliasPath.split(".");
131
- let current = rojoTree.tree;
132
- // Ensure the parent container is created if needed
133
- if (parts.length > 0 && serviceParents[parts[0]]) {
134
- current = getOrCreateNode(current, serviceParents[parts[0]]);
135
- }
136
- for (const part of parts) {
137
- current = getOrCreateNode(current, part);
138
- }
139
- }
140
- }
141
- }
142
- const mergedTags = { ...(config.tags || {}), ...(modeCopy.tags || {}) };
143
- const knownTags = new Set(Object.keys(config.tags || {}).map((t) => t.toLowerCase()));
144
- const activeTags = new Set(Object.keys(mergedTags)
145
- .filter((t) => mergedTags[t])
146
- .map((t) => t.toLowerCase()));
147
- const cliTags = (cliArgs.tag || []).map((t) => t.toLowerCase());
148
- for (const tag of cliTags) {
149
- activeTags.add(tag);
150
- }
151
- for (const tag of activeTags) {
152
- if (!knownTags.has(tag)) {
153
- throw new Error(`active tag "${tag}" is not declared in the root "tags" object.`);
154
- }
155
- }
156
- const tagRegexes = compileTagRegexes(activeTags);
157
- const context = {
158
- source: config.source || structuredClone(defaultConfig.source),
159
- ...modeCopy,
160
- isTsProject: env.isTsProject || (cliArgs.mode?.includes("ts") ?? false),
161
- emitLegacyScripts: rojoTree.emitLegacyScripts ?? true,
162
- name: rojoTree.name,
163
- routingMaps: generateRoutingMaps(config.aliases || {}),
164
- verbatim: config.verbatim ?? defaultConfig.verbatim,
165
- unwrap: config.unwrap ?? defaultConfig.unwrap,
166
- directoryMarkers: {},
167
- knownTags,
168
- activeTags,
169
- tagRegexes,
170
- };
171
- const combinedGlobIgnorePaths = Array.from(new Set([
172
- ...(rojoTree.globIgnorePaths || []),
173
- ...(config.globIgnorePaths || []),
174
- ...(modeCopy.globIgnorePaths || []),
175
- ]));
176
- const isIgnored = combinedGlobIgnorePaths.length > 0
177
- ? picomatch(combinedGlobIgnorePaths)
178
- : () => false;
179
- const nodeOrigins = new WeakMap();
180
- const collisions = [];
181
- let fileCount = 0;
182
- for (const sourcePath of sourcePaths) {
183
- const relativePath = path.relative(anchor, sourcePath);
184
- const subPath = buildSubPath(relativePath);
185
- const listings = await listTree(sourcePath);
186
- const directoryMarkers = buildDirectoryMarkers(sourcePath, listings, context.routingMaps, knownTags);
187
- const newContext = {
188
- ...context,
189
- build: path.join(context.build, subPath),
190
- directoryMarkers,
191
- };
192
- walkSource(sourcePath, sourcePath, listings, (filepath, isInit, isSync) => {
193
- const relativePath = path.relative(sourcePath, filepath);
194
- const { targetService, wrapperFolder, virtualParts, nodeName, projectPath, dropped, unwrap, } = resolveRoute(relativePath, isInit, newContext, isSync);
195
- if (dropped || (projectPath && isIgnored(toPosix(projectPath))))
196
- return;
197
- fileCount++;
198
- let current = rojoTree.tree;
199
- if (serviceParents[targetService]) {
200
- current = getOrCreateNode(current, serviceParents[targetService]);
201
- }
202
- current = getOrCreateNode(current, targetService);
203
- if (!unwrap) {
204
- current = getOrCreateNode(current, applyCasing(wrapperFolder, config.casing), "Folder");
205
- }
206
- for (const part of virtualParts) {
207
- current = getOrCreateNode(current, part, "Folder");
208
- }
209
- const existingNodeRaw = current[nodeName];
210
- if (existingNodeRaw && existingNodeRaw.$path) {
211
- const origin = nodeOrigins.get(existingNodeRaw);
212
- if (origin && origin.sourcePath === sourcePath) {
213
- collisions.push(`Name collision: "${origin.filepath}" and "${relativePath}" both map to the node "${nodeName}".`);
214
- }
215
- }
216
- const existingNode = existingNodeRaw || {};
217
- const newNode = {
218
- ...existingNode,
219
- $path: { optional: projectPath },
220
- };
221
- if (newNode.$className === "Folder") {
222
- delete newNode.$className;
223
- if (newNode.$ignoreUnknownInstances === false) {
224
- delete newNode.$ignoreUnknownInstances;
225
- }
226
- }
227
- current[nodeName] = newNode;
228
- nodeOrigins.set(newNode, {
229
- sourcePath,
230
- filepath: relativePath,
231
- });
232
- });
233
- }
234
- const outputDir = path.dirname(modeCopy.output);
235
- const removed = [];
236
- const prunedTree = pruneObject(rojoTree.tree, context.build, outputDir, removed);
237
- collapseFolders(prunedTree, context.build, outputDir, isIgnored);
238
- rojoTree.tree = prunedTree;
239
- if (combinedGlobIgnorePaths.length > 0) {
240
- rojoTree.globIgnorePaths = combinedGlobIgnorePaths;
241
- }
242
- const sortedTree = sortObject(rojoTree);
243
- const missingPaths = findMissingPaths(sortedTree.tree, context.build, outputDir);
244
- const exposedDataFiles = findExposedDataFiles(sortedTree.tree);
245
- return {
246
- output: modeCopy.output,
247
- tree: sortedTree,
248
- missingPaths,
249
- removed,
250
- collisions,
251
- exposedDataFiles,
252
- name: context.name,
253
- buildDir: context.build,
254
- fileCount,
255
- };
256
- }
package/dist/cli.js DELETED
@@ -1,115 +0,0 @@
1
- import { parseArgs } from "util";
2
- export function printHelp(logger) {
3
- const helpText = `
4
- Rogen - Feature-based architecture for Roblox
5
-
6
- Usage:
7
- rogen [command] [options]
8
-
9
- Commands:
10
- init Generate a .rogen.json config file
11
- build Build the project
12
- watch Watch the source and build automatically
13
-
14
- Options:
15
- -c, --config <path> Specify custom config file
16
- -p, --project <path> Specify path to Rojo project
17
- -t, --tag <tag> Activate environment tags
18
- -m, --mode <mode> Override modes (luau, ts, darklua, or custom)
19
- -s, --source <path> Override the directory containing uncompiled code
20
- -b, --build <path> Override output directory for transpiled code
21
- -o, --output <path> Override path of Rojo project file
22
- -h, --help Print help
23
- -v, --version Print version
24
- `;
25
- if (logger) {
26
- logger.info(helpText.trim());
27
- }
28
- else {
29
- console.log(helpText.trim());
30
- }
31
- }
32
- export function parseCliArgs(args = process.argv.slice(2), logger) {
33
- const options = {
34
- help: { type: "boolean", short: "h" },
35
- version: { type: "boolean", short: "v" },
36
- init: { type: "boolean", short: "i" },
37
- watch: { type: "boolean", short: "w" },
38
- config: { type: "string", short: "c" },
39
- mode: { type: "string", short: "m", multiple: true },
40
- source: { type: "string", short: "s", multiple: true },
41
- tag: { type: "string", short: "t", multiple: true },
42
- project: { type: "string", short: "p" },
43
- build: { type: "string", short: "b" },
44
- output: { type: "string", short: "o" },
45
- };
46
- try {
47
- const { values, positionals } = parseArgs({
48
- args,
49
- options,
50
- allowPositionals: true,
51
- strict: true,
52
- });
53
- const parsedArgs = values;
54
- if (positionals.length > 0) {
55
- const subcommand = positionals[0].toLowerCase();
56
- if (subcommand === "init") {
57
- parsedArgs.init = true;
58
- }
59
- else if (subcommand === "watch") {
60
- parsedArgs.watch = true;
61
- }
62
- else if (subcommand === "help") {
63
- parsedArgs.help = true;
64
- }
65
- else if (subcommand === "version") {
66
- parsedArgs.version = true;
67
- }
68
- else if (subcommand === "build") {
69
- // build is the default command, so it falls through cleanly
70
- }
71
- else {
72
- const errorMsg = `unknown subcommand "${positionals[0]}".\nRun 'rogen --help' to see a list of available commands.`;
73
- if (logger) {
74
- logger.error(errorMsg);
75
- }
76
- else {
77
- console.error(errorMsg);
78
- }
79
- process.exit(1);
80
- }
81
- }
82
- return parsedArgs;
83
- }
84
- catch (error) {
85
- let errCode;
86
- let errMsg = String(error);
87
- if (typeof error === "object" && error !== null) {
88
- const errObj = error;
89
- errCode = typeof errObj.code === "string" ? errObj.code : undefined;
90
- errMsg =
91
- typeof errObj.message === "string"
92
- ? errObj.message
93
- : String(error);
94
- }
95
- if (errCode === "ERR_PARSE_ARGS_UNKNOWN_OPTION" ||
96
- errMsg.includes("Unknown option")) {
97
- const cleanMsg = errMsg.replace(/^TypeError \[ERR_PARSE_ARGS_UNKNOWN_OPTION\]:\s*/, "");
98
- const errorMsg = `${cleanMsg}\nRun 'rogen --help' to see a list of available commands and options.`;
99
- if (logger) {
100
- logger.error(errorMsg);
101
- }
102
- else {
103
- console.error(errorMsg);
104
- }
105
- process.exit(1);
106
- }
107
- if (logger) {
108
- logger.error(errMsg);
109
- }
110
- else {
111
- console.error(errMsg);
112
- }
113
- process.exit(1);
114
- }
115
- }