microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f5be43b02a71f3cdbe563af80c287f6b4567c119

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

eng/scripts/helpers.js

213lines · modecode

1import { spawn, spawnSync } from "child_process";
2import { statSync, readdirSync, lstatSync, readFileSync } from "fs";
3import { dirname, join, resolve } from "path";
4import { fileURLToPath } from "url";
5
6function read(filename) {
7 const txt = readFileSync(filename, "utf8")
8 .replace(/\r/gm, "")
9 .replace(/\n/gm, "«")
10 .replace(/\/\*.*?\*\//gm, "")
11 .replace(/«/gm, "\n")
12 .replace(/\s+\/\/.*/g, "");
13 return JSON.parse(txt);
14}
15
16export const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
17export const prettier = resolve(repoRoot, "packages/compiler/node_modules/.bin/prettier");
18export const tsc = resolve(repoRoot, "packages/compiler/node_modules/.bin/tsc");
19export const autorest = resolve(repoRoot, "eng/scripts/node_modules/.bin/autorest");
20
21const rush = read(`${repoRoot}/rush.json`);
22
23export function forEachProject(onEach) {
24 // load all the projects
25 for (const each of rush.projects) {
26 const packageName = each.packageName;
27 const projectFolder = resolve(`${repoRoot}/${each.projectFolder}`);
28 const project = JSON.parse(readFileSync(`${projectFolder}/package.json`));
29 onEach(packageName, projectFolder, project);
30 }
31}
32
33export function npmForEach(cmd, options) {
34 forEachProject((name, location, project) => {
35 if (cmd === "test-official" && !project.scripts[cmd] && project.scripts["test"]) {
36 const pj = join(location, "package.json");
37 throw new Error(`${pj} has a 'test' script, but no 'test-official' script for CI.`);
38 }
39
40 if (project.scripts[cmd] || cmd === "pack") {
41 const args = cmd === "pack" ? [cmd] : ["run", cmd];
42 run("npm", args, { cwd: location, ...options });
43 }
44 });
45}
46
47// We could use { shell: true } to let Windows find .cmd, but that causes other issues.
48// It breaks ENOENT checking for command-not-found and also handles command/args with spaces
49// poorly.
50const isCmdOnWindows = ["rush", "npm", "code", "code-insiders", tsc, prettier, autorest];
51
52export class CommandFailedError extends Error {
53 constructor(msg, proc) {
54 super(msg);
55 this.proc = proc;
56 }
57}
58
59export function run(command, args, options) {
60 console.log();
61 console.log(`> ${command} ${args.join(" ")}`);
62
63 options = {
64 stdio: "inherit",
65 sync: true,
66 throwOnNonZeroExit: true,
67 ...options,
68 };
69
70 if (process.platform === "win32" && isCmdOnWindows.includes(command)) {
71 command += ".cmd";
72 }
73
74 const proc = (options.sync ? spawnSync : spawn)(command, args, options);
75 if (proc.error) {
76 if (options.ignoreCommandNotFound && proc.error.code === "ENOENT") {
77 console.log(`Skipped: Command \`${command}\` not found.`);
78 } else {
79 throw proc.error;
80 }
81 } else if (options.throwOnNonZeroExit && proc.status !== undefined && proc.status !== 0) {
82 throw new CommandFailedError(
83 `Command \`${command} ${args.join(" ")}\` failed with exit code ${proc.status}`,
84 proc
85 );
86 }
87
88 return proc;
89}
90
91export function runPrettier(...args) {
92 run(
93 prettier,
94 [
95 ...args,
96 "--config",
97 ".prettierrc.json",
98 "--ignore-path",
99 ".prettierignore",
100 "**/*.{ts,js,cjs,mjs,json,yml,yaml}",
101 ],
102 {
103 cwd: repoRoot,
104 }
105 );
106}
107
108export function clearScreen() {
109 process.stdout.write("\x1bc");
110}
111
112export function runWatch(watch, dir, build, options) {
113 let lastBuildTime;
114 dir = resolve(dir);
115
116 // We need to wait for directory to be created before watching it. This deals
117 // with races between watchers where one watcher must create a directory
118 // before another can watch it.
119 //
120 // For example, we can't watch for tmlanguage.js changes if the source watcher
121 // hasn't even created the directory in which tmlanguage.js will be written.
122 try {
123 statSync(dir);
124 } catch (err) {
125 if (err.code === "ENOENT") {
126 waitForDirectoryCreation();
127 return;
128 }
129 throw err;
130 }
131
132 // Directory already exists: we can start watching right away.
133 start();
134
135 function waitForDirectoryCreation() {
136 let dirCreated = false;
137 let parentDir = dirname(dir);
138 logWithTime(`Waiting for ${dir} to be created.`);
139
140 watch.createMonitor(parentDir, "created", (monitor) => {
141 monitor.on("created", (file) => {
142 if (!dirCreated && file === dir) {
143 dirCreated = true; // defend against duplicate events.
144 monitor.stop();
145 start();
146 }
147 });
148 });
149 }
150
151 function start() {
152 // build once up-front
153 runBuild();
154
155 // then build again on any change
156 watch.createMonitor(dir, { interval: 0.2, ...options }, (monitor) => {
157 monitor.on("created", (file) => runBuild(`${file} created`));
158 monitor.on("removed", (file) => runBuild(`${file} removed`));
159 monitor.on("changed", (file) => runBuild(`${file} changed`, monitor.files[file]?.mtime));
160 });
161 }
162
163 function runBuild(changeDescription, changeTime) {
164 runBuildAsync(changeDescription, changeTime).catch((err) => {
165 console.error(err.stack);
166 process.exit(1);
167 });
168 }
169
170 async function runBuildAsync(changeDescription, changeTime) {
171 if (changeTime && lastBuildTime && changeTime < lastBuildTime) {
172 // Don't rebuild if a change happened before the last build kicked off.
173 // Defends against duplicate events and building more than once when a
174 // bunch of files are changed at the same time.
175 return;
176 }
177
178 lastBuildTime = new Date();
179 if (changeDescription) {
180 clearScreen();
181 logWithTime(`File change detected: ${changeDescription}. Running build.`);
182 } else {
183 logWithTime("Starting build in watch mode.");
184 }
185
186 try {
187 await build();
188 logWithTime("Build succeeded. Waiting for file changes.");
189 } catch (err) {
190 console.error(err.stack);
191 logWithTime(`Build failed. Waiting for file changes.`);
192 }
193 }
194}
195
196export function logWithTime(msg) {
197 const time = new Date().toLocaleTimeString();
198 console.log(`[${time}] ${msg}`);
199}
200
201export function scanSwaggers(root) {
202 const files = [];
203 for (const file of readdirSync(root)) {
204 const fullPath = root + "/" + file;
205 if (lstatSync(fullPath).isDirectory()) {
206 scanSwaggers(fullPath).forEach((x) => files.push(x));
207 }
208 if (file === "openapi.json") {
209 files.push(fullPath);
210 }
211 }
212 return files;
213}
214