Files
orca/config/scripts/generate-monaco-associations.mjs
T
Jinwoo HongandE2E Test 03e9a03e9e feat(layout-owner): main opens CLI and phone files itself, in the window's live group (dark) (#31123)
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 08:48:53 -04:00

92 lines
3.4 KiB
JavaScript

import { readFileSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import ts from 'typescript-api'
const require = createRequire(import.meta.url)
const editorEntry = require.resolve('monaco-editor/esm/vs/editor/editor.main.js')
export const associationsPath = fileURLToPath(
new URL('../../src/shared/editor-language/monaco-language-associations.json', import.meta.url)
)
// Monaco omits common Ruby task, template and configuration files.
const rubyAssociations = {
extensions: ['.rake', '.ru', '.jbuilder', '.thor'],
filenames: ['Guardfile', 'Capfile', 'Podfile', 'Brewfile', 'Vagrantfile']
}
// Read registration metadata without importing Monaco or executing its grammar loaders.
export function readMonacoAssociations() {
const entry = ts.createSourceFile(
editorEntry,
readFileSync(editorEntry, 'utf8'),
ts.ScriptTarget.Latest
)
const files = entry.statements
.filter(ts.isImportDeclaration)
.map((node) => node.moduleSpecifier)
.filter(ts.isStringLiteral)
.map((node) => node.text)
.filter((path) => path.endsWith('.contribution.js'))
const registrations = []
for (const file of files) {
const path = resolve(dirname(editorEntry), file)
const source = ts.createSourceFile(path, readFileSync(path, 'utf8'), ts.ScriptTarget.Latest)
function visit(node) {
if (
ts.isCallExpression(node) &&
['registerLanguage', 'languages.register'].includes(node.expression.getText(source))
) {
const [argument] = node.arguments
if (!argument || !ts.isObjectLiteralExpression(argument)) {
throw new Error(`Unsupported Monaco registration in ${file}`)
}
const metadata = { id: '', extensions: [], filenames: [] }
for (const property of argument.properties) {
if (!ts.isPropertyAssignment(property)) {
continue
}
const key = property.name.getText(source)
if (!['id', 'extensions', 'filenames'].includes(key)) {
continue
}
const value = property.initializer
if (key === 'id' && ts.isStringLiteral(value)) {
metadata.id = value.text
} else if (key !== 'id' && ts.isArrayLiteralExpression(value)) {
metadata[key] = value.elements.map((element) => {
if (!ts.isStringLiteral(element)) {
throw new Error(`Nonliteral association in ${file}`)
}
return element.text
})
} else {
throw new Error(`Unsupported ${key} in ${file}`)
}
}
if (!metadata.id) {
throw new Error(`Missing language id in ${file}`)
}
if (metadata.id === 'ruby') {
metadata.extensions = [
...new Set([...metadata.extensions, ...rubyAssociations.extensions])
]
metadata.filenames = [...new Set([...metadata.filenames, ...rubyAssociations.filenames])]
}
registrations.push(metadata)
}
ts.forEachChild(node, visit)
}
visit(source)
}
if (registrations.length < 80) {
throw new Error('Monaco registration entry point changed')
}
return registrations
}
if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) {
writeFileSync(associationsPath, `${JSON.stringify(readMonacoAssociations(), null, 2)}\n`)
}