Skip to content

Commit 359b167

Browse files
committed
feat: create script to convert TS files to OSTS
1 parent ceadde5 commit 359b167

3 files changed

Lines changed: 158 additions & 1 deletion

File tree

convert-to-osts.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2+
import { join, resolve } from 'node:path';
3+
4+
const SCRIPTS_DIR = resolve(import.meta.dirname, 'scripts');
5+
6+
interface Param {
7+
name: string;
8+
type: string;
9+
}
10+
11+
interface JsonSchemaType {
12+
type: string;
13+
items?: JsonSchemaType;
14+
}
15+
16+
interface OstsFile {
17+
version: string;
18+
body: string;
19+
description: string;
20+
noCodeMetadata: string;
21+
parameterInfo: string;
22+
apiInfo: string;
23+
}
24+
25+
function tsTypeToJsonSchema(tsType: string): JsonSchemaType {
26+
const type = tsType.trim();
27+
28+
if (type.endsWith('[]')) {
29+
return { type: 'array', items: tsTypeToJsonSchema(type.slice(0, -2)) };
30+
}
31+
32+
switch (type) {
33+
case 'string': return { type: 'string' };
34+
case 'number': return { type: 'number' };
35+
case 'boolean': return { type: 'boolean' };
36+
default: return { type: 'string' };
37+
}
38+
}
39+
40+
function parseMainParams(code: string): Param[] {
41+
const match = code.match(/function\s+main\s*\(([\s\S]*?)\)\s*[:{]/);
42+
if (!match) throw new Error('No `main` function found.');
43+
44+
const paramsStr = match[1];
45+
const rawParams: string[] = [];
46+
let depth = 0;
47+
let current = '';
48+
49+
for (const ch of paramsStr) {
50+
if ('<(['.includes(ch)) depth++;
51+
else if ('>)]'.includes(ch)) depth--;
52+
else if (ch === ',' && depth === 0) {
53+
const trimmed = current.trim();
54+
if (trimmed) rawParams.push(trimmed);
55+
current = '';
56+
continue;
57+
}
58+
current += ch;
59+
}
60+
const last = current.trim();
61+
if (last) rawParams.push(last);
62+
63+
return rawParams.map(param => {
64+
const normalised = param.replace(/\s+/g, ' ').trim();
65+
const colonIdx = normalised.indexOf(':');
66+
if (colonIdx === -1) return { name: normalised, type: 'string' };
67+
return {
68+
name: normalised.slice(0, colonIdx).trim(),
69+
type: normalised.slice(colonIdx + 1).trim(),
70+
};
71+
});
72+
}
73+
74+
function parseDescription(code: string): string {
75+
const jsdocMatch = code.match(/\/\*\*([\s\S]*?)\*\/\s*function\s+main/);
76+
if (!jsdocMatch) return '';
77+
78+
const descLines: string[] = [];
79+
for (const line of jsdocMatch[1].split('\n')) {
80+
const cleaned = line.replace(/^\s*\*\s?/, '').trim();
81+
if (cleaned.startsWith('@')) break;
82+
if (cleaned) descLines.push(cleaned);
83+
}
84+
85+
return descLines.join(' ');
86+
}
87+
88+
function convertToOsts(inputPath: string): void {
89+
const code = readFileSync(inputPath, 'utf8');
90+
const body = code.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
91+
92+
const description = parseDescription(code);
93+
const allParams = parseMainParams(code);
94+
const userParams = allParams.filter(p => p.name !== 'workbook');
95+
96+
const properties: Record<string, JsonSchemaType> = {};
97+
for (const p of userParams) {
98+
properties[p.name] = tsTypeToJsonSchema(p.type);
99+
}
100+
101+
const parameterInfo = {
102+
version: 1,
103+
originalParameterOrder: userParams.map((p, i) => ({ name: p.name, index: i })),
104+
parameterSchema:
105+
userParams.length > 0
106+
? { type: 'object', required: userParams.map(p => p.name), properties }
107+
: { type: 'object', properties: {} },
108+
returnSchema: { type: 'object', properties: {} },
109+
signature: {
110+
comment: '',
111+
parameters: allParams.map(p => ({ name: p.name, comment: '' })),
112+
},
113+
};
114+
115+
const osts: OstsFile = {
116+
version: '0.3.0',
117+
body,
118+
description,
119+
noCodeMetadata: '',
120+
parameterInfo: JSON.stringify(parameterInfo),
121+
apiInfo: JSON.stringify({ variant: 'synchronous', variantVersion: 2 }),
122+
};
123+
124+
const outputPath = inputPath.replace(/\.ts$/, '.osts');
125+
writeFileSync(outputPath, JSON.stringify(osts));
126+
console.log(`Converted: ${outputPath}`);
127+
}
128+
129+
function walkAndConvert(dir: string): void {
130+
for (const entry of readdirSync(dir)) {
131+
const fullPath = join(dir, entry);
132+
if (statSync(fullPath).isDirectory()) {
133+
walkAndConvert(fullPath);
134+
} else if (entry.endsWith('.ts')) {
135+
try {
136+
convertToOsts(fullPath);
137+
} catch (err) {
138+
console.error(`Failed: ${fullPath}${(err as Error).message}`);
139+
process.exitCode = 1;
140+
}
141+
}
142+
}
143+
}
144+
145+
walkAndConvert(SCRIPTS_DIR);

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "excel-office-scripts",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"convert": "bun convert-to-osts.ts"
7+
},
8+
"devDependencies": {
9+
"@types/bun": "latest"
10+
}
11+
}

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
],
77
"noEmit": true,
88
"typeRoots": [
9-
"@types/office-scripts"
9+
"@types/office-scripts",
10+
"node_modules/@types"
1011
]
1112
}
1213
}

0 commit comments

Comments
 (0)