@ldgerrits/rogen 1.4.4 → 2.0.0-beta.2
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/LICENSE.md +6 -6
- package/README.md +109 -33
- package/dist/bundle.cjs +30204 -3895
- package/package.json +103 -66
- package/dist/build.js +0 -256
- package/dist/cli.js +0 -115
- package/dist/config.js +0 -349
- package/dist/constants.js +0 -100
- package/dist/execute.js +0 -128
- package/dist/index.js +0 -115
- package/dist/logger.js +0 -104
- package/dist/route.js +0 -345
- package/dist/tree.js +0 -223
- package/dist/types.js +0 -1
package/dist/config.js
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { defaultConfig, defaultProject } from "./constants.js";
|
|
4
|
-
const CONFIG_KEYS_MAP = {
|
|
5
|
-
source: true,
|
|
6
|
-
tags: true,
|
|
7
|
-
verbatim: true,
|
|
8
|
-
casing: true,
|
|
9
|
-
unwrap: true,
|
|
10
|
-
aliases: true,
|
|
11
|
-
globIgnorePaths: true,
|
|
12
|
-
luau: true,
|
|
13
|
-
ts: true,
|
|
14
|
-
darklua: true,
|
|
15
|
-
project: true,
|
|
16
|
-
};
|
|
17
|
-
const KEYS = Object.keys(CONFIG_KEYS_MAP);
|
|
18
|
-
const LEGACY_KEYS = {
|
|
19
|
-
keepRouteNames: "verbatim",
|
|
20
|
-
keepSuffixes: "verbatim",
|
|
21
|
-
template: "project",
|
|
22
|
-
};
|
|
23
|
-
function getClosestMatch(input, targets, distanceThreshold) {
|
|
24
|
-
let closest = null;
|
|
25
|
-
let minDistance = Infinity;
|
|
26
|
-
for (const target of targets) {
|
|
27
|
-
const matrix = Array.from({ length: input.length + 1 }, () => Array(target.length + 1).fill(0));
|
|
28
|
-
for (let i = 0; i <= input.length; i++)
|
|
29
|
-
matrix[i][0] = i;
|
|
30
|
-
for (let j = 0; j <= target.length; j++)
|
|
31
|
-
matrix[0][j] = j;
|
|
32
|
-
for (let i = 1; i <= input.length; i++) {
|
|
33
|
-
for (let j = 1; j <= target.length; j++) {
|
|
34
|
-
const cost = input[i - 1] === target[j - 1] ? 0 : 1;
|
|
35
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
|
|
36
|
-
matrix[i][j - 1] + 1, // insertion
|
|
37
|
-
matrix[i - 1][j - 1] + cost // substitution
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const distance = matrix[input.length][target.length];
|
|
42
|
-
if (distance < minDistance) {
|
|
43
|
-
minDistance = distance;
|
|
44
|
-
closest = target;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return minDistance <= distanceThreshold ? closest : null;
|
|
48
|
-
}
|
|
49
|
-
function isValidRojoTree(value) {
|
|
50
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
const record = value;
|
|
54
|
-
return (typeof record.tree === "object" &&
|
|
55
|
-
record.tree !== null &&
|
|
56
|
-
!Array.isArray(record.tree));
|
|
57
|
-
}
|
|
58
|
-
export function isMode(value) {
|
|
59
|
-
return (typeof value === "object" &&
|
|
60
|
-
value !== null &&
|
|
61
|
-
!Array.isArray(value) &&
|
|
62
|
-
typeof value.output === "string" &&
|
|
63
|
-
typeof value.build === "string");
|
|
64
|
-
}
|
|
65
|
-
export function resolveConfigPath(customPathArg, logger) {
|
|
66
|
-
const cwd = process.cwd();
|
|
67
|
-
if (customPathArg) {
|
|
68
|
-
const resolvedPath = path.resolve(cwd, customPathArg);
|
|
69
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
70
|
-
throw new Error(`specified config file not found: ${customPathArg}`);
|
|
71
|
-
}
|
|
72
|
-
return resolvedPath;
|
|
73
|
-
}
|
|
74
|
-
const defaultPath = path.resolve(cwd, ".rogen.json");
|
|
75
|
-
if (fs.existsSync(defaultPath)) {
|
|
76
|
-
return defaultPath;
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
const directoryFiles = fs.readdirSync(cwd);
|
|
80
|
-
const matchedConfig = directoryFiles.find((file) => file.endsWith(".rogen.json"));
|
|
81
|
-
if (matchedConfig) {
|
|
82
|
-
return path.resolve(cwd, matchedConfig);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
const msg = error instanceof Error ? error.message : "Unknown Error";
|
|
87
|
-
if (logger) {
|
|
88
|
-
logger.error(`failed to scan directory for .rogen.json - ${msg}`);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
console.error(`failed to scan directory for .rogen.json - ${msg}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
export function createFallbackConfig(cwd) {
|
|
97
|
-
const isTs = fs.existsSync(path.join(cwd, "tsconfig.json"));
|
|
98
|
-
const isWally = fs.existsSync(path.join(cwd, "wally.toml"));
|
|
99
|
-
const isPesde = fs.existsSync(path.join(cwd, "pesde.toml"));
|
|
100
|
-
const tree = structuredClone(defaultProject.tree);
|
|
101
|
-
const project = {
|
|
102
|
-
name: path.basename(cwd) || defaultProject.name,
|
|
103
|
-
tree: tree,
|
|
104
|
-
};
|
|
105
|
-
if (isTs) {
|
|
106
|
-
project.globIgnorePaths = ["**/package.json", "**/tsconfig.json"];
|
|
107
|
-
const hasRbxts = fs.existsSync(path.join(cwd, "node_modules", "@rbxts"));
|
|
108
|
-
const hasFlamework = fs.existsSync(path.join(cwd, "node_modules", "@flamework"));
|
|
109
|
-
const hasRbxtsJs = fs.existsSync(path.join(cwd, "node_modules", "@rbxts-js"));
|
|
110
|
-
const rbxtsIncludeNode = { $path: "include" };
|
|
111
|
-
if (hasRbxts || hasFlamework || hasRbxtsJs) {
|
|
112
|
-
const nodeModulesNode = { $className: "Folder" };
|
|
113
|
-
if (hasRbxts)
|
|
114
|
-
nodeModulesNode["@rbxts"] = { $path: "node_modules/@rbxts" };
|
|
115
|
-
if (hasFlamework)
|
|
116
|
-
nodeModulesNode["@flamework"] = {
|
|
117
|
-
$path: "node_modules/@flamework",
|
|
118
|
-
};
|
|
119
|
-
if (hasRbxtsJs)
|
|
120
|
-
nodeModulesNode["@rbxts-js"] = {
|
|
121
|
-
$path: "node_modules/@rbxts-js",
|
|
122
|
-
};
|
|
123
|
-
rbxtsIncludeNode.node_modules = nodeModulesNode;
|
|
124
|
-
}
|
|
125
|
-
tree.ReplicatedStorage = {
|
|
126
|
-
...(tree.ReplicatedStorage || {}),
|
|
127
|
-
rbxts_include: rbxtsIncludeNode,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
if (isWally) {
|
|
131
|
-
if (fs.existsSync(path.join(cwd, "Packages"))) {
|
|
132
|
-
tree.ReplicatedStorage = {
|
|
133
|
-
...(tree.ReplicatedStorage || {}),
|
|
134
|
-
Packages: { $path: "Packages" },
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
if (fs.existsSync(path.join(cwd, "ServerPackages"))) {
|
|
138
|
-
tree.ServerScriptService = {
|
|
139
|
-
...(tree.ServerScriptService || {}),
|
|
140
|
-
ServerPackages: { $path: "ServerPackages" },
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (isPesde) {
|
|
145
|
-
if (fs.existsSync(path.join(cwd, "roblox_packages"))) {
|
|
146
|
-
tree.ReplicatedStorage = {
|
|
147
|
-
...(tree.ReplicatedStorage || {}),
|
|
148
|
-
Packages: { $path: "roblox_packages" },
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
if (fs.existsSync(path.join(cwd, "roblox_server_packages"))) {
|
|
152
|
-
tree.ServerScriptService = {
|
|
153
|
-
...(tree.ServerScriptService || {}),
|
|
154
|
-
ServerPackages: { $path: "roblox_server_packages" },
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const config = {
|
|
159
|
-
...structuredClone(defaultConfig),
|
|
160
|
-
project: project,
|
|
161
|
-
};
|
|
162
|
-
return config;
|
|
163
|
-
}
|
|
164
|
-
export function loadConfig(configPath, projectPathArg) {
|
|
165
|
-
const anchor = configPath ? path.dirname(configPath) : process.cwd();
|
|
166
|
-
const config = createFallbackConfig(anchor);
|
|
167
|
-
if (configPath) {
|
|
168
|
-
const rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
169
|
-
for (const key in rawConfig) {
|
|
170
|
-
if (!KEYS.includes(key)) {
|
|
171
|
-
if (key in LEGACY_KEYS) {
|
|
172
|
-
throw new Error(`key "${key}" has been renamed to "${LEGACY_KEYS[key]}". Please, update your configuration.`);
|
|
173
|
-
}
|
|
174
|
-
const value = rawConfig[key];
|
|
175
|
-
const isObject = typeof value === "object" &&
|
|
176
|
-
value !== null &&
|
|
177
|
-
!Array.isArray(value);
|
|
178
|
-
const modeData = isObject
|
|
179
|
-
? value
|
|
180
|
-
: null;
|
|
181
|
-
// Default modes merging
|
|
182
|
-
if (isObject &&
|
|
183
|
-
(key === "luau" || key === "ts" || key === "darklua")) {
|
|
184
|
-
config[key] = {
|
|
185
|
-
...config[key],
|
|
186
|
-
...modeData,
|
|
187
|
-
};
|
|
188
|
-
config[key].tags =
|
|
189
|
-
modeData &&
|
|
190
|
-
typeof modeData.tags === "object" &&
|
|
191
|
-
modeData.tags !== null &&
|
|
192
|
-
!Array.isArray(modeData.tags)
|
|
193
|
-
? modeData.tags
|
|
194
|
-
: {};
|
|
195
|
-
config[key].globIgnorePaths = Array.isArray(config[key].globIgnorePaths)
|
|
196
|
-
? config[key].globIgnorePaths
|
|
197
|
-
: [];
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
// Custom modes merging
|
|
201
|
-
const intendedAsMode = modeData &&
|
|
202
|
-
("output" in modeData ||
|
|
203
|
-
"build" in modeData ||
|
|
204
|
-
"tags" in modeData ||
|
|
205
|
-
"globIgnorePaths" in modeData);
|
|
206
|
-
if (intendedAsMode) {
|
|
207
|
-
if (typeof modeData.output !== "string") {
|
|
208
|
-
throw new Error(`custom mode "${key}" is missing a valid "output" string.`);
|
|
209
|
-
}
|
|
210
|
-
if (typeof modeData.build !== "string") {
|
|
211
|
-
throw new Error(`custom mode "${key}" is missing a valid "build" string.`);
|
|
212
|
-
}
|
|
213
|
-
config[key] = {
|
|
214
|
-
output: modeData.output,
|
|
215
|
-
build: modeData.build,
|
|
216
|
-
tags: typeof modeData.tags === "object" &&
|
|
217
|
-
modeData.tags !== null &&
|
|
218
|
-
!Array.isArray(modeData.tags)
|
|
219
|
-
? modeData.tags
|
|
220
|
-
: {},
|
|
221
|
-
globIgnorePaths: Array.isArray(modeData.globIgnorePaths)
|
|
222
|
-
? modeData.globIgnorePaths
|
|
223
|
-
: [],
|
|
224
|
-
};
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
const closestMatch = getClosestMatch(key, KEYS, 2);
|
|
228
|
-
if (closestMatch) {
|
|
229
|
-
throw new Error(`unknown key "${key}". Did you mean "${closestMatch}"?`);
|
|
230
|
-
}
|
|
231
|
-
throw new Error(`unknown configuration key "${key}".`);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
// Validate keys
|
|
235
|
-
if (key === "source" &&
|
|
236
|
-
typeof rawConfig[key] !== "string" &&
|
|
237
|
-
!Array.isArray(rawConfig[key])) {
|
|
238
|
-
throw new Error(`'source' must be a string or an array of strings.`);
|
|
239
|
-
}
|
|
240
|
-
else if (key === "project" &&
|
|
241
|
-
typeof rawConfig[key] !== "object" &&
|
|
242
|
-
typeof rawConfig[key] !== "string") {
|
|
243
|
-
throw new Error(`'project' must be an inline object or a string path to a JSON file.`);
|
|
244
|
-
}
|
|
245
|
-
else if ((key === "verbatim" || key === "unwrap") &&
|
|
246
|
-
typeof rawConfig[key] !== "boolean") {
|
|
247
|
-
throw new Error(`'${key}' must be a boolean.`);
|
|
248
|
-
}
|
|
249
|
-
else if (key === "casing") {
|
|
250
|
-
if (typeof rawConfig[key] !== "string") {
|
|
251
|
-
throw new Error(`'casing' must be a string.`);
|
|
252
|
-
}
|
|
253
|
-
const casingVal = rawConfig[key]
|
|
254
|
-
.toLowerCase()
|
|
255
|
-
.replace(/[-_\s]/g, "");
|
|
256
|
-
if (casingVal === "pascal" || casingVal === "pascalcase") {
|
|
257
|
-
config.casing = "PascalCase";
|
|
258
|
-
}
|
|
259
|
-
else if (casingVal === "camel" ||
|
|
260
|
-
casingVal === "camelcase") {
|
|
261
|
-
config.casing = "camelCase";
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
throw new Error(`'casing' must be either "PascalCase" or "camelCase".`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
else if (key === "globIgnorePaths" &&
|
|
268
|
-
!Array.isArray(rawConfig[key])) {
|
|
269
|
-
throw new Error(`'${key}' must be an array of strings.`);
|
|
270
|
-
}
|
|
271
|
-
else if (key === "tags") {
|
|
272
|
-
if (typeof rawConfig[key] !== "object" ||
|
|
273
|
-
rawConfig[key] === null ||
|
|
274
|
-
Array.isArray(rawConfig[key])) {
|
|
275
|
-
throw new Error(`'tags' must be a key-value object of booleans.`);
|
|
276
|
-
}
|
|
277
|
-
for (const tagKey in rawConfig[key]) {
|
|
278
|
-
if (typeof rawConfig[key][tagKey] !== "boolean") {
|
|
279
|
-
throw new Error(`value for tag "${tagKey}" must be a boolean.`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
config[key] = rawConfig[key];
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// Project handling
|
|
288
|
-
if (projectPathArg) {
|
|
289
|
-
const projectPath = path.resolve(process.cwd(), projectPathArg);
|
|
290
|
-
if (!fs.existsSync(projectPath)) {
|
|
291
|
-
throw new Error(`specified project file not found: ${projectPath}`);
|
|
292
|
-
}
|
|
293
|
-
config.project = JSON.parse(fs.readFileSync(projectPath, "utf-8"));
|
|
294
|
-
}
|
|
295
|
-
else if (typeof config.project === "string") {
|
|
296
|
-
const projectPath = path.resolve(anchor, config.project);
|
|
297
|
-
if (!fs.existsSync(projectPath)) {
|
|
298
|
-
throw new Error(`specified project file not found: ${projectPath}`);
|
|
299
|
-
}
|
|
300
|
-
config.project = JSON.parse(fs.readFileSync(projectPath, "utf-8"));
|
|
301
|
-
}
|
|
302
|
-
if (!isValidRojoTree(config.project)) {
|
|
303
|
-
throw new Error("provided project is not a valid Rojo project. " +
|
|
304
|
-
"It must be a JSON object containing a 'name' and 'tree' property.");
|
|
305
|
-
}
|
|
306
|
-
return { config, anchor };
|
|
307
|
-
}
|
|
308
|
-
export function getEnvironment(anchor, cliModes) {
|
|
309
|
-
if (cliModes && cliModes.length > 0) {
|
|
310
|
-
return {
|
|
311
|
-
isTsProject: cliModes.includes("ts"),
|
|
312
|
-
isDarkluaProject: cliModes.includes("darklua"),
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
const isTsProject = fs.existsSync(path.join(anchor, "tsconfig.json"));
|
|
316
|
-
const isDarkluaProject = fs.existsSync(path.join(anchor, ".darklua.json")) ||
|
|
317
|
-
fs.existsSync(path.join(anchor, ".darklua.json5"));
|
|
318
|
-
return { isTsProject, isDarkluaProject };
|
|
319
|
-
}
|
|
320
|
-
export function resolveActiveModes(config, cliModes, env) {
|
|
321
|
-
const activeModes = [];
|
|
322
|
-
if (cliModes && cliModes.length > 0) {
|
|
323
|
-
for (const cliMode of cliModes) {
|
|
324
|
-
const requestedMode = config[cliMode];
|
|
325
|
-
if (!isMode(requestedMode)) {
|
|
326
|
-
throw new Error(`mode "${cliMode}" is not defined or is invalid in your config file.`);
|
|
327
|
-
}
|
|
328
|
-
activeModes.push({ name: cliMode, config: requestedMode });
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
for (const key in config) {
|
|
333
|
-
const potentialMode = config[key];
|
|
334
|
-
if (isMode(potentialMode)) {
|
|
335
|
-
if (key === "luau" && env.isTsProject)
|
|
336
|
-
continue;
|
|
337
|
-
if (key === "ts" && !env.isTsProject)
|
|
338
|
-
continue;
|
|
339
|
-
if (key === "darklua" && !env.isDarkluaProject)
|
|
340
|
-
continue;
|
|
341
|
-
activeModes.push({ name: key, config: potentialMode });
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (activeModes.length === 0) {
|
|
345
|
-
throw new Error("no output modes defined in configuration file. Add 'luau', 'ts', or custom modes.");
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return activeModes;
|
|
349
|
-
}
|
package/dist/constants.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
export const version = "1.4.4";
|
|
2
|
-
export const dataExtensions = new Set([
|
|
3
|
-
".csv",
|
|
4
|
-
".txt",
|
|
5
|
-
".json",
|
|
6
|
-
".toml",
|
|
7
|
-
".yaml",
|
|
8
|
-
".yml",
|
|
9
|
-
".msgpack",
|
|
10
|
-
".md",
|
|
11
|
-
]);
|
|
12
|
-
export const defaultProject = {
|
|
13
|
-
name: "roblox-game",
|
|
14
|
-
tree: { $className: "DataModel" },
|
|
15
|
-
};
|
|
16
|
-
export const defaultConfig = {
|
|
17
|
-
source: ["src"],
|
|
18
|
-
tags: {},
|
|
19
|
-
verbatim: false,
|
|
20
|
-
casing: "camelCase",
|
|
21
|
-
globIgnorePaths: [],
|
|
22
|
-
aliases: {},
|
|
23
|
-
unwrap: false,
|
|
24
|
-
luau: {
|
|
25
|
-
output: "default.project.json",
|
|
26
|
-
build: "src",
|
|
27
|
-
tags: {},
|
|
28
|
-
globIgnorePaths: [],
|
|
29
|
-
},
|
|
30
|
-
ts: {
|
|
31
|
-
output: "default.project.json",
|
|
32
|
-
build: "out",
|
|
33
|
-
tags: {},
|
|
34
|
-
globIgnorePaths: [],
|
|
35
|
-
},
|
|
36
|
-
darklua: {
|
|
37
|
-
output: "build.project.json",
|
|
38
|
-
build: "dist",
|
|
39
|
-
tags: {},
|
|
40
|
-
globIgnorePaths: [],
|
|
41
|
-
},
|
|
42
|
-
project: defaultProject,
|
|
43
|
-
};
|
|
44
|
-
export const services = {
|
|
45
|
-
Server: "ServerScriptService",
|
|
46
|
-
Client: "StarterPlayerScripts",
|
|
47
|
-
Shared: "ReplicatedStorage",
|
|
48
|
-
ServerScriptService: "ServerScriptService",
|
|
49
|
-
ReplicatedStorage: "ReplicatedStorage",
|
|
50
|
-
ReplicatedFirst: "ReplicatedFirst",
|
|
51
|
-
ServerStorage: "ServerStorage",
|
|
52
|
-
StarterGui: "StarterGui",
|
|
53
|
-
StarterPack: "StarterPack",
|
|
54
|
-
StarterPlayerScripts: "StarterPlayerScripts",
|
|
55
|
-
StarterCharacterScripts: "StarterCharacterScripts",
|
|
56
|
-
Workspace: "Workspace",
|
|
57
|
-
Lighting: "Lighting",
|
|
58
|
-
SoundService: "SoundService",
|
|
59
|
-
RobloxPluginGuiService: "RobloxPluginGuiService",
|
|
60
|
-
};
|
|
61
|
-
export const serviceParents = {
|
|
62
|
-
StarterPlayerScripts: "StarterPlayer",
|
|
63
|
-
StarterCharacterScripts: "StarterPlayer",
|
|
64
|
-
};
|
|
65
|
-
export const combinedServices = new Set([
|
|
66
|
-
...Object.values(services),
|
|
67
|
-
...Object.values(serviceParents),
|
|
68
|
-
]);
|
|
69
|
-
export const serverContainers = new Set([
|
|
70
|
-
"ServerScriptService",
|
|
71
|
-
"ServerStorage",
|
|
72
|
-
]);
|
|
73
|
-
export const clientContainers = new Set([
|
|
74
|
-
"StarterPlayer",
|
|
75
|
-
"StarterPlayerScripts",
|
|
76
|
-
"StarterCharacterScripts",
|
|
77
|
-
"StarterGui",
|
|
78
|
-
"StarterPack",
|
|
79
|
-
"ReplicatedFirst",
|
|
80
|
-
]);
|
|
81
|
-
export const serviceAliases = new Set(["server", "client", "shared"]);
|
|
82
|
-
export function generateRoutingMaps(customAliases = {}) {
|
|
83
|
-
const mergedServices = { ...services, ...customAliases };
|
|
84
|
-
const lowerCaseMap = Object.fromEntries(Object.entries(mergedServices).map(([k, v]) => [k.toLowerCase(), v]));
|
|
85
|
-
const mergedKeys = Object.keys(mergedServices).sort((a, b) => b.length - a.length);
|
|
86
|
-
const lowerKeys = Object.keys(lowerCaseMap).sort((a, b) => b.length - a.length);
|
|
87
|
-
const allPrefixKeys = Array.from(new Set([...lowerKeys, ...mergedKeys])).sort((a, b) => b.length - a.length);
|
|
88
|
-
const separatorSuffixRegex = new RegExp(`[\\.\\-_\\+](${lowerKeys.join("|")})$`, "i");
|
|
89
|
-
const pascalCaseSuffixRegex = new RegExp(`(${mergedKeys.join("|")})$`);
|
|
90
|
-
const separatorPrefixRegex = new RegExp(`^(${lowerKeys.join("|")})([\\.\\-_\\+])`, "i");
|
|
91
|
-
const camelCasePrefixRegex = new RegExp(`^(${allPrefixKeys.join("|")})(?=[A-Z])`);
|
|
92
|
-
return {
|
|
93
|
-
mergedServices,
|
|
94
|
-
lowerCaseMap,
|
|
95
|
-
separatorSuffixRegex,
|
|
96
|
-
pascalCaseSuffixRegex,
|
|
97
|
-
separatorPrefixRegex,
|
|
98
|
-
camelCasePrefixRegex,
|
|
99
|
-
};
|
|
100
|
-
}
|
package/dist/execute.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { build } from "./build.js";
|
|
4
|
-
import { ConsoleLogger, getTimeStamp } from "./logger.js";
|
|
5
|
-
export async function execute(sourcePaths, env, activeModes, baseProjectTree, config, cliArgs, anchor, logger = new ConsoleLogger(), isInitialRun = false) {
|
|
6
|
-
try {
|
|
7
|
-
for (const activeMode of activeModes) {
|
|
8
|
-
const targetConfig = activeMode.config;
|
|
9
|
-
const modeName = activeMode.name;
|
|
10
|
-
const buildResult = await build(targetConfig, baseProjectTree, config, env, sourcePaths, cliArgs, anchor);
|
|
11
|
-
const dropped = [];
|
|
12
|
-
if (buildResult.exposedDataFiles.length > 0) {
|
|
13
|
-
logger.warn(`${getTimeStamp()} [${modeName}] Ignored ${buildResult.exposedDataFiles.length} exposed data file(s). Rogen cannot map data files directly.`);
|
|
14
|
-
for (const exposed of buildResult.exposedDataFiles) {
|
|
15
|
-
const exposedPath = exposed.path;
|
|
16
|
-
const ext = path.extname(exposedPath);
|
|
17
|
-
const fileName = path.basename(exposedPath);
|
|
18
|
-
const baseName = fileName.substring(0, fileName.length - ext.length);
|
|
19
|
-
const dirName = path.dirname(exposedPath);
|
|
20
|
-
logger.info(` - Cannot resolve data type "${ext}" for: "${exposedPath}"`);
|
|
21
|
-
logger.info(` Fix: Wrap it in a folder (e.g., move it to "${dirName}/${baseName}/data${ext}") or add it to 'globIgnorePaths' to suppress this warning.`);
|
|
22
|
-
delete exposed.parent[exposed.key];
|
|
23
|
-
dropped.push(`${exposed.key} ($path "${exposedPath}")`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (buildResult.missingPaths.length > 0) {
|
|
27
|
-
// Only synthesize placeholder stubs/dirs for compiled projects (ts/darklua),
|
|
28
|
-
// where the build output legitimately does not exist yet at generation time. For a luau
|
|
29
|
-
// project the build dir IS the source, so a missing path means the file was removed —
|
|
30
|
-
// recreating it would fight an in-progress delete and desync Rojo. Drop the stale node instead.
|
|
31
|
-
const isCompiledProject = env.isTsProject || env.isDarkluaProject;
|
|
32
|
-
for (const item of buildResult.missingPaths) {
|
|
33
|
-
const ext = path.extname(item.absolutePath).toLowerCase();
|
|
34
|
-
if (isCompiledProject &&
|
|
35
|
-
(ext === ".luau" || ext === ".lua")) {
|
|
36
|
-
const dir = path.dirname(item.absolutePath);
|
|
37
|
-
if (!fs.existsSync(dir)) {
|
|
38
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
fs.writeFileSync(item.absolutePath, "");
|
|
41
|
-
}
|
|
42
|
-
else if (isCompiledProject && ext === "") {
|
|
43
|
-
if (!fs.existsSync(item.absolutePath)) {
|
|
44
|
-
fs.mkdirSync(item.absolutePath, {
|
|
45
|
-
recursive: true,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
delete item.parent[item.key];
|
|
51
|
-
dropped.push(`${item.treePath} ($path "${item.path}")`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const finalContent = JSON.stringify(buildResult.tree, null, "\t");
|
|
56
|
-
let shouldWrite = true;
|
|
57
|
-
if (fs.existsSync(buildResult.output)) {
|
|
58
|
-
const existingContent = fs.readFileSync(buildResult.output, "utf-8");
|
|
59
|
-
if (existingContent === finalContent) {
|
|
60
|
-
shouldWrite = false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!shouldWrite && cliArgs.watch && !isInitialRun) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (shouldWrite) {
|
|
67
|
-
const outputDir = path.dirname(buildResult.output);
|
|
68
|
-
if (!fs.existsSync(outputDir)) {
|
|
69
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
70
|
-
}
|
|
71
|
-
// Write atomically (temp + rename) so a watcher (e.g. Rojo) never reads a
|
|
72
|
-
// half-written project file during rapid regenerations.
|
|
73
|
-
const tempOutput = `${buildResult.output}.tmp`;
|
|
74
|
-
fs.writeFileSync(tempOutput, finalContent);
|
|
75
|
-
fs.renameSync(tempOutput, buildResult.output);
|
|
76
|
-
}
|
|
77
|
-
const totalRemoved = buildResult.removed.length + dropped.length;
|
|
78
|
-
if (totalRemoved > 0) {
|
|
79
|
-
if (cliArgs.watch) {
|
|
80
|
-
logger.warn(`Pruned ${totalRemoved} unresolvable paths.`);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
logger.warn(`Removed entries whose paths do not exist:`);
|
|
84
|
-
for (const item of buildResult.removed) {
|
|
85
|
-
logger.info(` - ${item.treePath} ($path "${item.rojoPath}")`);
|
|
86
|
-
}
|
|
87
|
-
for (const item of dropped) {
|
|
88
|
-
logger.info(` - ${item}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (buildResult.collisions.length > 0) {
|
|
93
|
-
for (const collision of buildResult.collisions) {
|
|
94
|
-
logger.warn(collision);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (!isInitialRun) {
|
|
98
|
-
const outputName = path.basename(buildResult.output);
|
|
99
|
-
logger.success(`[${modeName}] Rebuilt "${buildResult.name}" -> ${outputName}`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
if (shouldWrite) {
|
|
103
|
-
logger.success(`Generated Rojo tree for "${buildResult.name}"`);
|
|
104
|
-
logger.info(` Processed: ${buildResult.fileCount} files`);
|
|
105
|
-
logger.info(` Build dir: ${buildResult.buildDir}`);
|
|
106
|
-
const activeTags = Object.keys(targetConfig.tags || {}).filter((t) => targetConfig.tags[t]);
|
|
107
|
-
if (activeTags.length > 0) {
|
|
108
|
-
logger.info(` Tags: ${activeTags.join(", ")}`);
|
|
109
|
-
}
|
|
110
|
-
logger.info(` Output to: ${buildResult.output}`);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
logger.success(`Rojo tree for "${buildResult.name}" is already up to date`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
if (error instanceof Error) {
|
|
121
|
-
logger.error(`failed to execute build - ${error.message}`);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
logger.error(`failed to execute build due to an unknown error`);
|
|
125
|
-
}
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
}
|
package/dist/index.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
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, true);
|
|
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 isWindows = process.platform === "win32";
|
|
77
|
-
const watcher = chokidar.watch(sourcePaths, {
|
|
78
|
-
persistent: true,
|
|
79
|
-
ignoreInitial: true,
|
|
80
|
-
// Use polling on Windows to avoid "folder in use" locks
|
|
81
|
-
usePolling: isWindows,
|
|
82
|
-
interval: isWindows ? 400 : undefined,
|
|
83
|
-
binaryInterval: isWindows ? 800 : undefined,
|
|
84
|
-
});
|
|
85
|
-
let debounceTimeout;
|
|
86
|
-
let isBuilding = false;
|
|
87
|
-
watcher.on("all", (event, filePath) => {
|
|
88
|
-
logger.trace(`File change detected (${event}): ${filePath}`);
|
|
89
|
-
clearTimeout(debounceTimeout);
|
|
90
|
-
debounceTimeout = setTimeout(() => {
|
|
91
|
-
if (isBuilding)
|
|
92
|
-
return;
|
|
93
|
-
isBuilding = true;
|
|
94
|
-
try {
|
|
95
|
-
execute(sourcePaths, env, activeModes, config.project, config, cliArgs, anchor, logger, false);
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
logger.error(err instanceof Error ? err : String(err));
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
isBuilding = false;
|
|
102
|
-
}
|
|
103
|
-
}, 100);
|
|
104
|
-
});
|
|
105
|
-
watcher.on("error", (error) => logger.error(`${error}`));
|
|
106
|
-
await new Promise(() => { }); // Keep alive
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
export default function run() {
|
|
110
|
-
const logger = new ConsoleLogger();
|
|
111
|
-
main().catch((error) => {
|
|
112
|
-
logger.error(`${error.message}`);
|
|
113
|
-
process.exit(1);
|
|
114
|
-
});
|
|
115
|
-
}
|