Skip to content

Commit c28c213

Browse files
committed
build: Implement unified error handling in build/bump/release scripts
1 parent fc956d9 commit c28c213

File tree

9 files changed

+408
-353
lines changed

9 files changed

+408
-353
lines changed

scripts/build.ts

+117-196
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
1-
import path from "node:path";
21
import { parseArgs } from "node:util";
32
import builtins from "builtin-modules";
4-
import { $, type BunPlugin } from "bun";
5-
import { ResultAsync, okAsync } from "neverthrow";
3+
import {
4+
$,
5+
type BuildConfig,
6+
type BuildOutput,
7+
type BunFile,
8+
type BunPlugin,
9+
} from "bun";
10+
import { ResultAsync, errAsync, okAsync } from "neverthrow";
611
import type { IPackageJson } from "package-json-type";
712
import { resolveTsPaths } from "resolve-tspaths";
813
import { targetStylesFile } from "./common";
9-
import { BuildError, FileSystemError } from "./errors";
14+
import { Code, ScriptError } from "./errors";
1015
import {
16+
createFolder,
17+
isUndefined,
1118
readJsonFile,
1219
readTextFile,
1320
setupTestVault,
1421
writeFile,
1522
} from "./helpers";
23+
import { InlineGraphQlBunPlugin } from "./plugins/graphql";
24+
import { CompiledHandlebarsTemplateBunPlugin } from "./plugins/handlebars";
25+
import { InlineSqlBunPlugin } from "./plugins/sql";
26+
import { InlineWasmBunPlugin } from "./plugins/wasm";
1627

1728
const { values: args } = parseArgs({
1829
args: Bun.argv,
@@ -37,112 +48,6 @@ const { values: args } = parseArgs({
3748

3849
const outputFolder = "dist";
3950

40-
export const InlineWasmBunPlugin: BunPlugin = {
41-
name: "inline-wasm",
42-
setup(builder) {
43-
// Hook into the "resolve" phase to intercept .wasm imports
44-
builder.onResolve({ filter: /\.wasm$/ }, async (args) => {
45-
// Resolve the .wasm file path relative to the directory of the importing file
46-
const resolvedPath = Bun.resolveSync(
47-
args.path,
48-
path.dirname(args.importer),
49-
);
50-
return { path: resolvedPath, namespace: "wasm" };
51-
});
52-
53-
// Handle the .wasm file loading
54-
builder.onLoad(
55-
{ filter: /\.wasm$/, namespace: "wasm" },
56-
async (args) => {
57-
const wasmFile = await Bun.file(args.path).bytes();
58-
const wasm = Buffer.from(wasmFile).toString("base64");
59-
60-
// Create the inline WebAssembly module
61-
const contents = `
62-
const wasmBinary = Uint8Array.from(atob("${wasm}"), c => c.charCodeAt(0));
63-
export default wasmBinary;
64-
`;
65-
return { contents, loader: "js" };
66-
},
67-
);
68-
},
69-
};
70-
71-
export const InlineSqlBunPlugin: BunPlugin = {
72-
name: "inline-sql",
73-
setup(builder) {
74-
// Hook into the "resolve" phase to intercept .sql imports
75-
builder.onResolve({ filter: /\.sql$/ }, async (args) => {
76-
// Resolve the .sql file path relative to the directory of the importing file
77-
const resolvedPath = Bun.resolveSync(
78-
args.path,
79-
path.dirname(args.importer),
80-
);
81-
return { path: resolvedPath, namespace: "sql" };
82-
});
83-
84-
// Handle the .sql file loading
85-
builder.onLoad({ filter: /\.sql$/, namespace: "sql" }, async (args) => {
86-
const sqlFileContent = await Bun.file(args.path).text();
87-
const contents = `const sqlQuery = \`${sqlFileContent}\`;
88-
export default sqlQuery;`;
89-
return { contents, loader: "js" };
90-
});
91-
},
92-
};
93-
94-
export const InlineGraphQlBunPlugin: BunPlugin = {
95-
name: "inline-graphql",
96-
setup(builder) {
97-
// Hook into the "resolve" phase to intercept .gql imports
98-
builder.onResolve({ filter: /\.gql$/ }, async (args) => {
99-
// Resolve the .gql file path relative to the directory of the importing file
100-
const resolvedPath = Bun.resolveSync(
101-
args.path,
102-
path.dirname(args.importer),
103-
);
104-
return { path: resolvedPath, namespace: "graphql" };
105-
});
106-
107-
// Handle the .gql file loading
108-
builder.onLoad(
109-
{ filter: /\.gql$/, namespace: "graphql" },
110-
async (args) => {
111-
const graphQlQueryFileContent = await Bun.file(
112-
args.path,
113-
).text();
114-
const contents = `const graphQlQuery = \`${graphQlQueryFileContent}\`;
115-
export default graphQlQuery;`;
116-
return { contents, loader: "js" };
117-
},
118-
);
119-
},
120-
};
121-
122-
export const CompiledHandlebarsTemplateBunPlugin: BunPlugin = {
123-
name: "compile-handlebars",
124-
setup(builder) {
125-
builder.onResolve({ filter: /\.hbs$/ }, async (args) => {
126-
const resolvedPath = Bun.resolveSync(
127-
args.path,
128-
path.dirname(args.importer),
129-
);
130-
return { path: resolvedPath, namespace: "handlebars" };
131-
});
132-
133-
builder.onLoad(
134-
{ filter: /\.hbs$/, namespace: "handlebars" },
135-
async (args) => {
136-
const handlebarsFileContent = await Bun.file(args.path).text();
137-
const contents = `import Handlebars from "handlebars";
138-
const graphQlQuery = Handlebars.compile(\`${handlebarsFileContent}\`, { strict: true });
139-
export default graphQlQuery;`;
140-
return { contents, loader: "js" };
141-
},
142-
);
143-
},
144-
};
145-
14651
export interface WasmBuildConfig {
14752
target: "bundler" | "nodejs" | "web" | "no-modules" | "deno";
14853
path: string;
@@ -153,14 +58,14 @@ export function buildWasm(
15358
target: "web",
15459
path: "./pkg",
15560
},
156-
) {
61+
): ResultAsync<number, ScriptError<Code.Build | Code.FileSystem>> {
15762
console.log("Building Rust WASM");
158-
const wasmPackBuild = ResultAsync.fromPromise(
159-
$`wasm-pack build --target ${config.target}`,
160-
() => BuildError.WasmPackBuildFailed,
63+
const wasmPackBuild = ResultAsync.fromThrowable(
64+
(target: string) => $`wasm-pack build --target ${target}`,
65+
() => new ScriptError(Code.Build.WasmPackBuildFailed),
16166
);
16267
return (
163-
wasmPackBuild
68+
wasmPackBuild(config.target)
16469
.andThen(() =>
16570
readJsonFile<IPackageJson>(`${config.path}/package.json`),
16671
)
@@ -180,7 +85,7 @@ export function buildWasm(
18085
);
18186
}
18287

183-
export interface BuildConfig {
88+
export interface PluginBuildConfig {
18489
sourceFolder: string;
18590
entrypoints: {
18691
main: string;
@@ -202,95 +107,110 @@ const defaultBunPlugins = [
202107
CompiledHandlebarsTemplateBunPlugin,
203108
];
204109

205-
export function build(config: BuildConfig) {
206-
const createOutputFolder = ResultAsync.fromPromise(
207-
$`mkdir -p ${config.outputFolder}`,
208-
(error) => {
209-
console.error(`ERROR. ${error}`);
210-
return FileSystemError.FileSystemError;
211-
},
110+
export function buildStyles(
111+
source: BunFile,
112+
destination: BunFile,
113+
): ResultAsync<string, ScriptError<Code.Build>> {
114+
console.log("Building styles");
115+
return ResultAsync.fromThrowable(
116+
(s: BunFile, d: BunFile) => $`grass ${s} --style compressed > ${d}`,
117+
() => new ScriptError(Code.Build.UnableToBuildStylesFiles),
118+
)(source, destination).map((shellOutput) => shellOutput.text());
119+
}
120+
121+
export function buildJavaScript(
122+
config: Partial<PluginBuildConfig>,
123+
): ResultAsync<BuildOutput, ScriptError<Code.Build>> {
124+
if (isUndefined(config.entrypoints)) {
125+
return errAsync(
126+
new ScriptError(Code.Build.UnableToBuildJavaScriptFiles),
127+
);
128+
}
129+
130+
console.log(
131+
`Building JavaScript: ${config.sourceFolder}/${config.entrypoints.main} (output: ${config.outputFolder})`,
132+
);
133+
134+
const buildConfig: BuildConfig = {
135+
entrypoints: [`${config.sourceFolder}/${config.entrypoints.main}`],
136+
outdir: config.outputFolder,
137+
minify: config.minify,
138+
target: "browser",
139+
format: config.format,
140+
plugins: config.useWasm
141+
? [InlineWasmBunPlugin, ...defaultBunPlugins]
142+
: defaultBunPlugins,
143+
drop: config.drop,
144+
sourcemap: config.sourcemap ? "inline" : "none",
145+
external: [
146+
"obsidian",
147+
"electron",
148+
"@electron/remote",
149+
"@codemirror/autocomplete",
150+
"@codemirror/collab",
151+
"@codemirror/commands",
152+
"@codemirror/language",
153+
"@codemirror/lint",
154+
"@codemirror/search",
155+
"@codemirror/state",
156+
"@codemirror/view",
157+
"@lezer/common",
158+
"@lezer/highlight",
159+
"@lezer/lr",
160+
...builtins,
161+
],
162+
};
163+
164+
return ResultAsync.fromThrowable(
165+
(c: BuildConfig) => Bun.build(c),
166+
(error) => new ScriptError(Code.Build.UnableToBuildJavaScriptFiles),
167+
)(buildConfig);
168+
}
169+
170+
export function buildTypeScriptDeclarations(
171+
sourceFolder: string,
172+
outputFolder: string,
173+
): ResultAsync<string, ScriptError<Code.Build>> {
174+
console.log("Building types");
175+
const buildDeclarations = ResultAsync.fromThrowable(
176+
(folder: string) =>
177+
$`bun tsc --noEmit false --emitDeclarationOnly --declaration --outDir ${folder}/types`,
178+
() => new ScriptError(Code.Build.UnableToBuildTypesDeclarations),
212179
);
180+
const resolvePaths = ResultAsync.fromThrowable(
181+
(src: string, out: string) =>
182+
resolveTsPaths({
183+
src,
184+
out: `${out}/types`,
185+
}),
186+
(error) => new ScriptError(Code.Build.UnableToResolveTypeScriptPaths),
187+
);
188+
return buildDeclarations(outputFolder)
189+
.andThrough(() => resolvePaths(sourceFolder, outputFolder))
190+
.map((shellOutput) => shellOutput.text());
191+
}
213192

214-
return createOutputFolder
193+
export function build(config: PluginBuildConfig) {
194+
return createFolder(config.outputFolder)
215195
.andThrough(() => {
216196
if (config.wasmBuildConfig) {
217197
return buildWasm(config.wasmBuildConfig);
218198
}
219199
return okAsync();
220200
})
221-
.andThrough(() => {
222-
console.log("Building styles");
223-
return ResultAsync.fromPromise(
224-
$`grass ${Bun.file(`${config.sourceFolder}/${config.entrypoints.styles}`)} --style compressed > ${Bun.file(`${config.outputFolder}/${targetStylesFile}`)}`,
225-
(error) => {
226-
console.error(`ERROR. ${error}`);
227-
return BuildError.UnableToBuildStylesFiles;
228-
},
229-
);
230-
})
231-
.andThen(() => {
232-
console.log(
233-
`Building main: ${config.sourceFolder}/${config.entrypoints.main} (output: ${config.outputFolder})`,
234-
);
235-
return ResultAsync.fromPromise(
236-
Bun.build({
237-
entrypoints: [
238-
`${config.sourceFolder}/${config.entrypoints.main}`,
239-
],
240-
outdir: config.outputFolder,
241-
minify: config.minify,
242-
target: "browser",
243-
format: config.format,
244-
plugins: config.useWasm
245-
? [InlineWasmBunPlugin, ...defaultBunPlugins]
246-
: defaultBunPlugins,
247-
drop: config.drop,
248-
sourcemap: config.sourcemap ? "inline" : "none",
249-
external: [
250-
"obsidian",
251-
"electron",
252-
"@electron/remote",
253-
"@codemirror/autocomplete",
254-
"@codemirror/collab",
255-
"@codemirror/commands",
256-
"@codemirror/language",
257-
"@codemirror/lint",
258-
"@codemirror/search",
259-
"@codemirror/state",
260-
"@codemirror/view",
261-
"@lezer/common",
262-
"@lezer/highlight",
263-
"@lezer/lr",
264-
...builtins,
265-
],
266-
}),
267-
(error) => {
268-
console.error(`ERROR. ${error}`);
269-
return BuildError.UnableToBuildJavaScriptFiles;
270-
},
271-
);
272-
})
201+
.andThrough(() =>
202+
buildStyles(
203+
Bun.file(`${config.sourceFolder}/${config.entrypoints.styles}`),
204+
Bun.file(`${config.outputFolder}/${targetStylesFile}`),
205+
),
206+
)
207+
.andThen(() => buildJavaScript(config))
273208
.andThrough(() => {
274209
if (config.generateTypes) {
275210
// Build typescript declaration files
276-
console.log("Building types");
277-
return ResultAsync.fromPromise(
278-
$`bun tsc --noEmit false --emitDeclarationOnly --declaration --outDir ${config.outputFolder}/types`,
279-
(error) => {
280-
console.error(`ERROR. ${error}`);
281-
return BuildError.UnableToBuildTypesDeclarations;
282-
},
283-
).andThrough(() =>
284-
ResultAsync.fromPromise(
285-
resolveTsPaths({
286-
src: config.sourceFolder,
287-
out: `${config.outputFolder}/types`,
288-
}),
289-
(error) => {
290-
console.error(`ERROR. ${error}`);
291-
return BuildError.UnableToResolveTypeScriptPaths;
292-
},
293-
),
211+
return buildTypeScriptDeclarations(
212+
config.sourceFolder,
213+
config.outputFolder,
294214
);
295215
}
296216
return okAsync();
@@ -302,7 +222,8 @@ await build({
302222
entrypoints: { main: "main.ts", styles: "styles/index.scss" },
303223
outputFolder,
304224
format: "cjs",
305-
drop: args.dev ? [] : ["console"],
225+
// drop: args.dev ? [] : ["console"],
226+
drop: [],
306227
generateTypes: false,
307228
useWasm: true,
308229
minify: !args["no-minify"],
@@ -320,6 +241,6 @@ await build({
320241
})
321242
.andTee(() => console.log("Done!"))
322243
.orElse((error) => {
323-
console.error(`Build failed. Reason: ${error}`);
244+
error.log();
324245
process.exit(1);
325246
});

0 commit comments

Comments
 (0)