@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/index.js ADDED
@@ -0,0 +1,110 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import chokidar from "chokidar";
4
+ import { printHelp, parseCliArgs } from "./cli.js";
5
+ import { resolveConfigPath, loadConfig, getEnvironment, resolveActiveModes, createFallbackConfig, } from "./config.js";
6
+ import { execute } from "./execute.js";
7
+ import { version } from "./constants.js";
8
+ import { ConsoleLogger } from "./logger.js";
9
+ async function main() {
10
+ const logger = new ConsoleLogger();
11
+ const cliArgs = parseCliArgs(process.argv.slice(2), logger);
12
+ if (cliArgs.help) {
13
+ printHelp(logger);
14
+ process.exit(0);
15
+ }
16
+ if (cliArgs.version) {
17
+ logger.info(`rogen ${version}`);
18
+ process.exit(0);
19
+ }
20
+ if (cliArgs.init) {
21
+ const cwd = process.cwd();
22
+ const targetPath = path.resolve(cwd, ".rogen.json");
23
+ if (fs.existsSync(targetPath)) {
24
+ logger.error(`a .rogen.json file already exists in this directory.`);
25
+ process.exit(1);
26
+ }
27
+ const config = createFallbackConfig(cwd);
28
+ const isTs = fs.existsSync(path.join(cwd, "tsconfig.json"));
29
+ const isDarklua = fs.existsSync(path.join(cwd, ".darklua.json")) ||
30
+ fs.existsSync(path.join(cwd, ".darklua.json5"));
31
+ if (isTs) {
32
+ delete config.luau;
33
+ }
34
+ else {
35
+ delete config.ts;
36
+ }
37
+ if (!isDarklua) {
38
+ delete config.darklua;
39
+ }
40
+ for (const mode of ["luau", "ts", "darklua"]) {
41
+ if (config[mode]) {
42
+ delete config[mode].tags;
43
+ delete config[mode].globIgnorePaths;
44
+ }
45
+ }
46
+ delete config.globIgnorePaths;
47
+ delete config.tags;
48
+ delete config.aliases;
49
+ delete config.verbatim;
50
+ delete config.casing;
51
+ delete config.unwrap;
52
+ fs.writeFileSync(targetPath, JSON.stringify(config, null, "\t"));
53
+ logger.success(`Created .rogen.json in the current directory.`);
54
+ process.exit(0);
55
+ }
56
+ const configPath = resolveConfigPath(cliArgs.config, logger);
57
+ const { config, anchor } = loadConfig(configPath, cliArgs.project);
58
+ const rawSources = cliArgs.source || config.source;
59
+ const sourceDirs = Array.isArray(rawSources) ? rawSources : [rawSources];
60
+ const resolveBase = cliArgs.source ? process.cwd() : anchor;
61
+ const sourcePaths = sourceDirs.map((s) => {
62
+ const sourcePath = path.resolve(resolveBase, s);
63
+ if (!fs.existsSync(sourcePath)) {
64
+ throw new Error(`Source directory not found: ${sourcePath}`);
65
+ }
66
+ return sourcePath;
67
+ });
68
+ const env = getEnvironment(anchor, cliArgs.mode);
69
+ const activeModes = resolveActiveModes(config, cliArgs.mode, env);
70
+ const success = await execute(sourcePaths, env, activeModes, config.project, config, cliArgs, anchor, logger);
71
+ if (!success) {
72
+ process.exit(1);
73
+ }
74
+ if (cliArgs.watch) {
75
+ logger.info(`Watching for file changes in: "${sourceDirs.join(", ")}" (Ctrl+C to stop)`);
76
+ const watcher = chokidar.watch(sourcePaths, {
77
+ persistent: true,
78
+ ignoreInitial: true,
79
+ });
80
+ let debounceTimeout;
81
+ let isBuilding = false;
82
+ watcher.on("all", (event, filePath) => {
83
+ logger.trace(`File change detected (${event}): ${filePath}`);
84
+ clearTimeout(debounceTimeout);
85
+ debounceTimeout = setTimeout(() => {
86
+ if (isBuilding)
87
+ return;
88
+ isBuilding = true;
89
+ try {
90
+ execute(sourcePaths, env, activeModes, config.project, config, cliArgs, anchor, logger);
91
+ }
92
+ catch (err) {
93
+ logger.error(err instanceof Error ? err : String(err));
94
+ }
95
+ finally {
96
+ isBuilding = false;
97
+ }
98
+ }, 100);
99
+ });
100
+ watcher.on("error", (error) => logger.error(`${error}`));
101
+ await new Promise(() => { }); // Keep alive
102
+ }
103
+ }
104
+ export default function run() {
105
+ const logger = new ConsoleLogger();
106
+ main().catch((error) => {
107
+ logger.error(`${error.message}`);
108
+ process.exit(1);
109
+ });
110
+ }
package/dist/logger.js ADDED
@@ -0,0 +1,104 @@
1
+ export var LogLevel;
2
+ (function (LogLevel) {
3
+ LogLevel[LogLevel["Off"] = 0] = "Off";
4
+ LogLevel[LogLevel["Error"] = 1] = "Error";
5
+ LogLevel[LogLevel["Warn"] = 2] = "Warn";
6
+ LogLevel[LogLevel["Success"] = 3] = "Success";
7
+ LogLevel[LogLevel["Info"] = 4] = "Info";
8
+ LogLevel[LogLevel["Debug"] = 5] = "Debug";
9
+ LogLevel[LogLevel["Trace"] = 6] = "Trace";
10
+ })(LogLevel || (LogLevel = {}));
11
+ export class AbstractLogger {
12
+ level = LogLevel.Info;
13
+ setLevel(level) {
14
+ this.level = level;
15
+ }
16
+ getLevel() {
17
+ return this.level;
18
+ }
19
+ canLog(level) {
20
+ return this.level !== LogLevel.Off && this.level >= level;
21
+ }
22
+ format(message, args) {
23
+ let result = message instanceof Error
24
+ ? message.stack || message.message
25
+ : message;
26
+ for (const arg of args) {
27
+ if (arg instanceof Error) {
28
+ result += " " + (arg.stack || arg.message);
29
+ }
30
+ else if (typeof arg === "object") {
31
+ try {
32
+ result += " " + JSON.stringify(arg);
33
+ }
34
+ catch {
35
+ result += " [Unserializable Object]";
36
+ }
37
+ }
38
+ else {
39
+ result += " " + arg;
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ }
45
+ export function getTimeStamp(wrapInBrackets = true) {
46
+ const now = new Date();
47
+ const h = String(now.getHours()).padStart(2, "0");
48
+ const m = String(now.getMinutes()).padStart(2, "0");
49
+ const s = String(now.getSeconds()).padStart(2, "0");
50
+ const timeStamp = `${h}:${m}:${s}`;
51
+ return wrapInBrackets ? `[${timeStamp}]` : timeStamp;
52
+ }
53
+ export class ConsoleLogger extends AbstractLogger {
54
+ colors = {
55
+ reset: "\x1b[0m",
56
+ red: "\x1b[31m",
57
+ green: "\x1b[32m",
58
+ yellow: "\x1b[33m",
59
+ gray: "\x1b[90m",
60
+ cyan: "\x1b[36m",
61
+ };
62
+ error(message, ...args) {
63
+ if (this.canLog(LogLevel.Error)) {
64
+ console.error(`${this.colors.red}error:${this.colors.reset} ${this.format(message, args)}`);
65
+ }
66
+ }
67
+ warn(message, ...args) {
68
+ if (this.canLog(LogLevel.Warn)) {
69
+ console.warn(`${this.colors.yellow}warning:${this.colors.reset} ${this.format(message, args)}`);
70
+ }
71
+ }
72
+ success(message, ...args) {
73
+ if (this.canLog(LogLevel.Info)) {
74
+ console.info(`${getTimeStamp()} ${this.colors.green}success:${this.colors.reset} ${this.format(message, args)}`);
75
+ }
76
+ }
77
+ info(message, ...args) {
78
+ if (this.canLog(LogLevel.Info)) {
79
+ console.info(this.format(message, args));
80
+ }
81
+ }
82
+ debug(message, ...args) {
83
+ if (this.canLog(LogLevel.Debug)) {
84
+ console.debug(`${this.colors.gray}[debug] ${this.format(message, args)}${this.colors.reset}`);
85
+ }
86
+ }
87
+ trace(message, ...args) {
88
+ if (this.canLog(LogLevel.Trace)) {
89
+ console.debug(`${this.colors.cyan}[trace] ${this.format(message, args)}${this.colors.reset}`);
90
+ }
91
+ }
92
+ }
93
+ export class NullLogger {
94
+ setLevel(_level) { }
95
+ getLevel() {
96
+ return LogLevel.Off;
97
+ }
98
+ error(_message, ..._args) { }
99
+ warn(_message, ..._args) { }
100
+ success(_message, ..._args) { }
101
+ info(_message, ..._args) { }
102
+ debug(_message, ..._args) { }
103
+ trace(_message, ..._args) { }
104
+ }
package/dist/route.js ADDED
@@ -0,0 +1,345 @@
1
+ import path from "path";
2
+ import { serviceAliases, serverContainers, clientContainers, } from "./constants.js";
3
+ import { toPosix } from "./tree.js";
4
+ function resolveFolderRouting(parts, context) {
5
+ const { routingMaps, directoryMarkers, activeTags } = context;
6
+ const { lowerCaseMap } = routingMaps;
7
+ const virtualParts = [];
8
+ let targetService = "ReplicatedStorage";
9
+ let lastRouteKeyword = null;
10
+ let tagKeyword = null;
11
+ const flags = {
12
+ structure: false,
13
+ verbatim: false,
14
+ unwrap: false,
15
+ };
16
+ // Marker routing
17
+ const rootMarkers = directoryMarkers[""];
18
+ if (rootMarkers) {
19
+ if (rootMarkers.includes("structure")) {
20
+ flags.structure = true;
21
+ }
22
+ if (rootMarkers.includes("verbatim")) {
23
+ flags.verbatim = true;
24
+ }
25
+ if (rootMarkers.includes("unwrap")) {
26
+ flags.unwrap = true;
27
+ }
28
+ if (!flags.structure) {
29
+ const routingMarker = rootMarkers.find((m) => lowerCaseMap[m]);
30
+ if (routingMarker) {
31
+ targetService = lowerCaseMap[routingMarker];
32
+ lastRouteKeyword = routingMarker;
33
+ if (serviceAliases.has(routingMarker))
34
+ tagKeyword = routingMarker;
35
+ }
36
+ }
37
+ }
38
+ // Folder path routing
39
+ let currentPath = "";
40
+ for (const part of parts) {
41
+ currentPath = currentPath ? `${currentPath}/${part}` : part;
42
+ let lowerPart = part.toLowerCase();
43
+ let isInvisible = false;
44
+ if (lowerPart.startsWith("(") && lowerPart.endsWith(")")) {
45
+ isInvisible = true;
46
+ lowerPart = lowerPart.slice(1, -1);
47
+ }
48
+ const markers = directoryMarkers
49
+ ? directoryMarkers[currentPath]
50
+ : undefined;
51
+ if (markers) {
52
+ if (markers.includes("structure")) {
53
+ flags.structure = true;
54
+ }
55
+ if (markers.includes("verbatim")) {
56
+ flags.verbatim = true;
57
+ }
58
+ if (markers.includes("unwrap")) {
59
+ flags.unwrap = true;
60
+ }
61
+ }
62
+ if (flags.structure) {
63
+ if (!activeTags.has(lowerPart)) {
64
+ virtualParts.push(part);
65
+ }
66
+ continue;
67
+ }
68
+ const matchedService = lowerCaseMap[lowerPart];
69
+ if (markers) {
70
+ const routingMarker = markers.find((m) => lowerCaseMap[m]);
71
+ if (routingMarker) {
72
+ targetService = lowerCaseMap[routingMarker];
73
+ lastRouteKeyword = routingMarker;
74
+ if (serviceAliases.has(routingMarker))
75
+ tagKeyword = routingMarker;
76
+ if (!matchedService &&
77
+ !activeTags.has(lowerPart) &&
78
+ !isInvisible) {
79
+ virtualParts.push(part);
80
+ }
81
+ continue;
82
+ }
83
+ }
84
+ if (matchedService) {
85
+ targetService = matchedService;
86
+ lastRouteKeyword = lowerPart;
87
+ if (serviceAliases.has(lowerPart)) {
88
+ tagKeyword = lowerPart;
89
+ }
90
+ }
91
+ else {
92
+ if (!activeTags.has(lowerPart) && !isInvisible) {
93
+ virtualParts.push(part);
94
+ }
95
+ }
96
+ }
97
+ return {
98
+ targetService,
99
+ virtualParts,
100
+ lastRouteKeyword,
101
+ tagKeyword,
102
+ flags,
103
+ };
104
+ }
105
+ function resolveAffixes(basename, isInit, routingMaps) {
106
+ const { lowerCaseMap, mergedServices, separatorSuffixRegex, pascalCaseSuffixRegex, separatorPrefixRegex, camelCasePrefixRegex, } = routingMaps;
107
+ let match = basename.match(separatorSuffixRegex);
108
+ if (match && match[0].length < basename.length) {
109
+ const suffix = match[1].toLowerCase();
110
+ return {
111
+ mappedService: lowerCaseMap[suffix],
112
+ matchedLength: match[0].length,
113
+ exactMatch: match[0],
114
+ tagKeyword: !isInit && serviceAliases.has(suffix) ? suffix : undefined,
115
+ isPrefix: false,
116
+ };
117
+ }
118
+ match = basename.match(pascalCaseSuffixRegex);
119
+ if (match && match[0].length < basename.length) {
120
+ const suffix = match[1].toLowerCase();
121
+ return {
122
+ mappedService: mergedServices[match[1]],
123
+ matchedLength: match[0].length,
124
+ exactMatch: match[0],
125
+ tagKeyword: !isInit && serviceAliases.has(suffix) ? suffix : undefined,
126
+ isPrefix: false,
127
+ };
128
+ }
129
+ match = basename.match(separatorPrefixRegex);
130
+ if (match && match[0].length < basename.length) {
131
+ const prefix = match[1].toLowerCase();
132
+ return {
133
+ mappedService: lowerCaseMap[prefix],
134
+ matchedLength: match[0].length,
135
+ exactMatch: match[0],
136
+ tagKeyword: !isInit && serviceAliases.has(prefix) ? prefix : undefined,
137
+ isPrefix: true,
138
+ };
139
+ }
140
+ match = basename.match(camelCasePrefixRegex);
141
+ if (match && match[0].length < basename.length) {
142
+ const prefix = match[1].toLowerCase();
143
+ return {
144
+ mappedService: lowerCaseMap[prefix],
145
+ matchedLength: match[1].length,
146
+ exactMatch: match[0],
147
+ tagKeyword: !isInit && serviceAliases.has(prefix) ? prefix : undefined,
148
+ isPrefix: true,
149
+ };
150
+ }
151
+ return null;
152
+ }
153
+ function getWrapperFolder(targetService, tagKeyword) {
154
+ if (serverContainers.has(targetService))
155
+ return "server";
156
+ if (clientContainers.has(targetService))
157
+ return "client";
158
+ if (tagKeyword)
159
+ return tagKeyword;
160
+ return "shared";
161
+ }
162
+ export function resolveRoute(relativePath, isInit, context, isSync = false) {
163
+ const { emitLegacyScripts, isTsProject, build, routingMaps, verbatim, knownTags, activeTags, tagRegexes, directoryMarkers, } = context;
164
+ const parts = relativePath ? relativePath.split(/[\\/]/) : [];
165
+ let filename;
166
+ let rawBasename;
167
+ if (isSync) {
168
+ filename = parts.length > 0 ? parts[parts.length - 1] : "";
169
+ rawBasename = filename;
170
+ }
171
+ else {
172
+ filename = parts.length > 0 ? parts.pop() : "";
173
+ rawBasename = filename
174
+ ? path.basename(filename, path.extname(filename))
175
+ : "";
176
+ }
177
+ // Detect and strip the hoisting prefix
178
+ let isHoisted = false;
179
+ let cleanBasename = rawBasename;
180
+ if (cleanBasename.startsWith("^")) {
181
+ isHoisted = true;
182
+ cleanBasename = cleanBasename.slice(1);
183
+ }
184
+ let dropped = false;
185
+ // Check folder paths and folder marker files
186
+ let currentRel = "";
187
+ for (const part of parts) {
188
+ currentRel = currentRel ? `${currentRel}/${part}` : part;
189
+ let lowerPart = part.toLowerCase();
190
+ // Strip invisible folder syntax for drop checks
191
+ if (lowerPart.startsWith("(") && lowerPart.endsWith(")")) {
192
+ lowerPart = lowerPart.slice(1, -1);
193
+ }
194
+ // Drop if folder name is an inactive env
195
+ if (knownTags.has(lowerPart) && !activeTags.has(lowerPart)) {
196
+ dropped = true;
197
+ break;
198
+ }
199
+ // Drop if folder contains an inactive env marker file
200
+ const markers = directoryMarkers?.[currentRel];
201
+ if (markers) {
202
+ for (const m of markers) {
203
+ if (knownTags.has(m) && !activeTags.has(m)) {
204
+ dropped = true;
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ if (dropped)
210
+ break;
211
+ }
212
+ // Check root directory markers for drops
213
+ const rootMarkers = directoryMarkers?.[""];
214
+ if (!dropped && rootMarkers) {
215
+ for (const m of rootMarkers) {
216
+ if (knownTags.has(m) && !activeTags.has(m)) {
217
+ dropped = true;
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ // Check file affixes
223
+ if (!dropped && !isSync && cleanBasename) {
224
+ const delimiterSplit = cleanBasename.split(/[.\-_+]/);
225
+ for (const part of delimiterSplit) {
226
+ const lowerPart = part.toLowerCase();
227
+ if (knownTags.has(lowerPart) && !activeTags.has(lowerPart)) {
228
+ dropped = true;
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ // Stop processing and flag for removal
234
+ if (dropped) {
235
+ return {
236
+ targetService: "",
237
+ wrapperFolder: "",
238
+ virtualParts: [],
239
+ nodeName: "",
240
+ projectPath: "",
241
+ dropped: true,
242
+ unwrap: false,
243
+ };
244
+ }
245
+ // Strip active environment affixes
246
+ let basename = cleanBasename;
247
+ if (!isSync) {
248
+ for (const regexSet of tagRegexes) {
249
+ while (regexSet.suffix.test(basename)) {
250
+ basename = basename.replace(regexSet.suffix, "");
251
+ }
252
+ while (regexSet.prefix.test(basename)) {
253
+ basename = basename.replace(regexSet.prefix, "");
254
+ }
255
+ while (regexSet.middle.test(basename)) {
256
+ basename = basename.replace(regexSet.middle, "");
257
+ }
258
+ }
259
+ }
260
+ // Folder and marker routing
261
+ const { targetService: folderTarget, virtualParts, lastRouteKeyword, tagKeyword: folderFlag, flags, } = resolveFolderRouting(parts, context);
262
+ // Apply hoisting
263
+ if (isHoisted) {
264
+ virtualParts.length = 0;
265
+ }
266
+ // Affix routing
267
+ const affix = flags.structure || isSync
268
+ ? null
269
+ : resolveAffixes(basename, isInit, routingMaps);
270
+ // Resolve overrides
271
+ let targetService = affix?.mappedService ?? folderTarget;
272
+ const tagKeyword = affix?.tagKeyword ?? folderFlag;
273
+ // Resolve namespace wrapper folder
274
+ const wrapperFolder = getWrapperFolder(targetService, tagKeyword);
275
+ // Determine if the wrapper folder should be skipped
276
+ const unwrap = context.unwrap || flags.unwrap;
277
+ // Edge case: Scripts with non-legacy RunContext run incorrectly in StarterPlayer container,
278
+ // hence they need to be put in ReplicatedStorage.
279
+ const isStarterPlayerContainer = targetService === "StarterPlayerScripts" ||
280
+ targetService === "StarterCharacterScripts";
281
+ if (emitLegacyScripts === false && isStarterPlayerContainer) {
282
+ targetService = "ReplicatedStorage";
283
+ }
284
+ // Node naming and path formatting
285
+ let nodeName = basename;
286
+ let projectPath;
287
+ if (isSync) {
288
+ projectPath = toPosix(path.join(build, relativePath));
289
+ if (virtualParts.length > 0) {
290
+ nodeName = virtualParts.pop();
291
+ }
292
+ else {
293
+ nodeName = lastRouteKeyword
294
+ ? lastRouteKeyword
295
+ : filename || "source";
296
+ }
297
+ }
298
+ else if (isInit) {
299
+ const folderRelativePath = path.dirname(relativePath);
300
+ projectPath = toPosix(path.join(build, folderRelativePath));
301
+ if (virtualParts.length > 0) {
302
+ nodeName = virtualParts.pop();
303
+ }
304
+ else {
305
+ nodeName = lastRouteKeyword ? lastRouteKeyword : "source";
306
+ }
307
+ }
308
+ else {
309
+ let compiledRelativePath = relativePath;
310
+ if (isTsProject) {
311
+ const compiledFilename = filename.replace(/\.tsx?$/i, ".luau");
312
+ compiledRelativePath = path.join(path.dirname(relativePath), compiledFilename);
313
+ }
314
+ projectPath = toPosix(path.join(build, compiledRelativePath));
315
+ if (affix) {
316
+ const keepFullNames = verbatim || flags.verbatim;
317
+ let shouldStrip = !keepFullNames;
318
+ // Rojo relies on '.server' and '.client' explicitly for script types.
319
+ // Even if verbatim is true, we must strip these exact dot-prefixes.
320
+ if (keepFullNames) {
321
+ const exactMatch = affix.exactMatch.toLowerCase();
322
+ if (exactMatch === ".server" || exactMatch === ".client") {
323
+ shouldStrip = true;
324
+ }
325
+ }
326
+ if (shouldStrip) {
327
+ if (affix.isPrefix) {
328
+ nodeName = basename.slice(affix.matchedLength);
329
+ }
330
+ else {
331
+ nodeName = basename.slice(0, -affix.matchedLength);
332
+ }
333
+ }
334
+ }
335
+ }
336
+ return {
337
+ targetService,
338
+ wrapperFolder,
339
+ virtualParts,
340
+ nodeName,
341
+ projectPath,
342
+ dropped: false,
343
+ unwrap,
344
+ };
345
+ }