@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/LICENSE.md +6 -6
- package/README.md +112 -33
- package/dist/bundle.cjs +27433 -3839
- 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/package.json
CHANGED
|
@@ -1,66 +1,103 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ldgerrits/rogen",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A
|
|
5
|
-
"publishConfig": {
|
|
6
|
-
"access": "public"
|
|
7
|
-
},
|
|
8
|
-
"homepage": "https://rogen-
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/LDGerrits/rogen.git"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"roblox",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
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
|
-
}
|