mujoco-react 8.7.0 → 8.9.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/vite.d.ts CHANGED
@@ -14,7 +14,7 @@ type ViteServer = {
14
14
  interface MujocoReactPluginOptions {
15
15
  /** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */
16
16
  models: ModelInput;
17
- /** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */
17
+ /** Generated resource module. Defaults to `src/mujoco-register.gen.ts`. */
18
18
  generatedRegister?: string;
19
19
  /** Module name to augment. Defaults to `mujoco-react`. */
20
20
  moduleName?: string;
@@ -38,6 +38,7 @@ declare function mujocoReact(options: MujocoReactPluginOptions): {
38
38
  name: string;
39
39
  enforce: "pre";
40
40
  configResolved(config: ViteConfig): void;
41
+ transform(code: string, id: string): string | undefined;
41
42
  buildStart(this: {
42
43
  addWatchFile?: (file: string) => void;
43
44
  }): Promise<void>;
package/dist/vite.js CHANGED
@@ -18,7 +18,7 @@ function createEmptyNames() {
18
18
  function mujocoReact(options) {
19
19
  const models = normalizeModels(options.models);
20
20
  let root = process.cwd();
21
- let generatedRegister = options.generatedRegister ?? "src/mujoco-register.gen.d.ts";
21
+ let generatedRegister = options.generatedRegister ?? "src/mujoco-register.gen.ts";
22
22
  let watchedFiles = [];
23
23
  async function generate() {
24
24
  const result = await generateMujocoRegister({
@@ -41,6 +41,11 @@ function mujocoReact(options) {
41
41
  root = config.root;
42
42
  generatedRegister = path.resolve(root, generatedRegister);
43
43
  },
44
+ transform(code, id) {
45
+ if (!shouldInjectRegisterImport(id, root, generatedRegister)) return;
46
+ return `${renderGeneratedImport(id, generatedRegister)}
47
+ ${code}`;
48
+ },
44
49
  async buildStart() {
45
50
  const result = await generate();
46
51
  for (const file of result.files) this.addWatchFile?.(file);
@@ -68,12 +73,13 @@ async function generateMujocoRegister(options) {
68
73
  const models = normalizeModels(options.models);
69
74
  const names = createEmptyNames();
70
75
  const seen = /* @__PURE__ */ new Set();
76
+ const declarationsOnly = out.endsWith(".d.ts");
71
77
  for (const model of models) {
72
78
  await scanModel(path.resolve(root, model.file), root, seen, model.names);
73
79
  mergeNames(names, model.names);
74
80
  }
75
81
  await mkdir(path.dirname(out), { recursive: true });
76
- await writeFile(out, renderRegister(moduleName, names, models), "utf8");
82
+ await writeFile(out, renderRegister(moduleName, names, models, declarationsOnly), "utf8");
77
83
  return {
78
84
  out,
79
85
  files: [...seen].sort((a, b) => a.localeCompare(b)),
@@ -127,37 +133,130 @@ function readAttr(attrs, attr) {
127
133
  const pattern = new RegExp(`\\b${attr}\\s*=\\s*(['"])(.*?)\\1`, "i");
128
134
  return attrs.match(pattern)?.[2];
129
135
  }
130
- function renderRegister(moduleName, names, models) {
136
+ function renderRegister(moduleName, names, models, declarationsOnly) {
131
137
  const fields = REGISTER_KEYS.filter((key) => names[key].size > 0).map((key) => ` ${key}: ${renderUnion(names[key])};`);
132
138
  const robots = models.map((model) => ` ${quoteProperty(model.id)}: {
133
139
  ${renderRobotFields(model.names)}
134
140
  };`);
141
+ const namespaceAliases = renderNamespaceAliases(models);
142
+ const registerImport = declarationsOnly ? "" : `import { registerRobotResources } from '${moduleName}';
143
+ `;
144
+ const resources = declarationsOnly ? "" : `${renderResourceTypes(models)}
145
+
146
+ ${renderResourceConstants(models)}
147
+
148
+ registerRobotResources(generatedRobotResources);
149
+
150
+ `;
135
151
  return `// Auto-generated by mujoco-react. Do not edit.
136
152
  // Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
137
153
 
138
- import 'mujoco-react';
154
+ ${registerImport}import type { RobotResource } from '${moduleName}';
139
155
 
140
- declare module '${moduleName}' {
156
+ ${resources}declare module '${moduleName}' {
141
157
  interface Register {
142
158
  robots: {
143
159
  ${robots.join("\n")}
144
160
  };
145
161
  ${fields.join("\n")}
146
162
  }
163
+
164
+ ${namespaceAliases}
147
165
  }
148
166
  `;
149
167
  }
168
+ function renderResourceTypes(models) {
169
+ const modelTypes = models.map((model) => ` readonly ${quoteProperty(model.id)}: {
170
+ ${renderResourceTypeFields(model.names)}
171
+ };`).join("\n");
172
+ return `type GeneratedRobotResources = {
173
+ ${modelTypes}
174
+ };`;
175
+ }
176
+ function renderResourceTypeFields(names) {
177
+ return REGISTER_KEYS.map((key) => ` readonly ${key}: ${renderResourceObjectType(names[key])};`).join("\n");
178
+ }
179
+ function renderResourceObjectType(values) {
180
+ const entries = sortedValues(values).map((value) => ` readonly ${quoteProperty(value)}: '${escapeTs(value)}';`);
181
+ return entries.length > 0 ? `{
182
+ ${entries.join("\n")}
183
+ }` : "{}";
184
+ }
185
+ function renderResourceConstants(models) {
186
+ const entries = models.map((model) => ` ${quoteProperty(model.id)}: {
187
+ ${renderResourceConstantFields(model.names)}
188
+ },`).join("\n");
189
+ return `const generatedRobotResources: GeneratedRobotResources = {
190
+ ${entries}
191
+ };`;
192
+ }
193
+ function renderResourceConstantFields(names) {
194
+ return REGISTER_KEYS.map((key) => ` ${key}: ${renderResourceObject(names[key])},`).join("\n");
195
+ }
196
+ function renderResourceObject(values) {
197
+ const entries = sortedValues(values).map((value) => ` ${quoteProperty(value)}: '${escapeTs(value)}',`);
198
+ return entries.length > 0 ? `{
199
+ ${entries.join("\n")}
200
+ }` : "{}";
201
+ }
150
202
  function renderRobotFields(names) {
151
203
  return REGISTER_KEYS.map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : "never"};`).join("\n");
152
204
  }
153
205
  function renderUnion(values) {
154
- return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(" | ");
206
+ return sortedValues(values).map((value) => `'${escapeTs(value)}'`).join(" | ");
207
+ }
208
+ function renderNamespaceAliases(models) {
209
+ const namespaces = {
210
+ actuators: "RobotActuators",
211
+ sensors: "RobotSensors",
212
+ bodies: "RobotBodies",
213
+ joints: "RobotJoints",
214
+ sites: "RobotSites",
215
+ geoms: "RobotGeoms",
216
+ keyframes: "RobotKeyframes"
217
+ };
218
+ const blocks = REGISTER_KEYS.map((key) => {
219
+ const aliases = models.filter((model) => isIdentifier(model.id)).map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);
220
+ if (aliases.length === 0) return "";
221
+ return ` export namespace ${namespaces[key]} {
222
+ ${aliases.join("\n")}
223
+ }`;
224
+ }).filter(Boolean).join("\n\n");
225
+ return blocks ? `${blocks}
226
+ ` : "";
155
227
  }
156
228
  function escapeTs(value) {
157
229
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
158
230
  }
231
+ function sortedValues(values) {
232
+ return [...values].sort((a, b) => a.localeCompare(b));
233
+ }
159
234
  function quoteProperty(value) {
160
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : `'${escapeTs(value)}'`;
235
+ return isIdentifier(value) ? value : `'${escapeTs(value)}'`;
236
+ }
237
+ function isIdentifier(value) {
238
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
239
+ }
240
+ function toPosixPath(value) {
241
+ return value.split(path.sep).join("/");
242
+ }
243
+ function shouldInjectRegisterImport(id, root, generatedRegister) {
244
+ if (generatedRegister.endsWith(".d.ts")) return false;
245
+ const file = stripQuery(id);
246
+ if (!/\.[cm]?[jt]sx?$/.test(file)) return false;
247
+ if (file.includes(`${path.sep}node_modules${path.sep}`)) return false;
248
+ const absolute = path.resolve(file);
249
+ if (absolute === generatedRegister) return false;
250
+ return absolute.startsWith(root);
251
+ }
252
+ function renderGeneratedImport(id, generatedRegister) {
253
+ const fromDir = path.dirname(stripQuery(id));
254
+ let relative = toPosixPath(path.relative(fromDir, generatedRegister));
255
+ if (!relative.startsWith(".")) relative = `./${relative}`;
256
+ return `import '${relative}';`;
257
+ }
258
+ function stripQuery(id) {
259
+ return id.split("?")[0];
161
260
  }
162
261
  function normalizeModels(input) {
163
262
  if (typeof input === "string") return [createModelEntry(deriveModelId(input), input)];
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vite.ts"],"names":[],"mappings":"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","file":"vite.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\ntype ViteConfig = { root: string };\ntype ViteServer = {\n watcher: {\n add(paths: string | string[]): void;\n on(event: 'add' | 'change' | 'unlink', listener: (file: string) => void): void;\n };\n};\n\nexport interface MujocoReactPluginOptions {\n /** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */\n models: ModelInput;\n /** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */\n generatedRegister?: string;\n /** Module name to augment. Defaults to `mujoco-react`. */\n moduleName?: string;\n /** Disable console output. */\n disableLogging?: boolean;\n}\n\nexport interface MujocoRegisterCodegenOptions {\n models: ModelInput;\n out: string;\n moduleName?: string;\n root?: string;\n}\n\nexport interface MujocoRegisterCodegenResult {\n out: string;\n files: string[];\n counts: Record<RegisterKey, number>;\n}\n\ntype RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';\nexport type ModelInput = string | readonly string[] | Record<string, string>;\n\ninterface ModelEntry {\n id: string;\n file: string;\n names: Record<RegisterKey, Set<string>>;\n}\n\nconst REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];\nconst MODEL_EXTENSIONS = new Set(['.xml', '.mjcf', '.urdf']);\n\nfunction createEmptyNames(): Record<RegisterKey, Set<string>> {\n return {\n actuators: new Set(),\n sensors: new Set(),\n bodies: new Set(),\n joints: new Set(),\n sites: new Set(),\n geoms: new Set(),\n keyframes: new Set(),\n };\n}\n\nexport function mujocoReact(options: MujocoReactPluginOptions) {\n const models = normalizeModels(options.models);\n let root = process.cwd();\n let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.d.ts';\n let watchedFiles: string[] = [];\n\n async function generate() {\n const result = await generateMujocoRegister({\n models: options.models,\n out: generatedRegister,\n moduleName: options.moduleName,\n root,\n });\n watchedFiles = result.files;\n if (!options.disableLogging) {\n const total = Object.values(result.counts).reduce((sum, count) => sum + count, 0);\n console.log(`[mujoco-react] generated ${path.relative(root, result.out)} (${total} names)`);\n }\n return result;\n }\n\n return {\n name: 'mujoco-react',\n enforce: 'pre' as const,\n configResolved(config: ViteConfig) {\n root = config.root;\n generatedRegister = path.resolve(root, generatedRegister);\n },\n async buildStart(this: { addWatchFile?: (file: string) => void }) {\n const result = await generate();\n for (const file of result.files) this.addWatchFile?.(file);\n },\n configureServer(server: ViteServer) {\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n\n server.watcher.on('add', regenerateIfModelFile);\n server.watcher.on('change', regenerateIfModelFile);\n server.watcher.on('unlink', regenerateIfModelFile);\n\n function regenerateIfModelFile(file: string) {\n if (!shouldRegenerate(file, watchedFiles, models, root)) return;\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n }\n },\n };\n}\n\nexport async function generateMujocoRegister(\n options: MujocoRegisterCodegenOptions\n): Promise<MujocoRegisterCodegenResult> {\n const root = path.resolve(options.root ?? process.cwd());\n const out = path.resolve(root, options.out);\n const moduleName = options.moduleName ?? 'mujoco-react';\n const models = normalizeModels(options.models);\n const names = createEmptyNames();\n const seen = new Set<string>();\n\n for (const model of models) {\n await scanModel(path.resolve(root, model.file), root, seen, model.names);\n mergeNames(names, model.names);\n }\n\n await mkdir(path.dirname(out), { recursive: true });\n await writeFile(out, renderRegister(moduleName, names, models), 'utf8');\n\n return {\n out,\n files: [...seen].sort((a, b) => a.localeCompare(b)),\n counts: Object.fromEntries(REGISTER_KEYS.map((key) => [key, names[key].size])) as Record<RegisterKey, number>,\n };\n}\n\nasync function scanModel(\n filePath: string,\n root: string,\n seen: Set<string>,\n names: Record<RegisterKey, Set<string>>\n) {\n const normalized = path.normalize(filePath);\n if (seen.has(normalized)) return;\n seen.add(normalized);\n\n const xml = await readFile(normalized, 'utf8');\n collectSimpleTagNames(xml, 'body', names.bodies);\n collectSimpleTagNames(xml, 'joint', names.joints);\n collectSimpleTagNames(xml, 'site', names.sites);\n collectSimpleTagNames(xml, 'geom', names.geoms);\n collectSimpleTagNames(xml, 'key', names.keyframes);\n collectSectionNames(xml, 'actuator', names.actuators);\n collectSectionNames(xml, 'sensor', names.sensors);\n\n for (const includePath of collectIncludePaths(xml)) {\n const next = path.resolve(path.dirname(normalized), includePath);\n if (next.startsWith(root)) await scanModel(next, root, seen, names);\n }\n}\n\nfunction collectSimpleTagNames(xml: string, tag: string, target: Set<string>) {\n const pattern = new RegExp(`<\\\\s*${tag}\\\\b([^>]*)>`, 'gi');\n for (const match of xml.matchAll(pattern)) {\n const name = readAttr(match[1], 'name');\n if (name) target.add(name);\n }\n}\n\nfunction collectSectionNames(xml: string, section: string, target: Set<string>) {\n const sectionPattern = new RegExp(`<\\\\s*${section}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\s*/\\\\s*${section}\\\\s*>`, 'gi');\n for (const sectionMatch of xml.matchAll(sectionPattern)) {\n const tagPattern = /<\\s*[a-zA-Z0-9_:-]+\\b([^>]*)>/g;\n for (const tagMatch of sectionMatch[1].matchAll(tagPattern)) {\n const name = readAttr(tagMatch[1], 'name');\n if (name) target.add(name);\n }\n }\n}\n\nfunction collectIncludePaths(xml: string): string[] {\n const result: string[] = [];\n const pattern = /<\\s*include\\b([^>]*)>/gi;\n for (const match of xml.matchAll(pattern)) {\n const file = readAttr(match[1], 'file');\n if (file && !file.includes('://') && !file.startsWith('/')) result.push(file);\n }\n return result;\n}\n\nfunction readAttr(attrs: string, attr: string): string | undefined {\n const pattern = new RegExp(`\\\\b${attr}\\\\s*=\\\\s*(['\"])(.*?)\\\\1`, 'i');\n return attrs.match(pattern)?.[2];\n}\n\nfunction renderRegister(\n moduleName: string,\n names: Record<RegisterKey, Set<string>>,\n models: readonly ModelEntry[]\n): string {\n const fields = REGISTER_KEYS\n .filter((key) => names[key].size > 0)\n .map((key) => ` ${key}: ${renderUnion(names[key])};`);\n const robots = models\n .map((model) => ` ${quoteProperty(model.id)}: {\\n${renderRobotFields(model.names)}\\n };`);\n\n return `// Auto-generated by mujoco-react. Do not edit.\n// Regenerate by running Vite with the mujocoReact() plugin or \\`mujoco-react codegen\\`.\n\nimport 'mujoco-react';\n\ndeclare module '${moduleName}' {\n interface Register {\n robots: {\n${robots.join('\\n')}\n };\n${fields.join('\\n')}\n }\n}\n`;\n}\n\nfunction renderRobotFields(names: Record<RegisterKey, Set<string>>): string {\n return REGISTER_KEYS\n .map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)\n .join('\\n');\n}\n\nfunction renderUnion(values: Set<string>): string {\n return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(' | ');\n}\n\nfunction escapeTs(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction quoteProperty(value: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : `'${escapeTs(value)}'`;\n}\n\nfunction normalizeModels(input: ModelInput): ModelEntry[] {\n if (typeof input === 'string') return [createModelEntry(deriveModelId(input), input)];\n if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));\n return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));\n}\n\nfunction createModelEntry(id: string, file: string): ModelEntry {\n return { id, file, names: createEmptyNames() };\n}\n\nfunction deriveModelId(file: string): string {\n const normalized = file.replace(/\\\\/g, '/');\n const parts = normalized.split('/').filter(Boolean);\n const filename = parts.at(-1) ?? 'model';\n const parent = parts.length > 1 ? parts.at(-2) : undefined;\n const base = filename.replace(/\\.(xml|mjcf|urdf)$/i, '');\n return sanitizeModelId(parent && ['scene', 'model', 'robot'].includes(base.toLowerCase()) ? parent : base);\n}\n\nfunction sanitizeModelId(value: string): string {\n const sanitized = value.replace(/[^A-Za-z0-9_$]/g, '_').replace(/^[^A-Za-z_$]+/, '');\n return sanitized || 'model';\n}\n\nfunction mergeNames(target: Record<RegisterKey, Set<string>>, source: Record<RegisterKey, Set<string>>) {\n for (const key of REGISTER_KEYS) {\n for (const value of source[key]) target[key].add(value);\n }\n}\n\nfunction shouldRegenerate(file: string, watchedFiles: string[], models: readonly ModelEntry[], root: string): boolean {\n const absolute = path.resolve(file);\n if (watchedFiles.includes(absolute)) return true;\n if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;\n const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));\n return modelDirs.some((dir) => absolute.startsWith(dir));\n}\n"]}
1
+ {"version":3,"sources":["../src/vite.ts"],"names":[],"mappings":"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","file":"vite.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\ntype ViteConfig = { root: string };\ntype ViteServer = {\n watcher: {\n add(paths: string | string[]): void;\n on(event: 'add' | 'change' | 'unlink', listener: (file: string) => void): void;\n };\n};\n\nexport interface MujocoReactPluginOptions {\n /** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */\n models: ModelInput;\n /** Generated resource module. Defaults to `src/mujoco-register.gen.ts`. */\n generatedRegister?: string;\n /** Module name to augment. Defaults to `mujoco-react`. */\n moduleName?: string;\n /** Disable console output. */\n disableLogging?: boolean;\n}\n\nexport interface MujocoRegisterCodegenOptions {\n models: ModelInput;\n out: string;\n moduleName?: string;\n root?: string;\n}\n\nexport interface MujocoRegisterCodegenResult {\n out: string;\n files: string[];\n counts: Record<RegisterKey, number>;\n}\n\ntype RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';\nexport type ModelInput = string | readonly string[] | Record<string, string>;\n\ninterface ModelEntry {\n id: string;\n file: string;\n names: Record<RegisterKey, Set<string>>;\n}\n\nconst REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];\nconst MODEL_EXTENSIONS = new Set(['.xml', '.mjcf', '.urdf']);\n\nfunction createEmptyNames(): Record<RegisterKey, Set<string>> {\n return {\n actuators: new Set(),\n sensors: new Set(),\n bodies: new Set(),\n joints: new Set(),\n sites: new Set(),\n geoms: new Set(),\n keyframes: new Set(),\n };\n}\n\nexport function mujocoReact(options: MujocoReactPluginOptions) {\n const models = normalizeModels(options.models);\n let root = process.cwd();\n let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.ts';\n let watchedFiles: string[] = [];\n\n async function generate() {\n const result = await generateMujocoRegister({\n models: options.models,\n out: generatedRegister,\n moduleName: options.moduleName,\n root,\n });\n watchedFiles = result.files;\n if (!options.disableLogging) {\n const total = Object.values(result.counts).reduce((sum, count) => sum + count, 0);\n console.log(`[mujoco-react] generated ${path.relative(root, result.out)} (${total} names)`);\n }\n return result;\n }\n\n return {\n name: 'mujoco-react',\n enforce: 'pre' as const,\n configResolved(config: ViteConfig) {\n root = config.root;\n generatedRegister = path.resolve(root, generatedRegister);\n },\n transform(code: string, id: string) {\n if (!shouldInjectRegisterImport(id, root, generatedRegister)) return;\n return `${renderGeneratedImport(id, generatedRegister)}\\n${code}`;\n },\n async buildStart(this: { addWatchFile?: (file: string) => void }) {\n const result = await generate();\n for (const file of result.files) this.addWatchFile?.(file);\n },\n configureServer(server: ViteServer) {\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n\n server.watcher.on('add', regenerateIfModelFile);\n server.watcher.on('change', regenerateIfModelFile);\n server.watcher.on('unlink', regenerateIfModelFile);\n\n function regenerateIfModelFile(file: string) {\n if (!shouldRegenerate(file, watchedFiles, models, root)) return;\n generate().then((result) => server.watcher.add(result.files)).catch((error: unknown) => {\n console.error('[mujoco-react] register generation failed', error);\n });\n }\n },\n };\n}\n\nexport async function generateMujocoRegister(\n options: MujocoRegisterCodegenOptions\n): Promise<MujocoRegisterCodegenResult> {\n const root = path.resolve(options.root ?? process.cwd());\n const out = path.resolve(root, options.out);\n const moduleName = options.moduleName ?? 'mujoco-react';\n const models = normalizeModels(options.models);\n const names = createEmptyNames();\n const seen = new Set<string>();\n const declarationsOnly = out.endsWith('.d.ts');\n\n for (const model of models) {\n await scanModel(path.resolve(root, model.file), root, seen, model.names);\n mergeNames(names, model.names);\n }\n\n await mkdir(path.dirname(out), { recursive: true });\n await writeFile(out, renderRegister(moduleName, names, models, declarationsOnly), 'utf8');\n\n return {\n out,\n files: [...seen].sort((a, b) => a.localeCompare(b)),\n counts: Object.fromEntries(REGISTER_KEYS.map((key) => [key, names[key].size])) as Record<RegisterKey, number>,\n };\n}\n\nasync function scanModel(\n filePath: string,\n root: string,\n seen: Set<string>,\n names: Record<RegisterKey, Set<string>>\n) {\n const normalized = path.normalize(filePath);\n if (seen.has(normalized)) return;\n seen.add(normalized);\n\n const xml = await readFile(normalized, 'utf8');\n collectSimpleTagNames(xml, 'body', names.bodies);\n collectSimpleTagNames(xml, 'joint', names.joints);\n collectSimpleTagNames(xml, 'site', names.sites);\n collectSimpleTagNames(xml, 'geom', names.geoms);\n collectSimpleTagNames(xml, 'key', names.keyframes);\n collectSectionNames(xml, 'actuator', names.actuators);\n collectSectionNames(xml, 'sensor', names.sensors);\n\n for (const includePath of collectIncludePaths(xml)) {\n const next = path.resolve(path.dirname(normalized), includePath);\n if (next.startsWith(root)) await scanModel(next, root, seen, names);\n }\n}\n\nfunction collectSimpleTagNames(xml: string, tag: string, target: Set<string>) {\n const pattern = new RegExp(`<\\\\s*${tag}\\\\b([^>]*)>`, 'gi');\n for (const match of xml.matchAll(pattern)) {\n const name = readAttr(match[1], 'name');\n if (name) target.add(name);\n }\n}\n\nfunction collectSectionNames(xml: string, section: string, target: Set<string>) {\n const sectionPattern = new RegExp(`<\\\\s*${section}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\s*/\\\\s*${section}\\\\s*>`, 'gi');\n for (const sectionMatch of xml.matchAll(sectionPattern)) {\n const tagPattern = /<\\s*[a-zA-Z0-9_:-]+\\b([^>]*)>/g;\n for (const tagMatch of sectionMatch[1].matchAll(tagPattern)) {\n const name = readAttr(tagMatch[1], 'name');\n if (name) target.add(name);\n }\n }\n}\n\nfunction collectIncludePaths(xml: string): string[] {\n const result: string[] = [];\n const pattern = /<\\s*include\\b([^>]*)>/gi;\n for (const match of xml.matchAll(pattern)) {\n const file = readAttr(match[1], 'file');\n if (file && !file.includes('://') && !file.startsWith('/')) result.push(file);\n }\n return result;\n}\n\nfunction readAttr(attrs: string, attr: string): string | undefined {\n const pattern = new RegExp(`\\\\b${attr}\\\\s*=\\\\s*(['\"])(.*?)\\\\1`, 'i');\n return attrs.match(pattern)?.[2];\n}\n\nfunction renderRegister(\n moduleName: string,\n names: Record<RegisterKey, Set<string>>,\n models: readonly ModelEntry[],\n declarationsOnly: boolean\n): string {\n const fields = REGISTER_KEYS\n .filter((key) => names[key].size > 0)\n .map((key) => ` ${key}: ${renderUnion(names[key])};`);\n const robots = models\n .map((model) => ` ${quoteProperty(model.id)}: {\\n${renderRobotFields(model.names)}\\n };`);\n const namespaceAliases = renderNamespaceAliases(models);\n const registerImport = declarationsOnly ? '' : `import { registerRobotResources } from '${moduleName}';\\n`;\n const resources = declarationsOnly ? '' : `${renderResourceTypes(models)}\\n\\n${renderResourceConstants(models)}\\n\\nregisterRobotResources(generatedRobotResources);\\n\\n`;\n\n return `// Auto-generated by mujoco-react. Do not edit.\n// Regenerate by running Vite with the mujocoReact() plugin or \\`mujoco-react codegen\\`.\n\n${registerImport}import type { RobotResource } from '${moduleName}';\n\n${resources}declare module '${moduleName}' {\n interface Register {\n robots: {\n${robots.join('\\n')}\n };\n${fields.join('\\n')}\n }\n\n${namespaceAliases}\n}\n`;\n}\n\nfunction renderResourceTypes(models: readonly ModelEntry[]): string {\n const modelTypes = models\n .map((model) => ` readonly ${quoteProperty(model.id)}: {\\n${renderResourceTypeFields(model.names)}\\n };`)\n .join('\\n');\n return `type GeneratedRobotResources = {\\n${modelTypes}\\n};`;\n}\n\nfunction renderResourceTypeFields(names: Record<RegisterKey, Set<string>>): string {\n return REGISTER_KEYS\n .map((key) => ` readonly ${key}: ${renderResourceObjectType(names[key])};`)\n .join('\\n');\n}\n\nfunction renderResourceObjectType(values: Set<string>): string {\n const entries = sortedValues(values)\n .map((value) => ` readonly ${quoteProperty(value)}: '${escapeTs(value)}';`);\n return entries.length > 0 ? `{\\n${entries.join('\\n')}\\n }` : '{}';\n}\n\nfunction renderResourceConstants(models: readonly ModelEntry[]): string {\n const entries = models\n .map((model) => ` ${quoteProperty(model.id)}: {\\n${renderResourceConstantFields(model.names)}\\n },`)\n .join('\\n');\n return `const generatedRobotResources: GeneratedRobotResources = {\\n${entries}\\n};`;\n}\n\nfunction renderResourceConstantFields(names: Record<RegisterKey, Set<string>>): string {\n return REGISTER_KEYS\n .map((key) => ` ${key}: ${renderResourceObject(names[key])},`)\n .join('\\n');\n}\n\nfunction renderResourceObject(values: Set<string>): string {\n const entries = sortedValues(values)\n .map((value) => ` ${quoteProperty(value)}: '${escapeTs(value)}',`);\n return entries.length > 0 ? `{\\n${entries.join('\\n')}\\n }` : '{}';\n}\n\nfunction renderRobotFields(names: Record<RegisterKey, Set<string>>): string {\n return REGISTER_KEYS\n .map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)\n .join('\\n');\n}\n\nfunction renderUnion(values: Set<string>): string {\n return sortedValues(values).map((value) => `'${escapeTs(value)}'`).join(' | ');\n}\n\nfunction renderNamespaceAliases(models: readonly ModelEntry[]): string {\n const namespaces: Record<RegisterKey, string> = {\n actuators: 'RobotActuators',\n sensors: 'RobotSensors',\n bodies: 'RobotBodies',\n joints: 'RobotJoints',\n sites: 'RobotSites',\n geoms: 'RobotGeoms',\n keyframes: 'RobotKeyframes',\n };\n\n const blocks = REGISTER_KEYS\n .map((key) => {\n const aliases = models\n .filter((model) => isIdentifier(model.id))\n .map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);\n if (aliases.length === 0) return '';\n return ` export namespace ${namespaces[key]} {\\n${aliases.join('\\n')}\\n }`;\n })\n .filter(Boolean)\n .join('\\n\\n');\n\n return blocks ? `${blocks}\\n` : '';\n}\n\nfunction escapeTs(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction sortedValues(values: Set<string>): string[] {\n return [...values].sort((a, b) => a.localeCompare(b));\n}\n\nfunction quoteProperty(value: string): string {\n return isIdentifier(value) ? value : `'${escapeTs(value)}'`;\n}\n\nfunction isIdentifier(value: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);\n}\n\nfunction toPosixPath(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction shouldInjectRegisterImport(id: string, root: string, generatedRegister: string): boolean {\n if (generatedRegister.endsWith('.d.ts')) return false;\n const file = stripQuery(id);\n if (!/\\.[cm]?[jt]sx?$/.test(file)) return false;\n if (file.includes(`${path.sep}node_modules${path.sep}`)) return false;\n const absolute = path.resolve(file);\n if (absolute === generatedRegister) return false;\n return absolute.startsWith(root);\n}\n\nfunction renderGeneratedImport(id: string, generatedRegister: string): string {\n const fromDir = path.dirname(stripQuery(id));\n let relative = toPosixPath(path.relative(fromDir, generatedRegister));\n if (!relative.startsWith('.')) relative = `./${relative}`;\n return `import '${relative}';`;\n}\n\nfunction stripQuery(id: string): string {\n return id.split('?')[0];\n}\n\nfunction normalizeModels(input: ModelInput): ModelEntry[] {\n if (typeof input === 'string') return [createModelEntry(deriveModelId(input), input)];\n if (Array.isArray(input)) return input.map((file) => createModelEntry(deriveModelId(file), file));\n return Object.entries(input).map(([id, file]) => createModelEntry(sanitizeModelId(id), file));\n}\n\nfunction createModelEntry(id: string, file: string): ModelEntry {\n return { id, file, names: createEmptyNames() };\n}\n\nfunction deriveModelId(file: string): string {\n const normalized = file.replace(/\\\\/g, '/');\n const parts = normalized.split('/').filter(Boolean);\n const filename = parts.at(-1) ?? 'model';\n const parent = parts.length > 1 ? parts.at(-2) : undefined;\n const base = filename.replace(/\\.(xml|mjcf|urdf)$/i, '');\n return sanitizeModelId(parent && ['scene', 'model', 'robot'].includes(base.toLowerCase()) ? parent : base);\n}\n\nfunction sanitizeModelId(value: string): string {\n const sanitized = value.replace(/[^A-Za-z0-9_$]/g, '_').replace(/^[^A-Za-z_$]+/, '');\n return sanitized || 'model';\n}\n\nfunction mergeNames(target: Record<RegisterKey, Set<string>>, source: Record<RegisterKey, Set<string>>) {\n for (const key of REGISTER_KEYS) {\n for (const value of source[key]) target[key].add(value);\n }\n}\n\nfunction shouldRegenerate(file: string, watchedFiles: string[], models: readonly ModelEntry[], root: string): boolean {\n const absolute = path.resolve(file);\n if (watchedFiles.includes(absolute)) return true;\n if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;\n const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));\n return modelDirs.some((dir) => absolute.startsWith(dir));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "8.7.0",
3
+ "version": "8.9.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -145,13 +145,6 @@ export type {
145
145
  RegisteredRobotMap,
146
146
  RobotResource,
147
147
  Robots,
148
- RobotActuators,
149
- RobotSensors,
150
- RobotBodies,
151
- RobotJoints,
152
- RobotSites,
153
- RobotGeoms,
154
- RobotKeyframes,
155
148
  Actuators,
156
149
  Sensors,
157
150
  Bodies,
@@ -161,6 +154,18 @@ export type {
161
154
  Keyframes,
162
155
  } from './types';
163
156
 
157
+ export {
158
+ registerRobotResources,
159
+ RobotResources,
160
+ RobotActuators,
161
+ RobotSensors,
162
+ RobotBodies,
163
+ RobotJoints,
164
+ RobotSites,
165
+ RobotGeoms,
166
+ RobotKeyframes,
167
+ } from './types';
168
+
164
169
  // Re-export MuJoCo types for convenience
165
170
  export type { MujocoModule, MujocoModel, MujocoData, MujocoContact, MujocoContactArray } from './types';
166
171
  export { getContact } from './types';
package/src/types.ts CHANGED
@@ -55,6 +55,86 @@ export type RobotSites<TRobot extends string> = RobotResource<TRobot, 'sites'>;
55
55
  export type RobotGeoms<TRobot extends string> = RobotResource<TRobot, 'geoms'>;
56
56
  export type RobotKeyframes<TRobot extends string> = RobotResource<TRobot, 'keyframes'>;
57
57
 
58
+ export type RegisterResourceKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';
59
+ export type RobotResourceObject<TRobot extends string, TKey extends RegisterResourceKey> =
60
+ string extends RobotResource<TRobot, TKey>
61
+ ? Record<string, string>
62
+ : { readonly [K in RobotResource<TRobot, TKey>]: K };
63
+ export type RobotResourceCategory<TKey extends RegisterResourceKey> =
64
+ string extends Robots
65
+ ? Record<string, Record<string, string>>
66
+ : { readonly [TRobot in Robots]: RobotResourceObject<TRobot, TKey> };
67
+ export type RobotResourceRegistry =
68
+ string extends Robots
69
+ ? Record<string, Record<RegisterResourceKey, Record<string, string>>>
70
+ : { readonly [TRobot in Robots]: { readonly [TKey in RegisterResourceKey]: RobotResourceObject<TRobot, TKey> } };
71
+
72
+ type RuntimeRobotResources = Record<string, Record<RegisterResourceKey, Record<string, string>>>;
73
+ type RuntimeRobotResourceRegistration = Readonly<Record<string, Readonly<Record<RegisterResourceKey, Readonly<Record<string, string>>>>>>;
74
+
75
+ const runtimeRobotResources: RuntimeRobotResources = {};
76
+ const REGISTER_RESOURCE_KEYS: RegisterResourceKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];
77
+
78
+ function createEmptyRuntimeResources(): Record<RegisterResourceKey, Record<string, string>> {
79
+ return {
80
+ actuators: {},
81
+ sensors: {},
82
+ bodies: {},
83
+ joints: {},
84
+ sites: {},
85
+ geoms: {},
86
+ keyframes: {},
87
+ };
88
+ }
89
+
90
+ export function registerRobotResources(resources: RuntimeRobotResourceRegistration): void {
91
+ for (const [robot, robotResources] of Object.entries(resources)) {
92
+ const existing = runtimeRobotResources[robot] ?? createEmptyRuntimeResources();
93
+ for (const key of REGISTER_RESOURCE_KEYS) {
94
+ existing[key] = { ...existing[key], ...(robotResources[key] ?? {}) };
95
+ }
96
+ runtimeRobotResources[robot] = existing;
97
+ }
98
+ }
99
+
100
+ function createResourceCategory<TKey extends RegisterResourceKey>(key: TKey): RobotResourceCategory<TKey> {
101
+ return new Proxy({}, {
102
+ get(_target, robot) {
103
+ if (typeof robot !== 'string') return undefined;
104
+ return runtimeRobotResources[robot]?.[key] ?? {};
105
+ },
106
+ ownKeys() {
107
+ return Reflect.ownKeys(runtimeRobotResources);
108
+ },
109
+ getOwnPropertyDescriptor(_target, robot) {
110
+ if (typeof robot !== 'string' || !(robot in runtimeRobotResources)) return undefined;
111
+ return { enumerable: true, configurable: true };
112
+ },
113
+ }) as RobotResourceCategory<TKey>;
114
+ }
115
+
116
+ export const RobotResources: RobotResourceRegistry = new Proxy(runtimeRobotResources, {
117
+ get(target, robot) {
118
+ if (typeof robot !== 'string') return undefined;
119
+ return target[robot] ?? createEmptyRuntimeResources();
120
+ },
121
+ ownKeys(target) {
122
+ return Reflect.ownKeys(target);
123
+ },
124
+ getOwnPropertyDescriptor(target, robot) {
125
+ if (typeof robot !== 'string' || !(robot in target)) return undefined;
126
+ return { enumerable: true, configurable: true };
127
+ },
128
+ }) as RobotResourceRegistry;
129
+
130
+ export const RobotActuators: RobotResourceCategory<'actuators'> = createResourceCategory('actuators');
131
+ export const RobotSensors: RobotResourceCategory<'sensors'> = createResourceCategory('sensors');
132
+ export const RobotBodies: RobotResourceCategory<'bodies'> = createResourceCategory('bodies');
133
+ export const RobotJoints: RobotResourceCategory<'joints'> = createResourceCategory('joints');
134
+ export const RobotSites: RobotResourceCategory<'sites'> = createResourceCategory('sites');
135
+ export const RobotGeoms: RobotResourceCategory<'geoms'> = createResourceCategory('geoms');
136
+ export const RobotKeyframes: RobotResourceCategory<'keyframes'> = createResourceCategory('keyframes');
137
+
58
138
  export type Actuators = Register extends { actuators: infer T extends string } ? T : string;
59
139
  export type Sensors = Register extends { sensors: infer T extends string } ? T : string;
60
140
  export type Bodies = Register extends { bodies: infer T extends string } ? T : string;
package/src/vite.ts CHANGED
@@ -17,7 +17,7 @@ type ViteServer = {
17
17
  export interface MujocoReactPluginOptions {
18
18
  /** Entry MJCF/URDF files to scan. Prefer a record for stable per-robot type names. */
19
19
  models: ModelInput;
20
- /** Generated declaration file. Defaults to `src/mujoco-register.gen.d.ts`. */
20
+ /** Generated resource module. Defaults to `src/mujoco-register.gen.ts`. */
21
21
  generatedRegister?: string;
22
22
  /** Module name to augment. Defaults to `mujoco-react`. */
23
23
  moduleName?: string;
@@ -65,7 +65,7 @@ function createEmptyNames(): Record<RegisterKey, Set<string>> {
65
65
  export function mujocoReact(options: MujocoReactPluginOptions) {
66
66
  const models = normalizeModels(options.models);
67
67
  let root = process.cwd();
68
- let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.d.ts';
68
+ let generatedRegister = options.generatedRegister ?? 'src/mujoco-register.gen.ts';
69
69
  let watchedFiles: string[] = [];
70
70
 
71
71
  async function generate() {
@@ -90,6 +90,10 @@ export function mujocoReact(options: MujocoReactPluginOptions) {
90
90
  root = config.root;
91
91
  generatedRegister = path.resolve(root, generatedRegister);
92
92
  },
93
+ transform(code: string, id: string) {
94
+ if (!shouldInjectRegisterImport(id, root, generatedRegister)) return;
95
+ return `${renderGeneratedImport(id, generatedRegister)}\n${code}`;
96
+ },
93
97
  async buildStart(this: { addWatchFile?: (file: string) => void }) {
94
98
  const result = await generate();
95
99
  for (const file of result.files) this.addWatchFile?.(file);
@@ -122,6 +126,7 @@ export async function generateMujocoRegister(
122
126
  const models = normalizeModels(options.models);
123
127
  const names = createEmptyNames();
124
128
  const seen = new Set<string>();
129
+ const declarationsOnly = out.endsWith('.d.ts');
125
130
 
126
131
  for (const model of models) {
127
132
  await scanModel(path.resolve(root, model.file), root, seen, model.names);
@@ -129,7 +134,7 @@ export async function generateMujocoRegister(
129
134
  }
130
135
 
131
136
  await mkdir(path.dirname(out), { recursive: true });
132
- await writeFile(out, renderRegister(moduleName, names, models), 'utf8');
137
+ await writeFile(out, renderRegister(moduleName, names, models, declarationsOnly), 'utf8');
133
138
 
134
139
  return {
135
140
  out,
@@ -200,30 +205,74 @@ function readAttr(attrs: string, attr: string): string | undefined {
200
205
  function renderRegister(
201
206
  moduleName: string,
202
207
  names: Record<RegisterKey, Set<string>>,
203
- models: readonly ModelEntry[]
208
+ models: readonly ModelEntry[],
209
+ declarationsOnly: boolean
204
210
  ): string {
205
211
  const fields = REGISTER_KEYS
206
212
  .filter((key) => names[key].size > 0)
207
213
  .map((key) => ` ${key}: ${renderUnion(names[key])};`);
208
214
  const robots = models
209
215
  .map((model) => ` ${quoteProperty(model.id)}: {\n${renderRobotFields(model.names)}\n };`);
216
+ const namespaceAliases = renderNamespaceAliases(models);
217
+ const registerImport = declarationsOnly ? '' : `import { registerRobotResources } from '${moduleName}';\n`;
218
+ const resources = declarationsOnly ? '' : `${renderResourceTypes(models)}\n\n${renderResourceConstants(models)}\n\nregisterRobotResources(generatedRobotResources);\n\n`;
210
219
 
211
220
  return `// Auto-generated by mujoco-react. Do not edit.
212
221
  // Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
213
222
 
214
- import 'mujoco-react';
223
+ ${registerImport}import type { RobotResource } from '${moduleName}';
215
224
 
216
- declare module '${moduleName}' {
225
+ ${resources}declare module '${moduleName}' {
217
226
  interface Register {
218
227
  robots: {
219
228
  ${robots.join('\n')}
220
229
  };
221
230
  ${fields.join('\n')}
222
231
  }
232
+
233
+ ${namespaceAliases}
223
234
  }
224
235
  `;
225
236
  }
226
237
 
238
+ function renderResourceTypes(models: readonly ModelEntry[]): string {
239
+ const modelTypes = models
240
+ .map((model) => ` readonly ${quoteProperty(model.id)}: {\n${renderResourceTypeFields(model.names)}\n };`)
241
+ .join('\n');
242
+ return `type GeneratedRobotResources = {\n${modelTypes}\n};`;
243
+ }
244
+
245
+ function renderResourceTypeFields(names: Record<RegisterKey, Set<string>>): string {
246
+ return REGISTER_KEYS
247
+ .map((key) => ` readonly ${key}: ${renderResourceObjectType(names[key])};`)
248
+ .join('\n');
249
+ }
250
+
251
+ function renderResourceObjectType(values: Set<string>): string {
252
+ const entries = sortedValues(values)
253
+ .map((value) => ` readonly ${quoteProperty(value)}: '${escapeTs(value)}';`);
254
+ return entries.length > 0 ? `{\n${entries.join('\n')}\n }` : '{}';
255
+ }
256
+
257
+ function renderResourceConstants(models: readonly ModelEntry[]): string {
258
+ const entries = models
259
+ .map((model) => ` ${quoteProperty(model.id)}: {\n${renderResourceConstantFields(model.names)}\n },`)
260
+ .join('\n');
261
+ return `const generatedRobotResources: GeneratedRobotResources = {\n${entries}\n};`;
262
+ }
263
+
264
+ function renderResourceConstantFields(names: Record<RegisterKey, Set<string>>): string {
265
+ return REGISTER_KEYS
266
+ .map((key) => ` ${key}: ${renderResourceObject(names[key])},`)
267
+ .join('\n');
268
+ }
269
+
270
+ function renderResourceObject(values: Set<string>): string {
271
+ const entries = sortedValues(values)
272
+ .map((value) => ` ${quoteProperty(value)}: '${escapeTs(value)}',`);
273
+ return entries.length > 0 ? `{\n${entries.join('\n')}\n }` : '{}';
274
+ }
275
+
227
276
  function renderRobotFields(names: Record<RegisterKey, Set<string>>): string {
228
277
  return REGISTER_KEYS
229
278
  .map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)
@@ -231,15 +280,73 @@ function renderRobotFields(names: Record<RegisterKey, Set<string>>): string {
231
280
  }
232
281
 
233
282
  function renderUnion(values: Set<string>): string {
234
- return [...values].sort((a, b) => a.localeCompare(b)).map((value) => `'${escapeTs(value)}'`).join(' | ');
283
+ return sortedValues(values).map((value) => `'${escapeTs(value)}'`).join(' | ');
284
+ }
285
+
286
+ function renderNamespaceAliases(models: readonly ModelEntry[]): string {
287
+ const namespaces: Record<RegisterKey, string> = {
288
+ actuators: 'RobotActuators',
289
+ sensors: 'RobotSensors',
290
+ bodies: 'RobotBodies',
291
+ joints: 'RobotJoints',
292
+ sites: 'RobotSites',
293
+ geoms: 'RobotGeoms',
294
+ keyframes: 'RobotKeyframes',
295
+ };
296
+
297
+ const blocks = REGISTER_KEYS
298
+ .map((key) => {
299
+ const aliases = models
300
+ .filter((model) => isIdentifier(model.id))
301
+ .map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);
302
+ if (aliases.length === 0) return '';
303
+ return ` export namespace ${namespaces[key]} {\n${aliases.join('\n')}\n }`;
304
+ })
305
+ .filter(Boolean)
306
+ .join('\n\n');
307
+
308
+ return blocks ? `${blocks}\n` : '';
235
309
  }
236
310
 
237
311
  function escapeTs(value: string): string {
238
312
  return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
239
313
  }
240
314
 
315
+ function sortedValues(values: Set<string>): string[] {
316
+ return [...values].sort((a, b) => a.localeCompare(b));
317
+ }
318
+
241
319
  function quoteProperty(value: string): string {
242
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : `'${escapeTs(value)}'`;
320
+ return isIdentifier(value) ? value : `'${escapeTs(value)}'`;
321
+ }
322
+
323
+ function isIdentifier(value: string): boolean {
324
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
325
+ }
326
+
327
+ function toPosixPath(value: string): string {
328
+ return value.split(path.sep).join('/');
329
+ }
330
+
331
+ function shouldInjectRegisterImport(id: string, root: string, generatedRegister: string): boolean {
332
+ if (generatedRegister.endsWith('.d.ts')) return false;
333
+ const file = stripQuery(id);
334
+ if (!/\.[cm]?[jt]sx?$/.test(file)) return false;
335
+ if (file.includes(`${path.sep}node_modules${path.sep}`)) return false;
336
+ const absolute = path.resolve(file);
337
+ if (absolute === generatedRegister) return false;
338
+ return absolute.startsWith(root);
339
+ }
340
+
341
+ function renderGeneratedImport(id: string, generatedRegister: string): string {
342
+ const fromDir = path.dirname(stripQuery(id));
343
+ let relative = toPosixPath(path.relative(fromDir, generatedRegister));
344
+ if (!relative.startsWith('.')) relative = `./${relative}`;
345
+ return `import '${relative}';`;
346
+ }
347
+
348
+ function stripQuery(id: string): string {
349
+ return id.split('?')[0];
243
350
  }
244
351
 
245
352
  function normalizeModels(input: ModelInput): ModelEntry[] {