-
Notifications
You must be signed in to change notification settings - Fork 235
Expand file tree
/
Copy pathtypespec.ts
More file actions
145 lines (132 loc) · 4.66 KB
/
typespec.ts
File metadata and controls
145 lines (132 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { resolvePath, getDirectoryPath, ResolveCompilerOptionsOptions, formatDiagnostic } from "@typespec/compiler";
import { ModuleResolutionResult, resolveModule, ResolveModuleHost } from "@typespec/compiler/module-resolver";
import { Logger } from "./log.js";
import { readFile, readdir, realpath, stat } from "fs/promises";
import { pathToFileURL } from "url";
export interface TspLocation {
directory: string;
commit: string;
repo: string;
additionalDirectories?: string[];
}
export function resolveTspConfigUrl(configUrl: string): {
resolvedUrl: string;
commit: string;
repo: string;
path: string;
} {
let resolvedConfigUrl = configUrl;
const res = configUrl.match('^https://(?<urlRoot>github|raw.githubusercontent).com/(?<repo>[^/]*/azure-rest-api-specs(-pr)?)/(tree/|blob/)?(?<commit>[0-9a-f]{40})/(?<path>.*)/tspconfig.yaml$')
if (res && res.groups) {
if (res.groups["urlRoot"]! === "github") {
resolvedConfigUrl = configUrl.replace("github.com", "raw.githubusercontent.com");
resolvedConfigUrl = resolvedConfigUrl.replace("/blob/", "/");
}
return {
resolvedUrl: resolvedConfigUrl,
commit: res.groups!["commit"]!,
repo: res.groups!["repo"]!,
path: res.groups!["path"]!,
}
} else {
throw new Error(`Invalid tspconfig.yaml url: ${configUrl}`);
}
}
export async function discoverMainFile(srcDir: string): Promise<string> {
Logger.debug(`Discovering entry file in ${srcDir}`)
let entryTsp = "";
const files = await readdir(srcDir, {recursive: true });
for (const file of files) {
if (file.includes("client.tsp") || file.includes("main.tsp")) {
entryTsp = file;
Logger.debug(`Found entry file: ${entryTsp}`);
return entryTsp;
}
};
throw new Error(`No main.tsp or client.tsp found`);
}
export async function compileTsp({
emitterPackage,
outputPath,
resolvedMainFilePath,
additionalEmitterOptions,
saveInputs,
}: {
emitterPackage: string;
outputPath: string;
resolvedMainFilePath: string;
additionalEmitterOptions?: string;
saveInputs?: boolean;
}): Promise<boolean> {
const parsedEntrypoint = getDirectoryPath(resolvedMainFilePath);
const { compile, NodeHost, resolveCompilerOptions } = await importTsp(parsedEntrypoint);
const outputDir = resolvePath(outputPath);
const overrideOptions: Record<string, Record<string, string>> = {
[emitterPackage]: {
"emitter-output-dir": outputDir,
},
};
const emitterOverrideOptions = overrideOptions[emitterPackage] ?? {[emitterPackage]: {}};
if (saveInputs) {
emitterOverrideOptions["save-inputs"] = "true";
}
if (additionalEmitterOptions) {
additionalEmitterOptions.split(";").forEach((option) => {
const [key, value] = option.split("=");
if (key && value) {
emitterOverrideOptions[key] = value;
}
});
}
const overrides: Partial<ResolveCompilerOptionsOptions["overrides"]> = {
outputDir,
emit: [emitterPackage],
options: overrideOptions,
};
Logger.info(`Compiling tsp using ${emitterPackage}...`);
const [options, diagnostics] = await resolveCompilerOptions(NodeHost, {
cwd: process.cwd(),
entrypoint: resolvedMainFilePath,
overrides,
});
Logger.debug(`Compiler options: ${JSON.stringify(options)}`);
if (diagnostics.length > 0) {
// This should not happen, but if it does, we should log it.
Logger.error("Diagnostics were reported while resolving compiler options...")
diagnostics.forEach((diagnostic) => { Logger.error(formatDiagnostic(diagnostic)); });
return false;
}
const program = await compile(NodeHost, resolvedMainFilePath, options);
if (program.diagnostics.length > 0) {
Logger.error("Diagnostics were reported during compilation...");
program.diagnostics.forEach((diagnostic) => { Logger.error(formatDiagnostic(diagnostic)); });
return false;
} else {
Logger.success("generation complete");
}
return true;
}
export async function importTsp(baseDir: string): Promise<typeof import("@typespec/compiler")> {
try {
const host: ResolveModuleHost = {
realpath,
readFile: async (path: string) => await readFile(path, "utf-8"),
stat,
};
const resolved: ModuleResolutionResult = await resolveModule(host, "@typespec/compiler", {
baseDir,
});
Logger.info(`Resolved path: ${resolved.path}`);
if (resolved.type === "module") {
return import(pathToFileURL(resolved.mainFile).toString());
}
return import(pathToFileURL(resolved.path).toString());
} catch (err: any) {
if (err.code === "MODULE_NOT_FOUND") {
// Resolution from cwd failed: use current package.
return import("@typespec/compiler");
} else {
throw err;
}
}
}