@webiny/build-tools 6.4.5-beta.0 → 6.6.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -159,7 +159,9 @@ const getEnvVars = () => {
159
159
  // Provide values one by one, not as a single process.env object,
160
160
  // because otherwise plugin will put a big JSON object every time process.env is used in code.
161
161
  // This way minifier also removes redundant code on prod (like if(process.env.NODE_ENV === 'development')).
162
- const envVarsAsStrings = {};
162
+ const envVarsAsStrings = {
163
+ "process.env": "{}"
164
+ };
163
165
  for (const key of Object.keys(raw)) {
164
166
  envVarsAsStrings[`process.env.${key}`] = JSON.stringify(raw[key]);
165
167
  }
@@ -10,6 +10,11 @@ export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
10
10
  const paths = getPaths(cwd);
11
11
  const mode = getMode();
12
12
  const isDebugEnabled = process.env.DEBUG === "true";
13
+ // NOTE: dirty for now — sniffing the hosting type off an env var here. Ideally the build config
14
+ // wouldn't know about hosting types at all: the caller (createBuildFunction, or the flavour's own
15
+ // build layer) would pass in the externals policy + assetPrefix as options, keeping this file
16
+ // hosting-agnostic. Good enough while the server hosting type is ALPHA; revisit when it settles.
17
+ const isServer = process.env.WEBINY_HOSTING_TYPE === "server";
13
18
 
14
19
  // Configurable via WEBINY_INFRA_API_MAX_BUNDLE_SIZE (bytes).
15
20
  // Only enforced during build — watch mode skips size checks.
@@ -19,12 +24,23 @@ export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
19
24
 
20
25
  return /** @type {import("@rsbuild/core").RsbuildConfig} */ ({
21
26
  source: { entry: { index: paths.fn.entryFile } },
27
+ // Resolve chunk/asset URLs relative to the running module, not from an absolute root. The
28
+ // server's bg-tasks worker chunk is spawned via `new Worker(new URL("...", import.meta.url))`
29
+ // and rsbuild's default publicPath "/" turns that into an ABSOLUTE url ("/xyz.mjs"), which
30
+ // resolves to the filesystem root and can't be found. "auto" resolves it relative to the module
31
+ // (handler.mjs) instead, so the chunk loads from build/. publicPath comes from
32
+ // `output.assetPrefix` in production and `dev.assetPrefix` in development (watch runs
33
+ // `rsbuild.build({ watch: true })` under NODE_ENV=development), so set BOTH. Not gated to the
34
+ // server hosting type: AWS has no worker chunks so it's a no-op there, and for any other
35
+ // (async import) chunks "auto" is at least as correct as "/" (Lambda runs from its own dir too).
36
+ dev: { assetPrefix: "auto" },
22
37
  output: {
23
38
  module: true,
24
39
  target: "node",
40
+ assetPrefix: "auto",
25
41
  minify: true,
26
42
  sourceMap: {
27
- js: isDebugEnabled ? "source-map" : false
43
+ js: isDebugEnabled || mode === "development" ? "source-map" : false
28
44
  },
29
45
  filename: {
30
46
  js: pathData => {
@@ -48,7 +64,15 @@ export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
48
64
  maxAssetSize: maxBundleSize
49
65
  }
50
66
  }),
51
- externals: [/^@aws-sdk/, /^aws-sdk$/, /^sharp$/],
67
+ // Both hosting types bundle; externalize only what genuinely can't be bundled.
68
+ // sharp is a native .node binary; knex statically require()s a driver for every SQL
69
+ // dialect (bundling pulls in uninstalled ones) and lazily loads only the configured
70
+ // one at runtime (e.g. better-sqlite3). AWS additionally externalizes aws-sdk (the
71
+ // Lambda runtime provides it). Server ships these in build/node_modules — the build's
72
+ // packaging step (CI/Linux) copies them, natives included.
73
+ externals: isServer
74
+ ? [/^sharp$/, /^knex(\/|$)/]
75
+ : [/^@aws-sdk/, /^aws-sdk$/, /^sharp$/, /^knex(\/|$)/],
52
76
  plugins: [
53
77
  // This is necessary to enable JSDOM usage in Lambda.
54
78
  // https://rspack.dev/plugins/webpack/ignore-plugin
@@ -9,6 +9,7 @@ const whitelist = [
9
9
  "@webiny/cognito",
10
10
  "@webiny/auth0",
11
11
  "@webiny/okta",
12
+ "@webiny/self-hosted-auth",
12
13
  "@webiny/plugins",
13
14
  "@webiny/sdk",
14
15
  "@webiny/stdlib",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/build-tools",
3
- "version": "6.4.5-beta.0",
3
+ "version": "6.6.0-alpha.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -17,22 +17,22 @@
17
17
  "Adrian Smijulj <adrian@webiny.com>"
18
18
  ],
19
19
  "dependencies": {
20
- "@rsbuild/core": "2.1.5",
20
+ "@rsbuild/core": "2.1.8",
21
21
  "@rsbuild/plugin-react": "2.1.0",
22
22
  "@rsbuild/plugin-sass": "2.0.1",
23
23
  "@rsbuild/plugin-svgr": "2.0.5",
24
- "@rsbuild/plugin-type-check": "1.5.0",
25
- "@rsdoctor/rspack-plugin": "1.5.18",
24
+ "@rsbuild/plugin-type-check": "1.6.0",
25
+ "@rsdoctor/rspack-plugin": "1.6.1",
26
26
  "@rslib/core": "0.23.2",
27
- "@rspack/core": "2.1.3",
27
+ "@rspack/core": "2.1.5",
28
28
  "@swc/plugin-emotion": "14.15.0",
29
- "@tailwindcss/postcss": "4.3.2",
30
- "chalk": "5.6.2",
29
+ "@tailwindcss/postcss": "4.3.3",
30
+ "@webiny/stdlib": "0.0.14",
31
+ "chalk": "6.0.0",
31
32
  "css-loader": "7.1.4",
32
33
  "fast-glob": "3.3.3",
33
34
  "find-up": "8.0.0",
34
- "fs-extra": "11.3.6",
35
- "get-yarn-workspaces": "1.0.2",
35
+ "fs-extra": "11.4.0",
36
36
  "load-json-file": "7.0.1",
37
37
  "lodash": "4.18.1",
38
38
  "postcss-loader": "8.2.1",
@@ -40,12 +40,13 @@
40
40
  "react-dom": "18.3.1",
41
41
  "react-refresh": "0.18.0",
42
42
  "rimraf": "6.1.3",
43
- "sass": "1.101.0",
43
+ "sass": "1.102.0",
44
44
  "sass-loader": "17.0.0",
45
+ "strip-json-comments": "5.0.3",
45
46
  "style-loader": "4.0.0",
46
47
  "ts-morph": "28.0.0",
47
- "tsx": "4.23.0",
48
- "typescript": "6.0.3",
48
+ "tsx": "4.23.1",
49
+ "typescript": "7.0.2",
49
50
  "url-loader": "4.1.1",
50
51
  "utf-8-validate": "6.0.6"
51
52
  },
@@ -6,7 +6,9 @@ import glob from "fast-glob";
6
6
  const COMPILE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
7
7
  const SKIP_EXTENSIONS = [".d.ts"];
8
8
 
9
- export const rslibCompile = async ({ cwd }) => {
9
+ export const rslibCompile = async ({ cwd, outputDir }) => {
10
+ const distRoot = outputDir || path.join(cwd, "dist");
11
+
10
12
  // Copy non-compilable files (assets, json, graphql, etc.) as-is.
11
13
  const pattern = path.join(cwd, "src/**/*.*").replace(/\\/g, "/");
12
14
  const allFiles = glob.sync(pattern, { onlyFiles: true, dot: true });
@@ -16,7 +18,7 @@ export const rslibCompile = async ({ cwd }) => {
16
18
  const shouldSkip = SKIP_EXTENSIONS.some(ext => file.endsWith(ext));
17
19
 
18
20
  if (!shouldCompile || shouldSkip) {
19
- const destPath = file.replace(path.join(cwd, "src"), path.join(cwd, "dist"));
21
+ const destPath = file.replace(path.join(cwd, "src"), distRoot);
20
22
  fs.mkdirSync(dirname(destPath), { recursive: true });
21
23
  fs.copyFileSync(file, destPath);
22
24
  }
@@ -44,7 +46,7 @@ export const rslibCompile = async ({ cwd }) => {
44
46
  },
45
47
  output: {
46
48
  target: "web",
47
- distPath: { root: "./dist" },
49
+ distPath: { root: path.relative(cwd, distRoot) || "." },
48
50
  cleanDistPath: false,
49
51
  sourceMap: { js: "source-map" },
50
52
  // mixedImport emits each SVG as a static asset for the default URL
@@ -1,99 +1,80 @@
1
- import { join } from "path";
2
- import ts from "typescript";
1
+ import { join, resolve } from "path";
2
+ import fs from "node:fs";
3
3
  import merge from "lodash/merge.js";
4
4
  import { replaceTscAliases } from "./tsAliasReplacer.js";
5
+ import { getTscBinaryPath } from "./typescript/getTscBinaryPath.js";
6
+ import { readTsConfig } from "./typescript/readTsConfig.js";
7
+ import { runTsc } from "./typescript/runTsc.js";
8
+ import { writeTempTsConfig } from "./typescript/writeTempTsConfig.js";
5
9
 
6
10
  export const tsCompile = async ({ cwd = "", overrides, debug, outputDir, checkOnly = false }) => {
7
- // Normalize path separators to forward slashes for consistent behavior on Windows.
8
- const normalizedCwd = cwd.replace(/\\/g, "/");
11
+ const normalizedCwd = cwd || process.cwd();
12
+ const tscPath = getTscBinaryPath();
9
13
 
10
- const tsConfigPath = join(normalizedCwd, "tsconfig.build.json");
14
+ const originalConfigPath = join(normalizedCwd, "tsconfig.build.json");
15
+ let tsConfigPath = originalConfigPath;
16
+ let tempConfigPath = null;
17
+ let resolvedOutDir = null;
11
18
 
12
- let { config: readTsConfig } = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
19
+ const hasOverrides = overrides?.tsConfig || outputDir;
13
20
 
14
- if (overrides.tsConfig) {
15
- if (typeof overrides.tsConfig === "function") {
16
- readTsConfig = overrides.tsConfig(readTsConfig);
17
- } else {
18
- merge(readTsConfig, overrides.tsConfig);
19
- }
20
-
21
- if (debug) {
22
- console.log(`"tsconfig.build.json" overridden. New config:`);
23
- console.log(readTsConfig);
24
- }
25
- }
26
- if (outputDir) {
27
- readTsConfig.compilerOptions = readTsConfig.compilerOptions || {};
28
- readTsConfig.compilerOptions.outDir = outputDir;
29
- readTsConfig.compilerOptions.declarationDir = outputDir;
30
- }
21
+ if (hasOverrides) {
22
+ let config = readTsConfig(originalConfigPath);
31
23
 
32
- const parsedJsonConfigFile = ts.parseJsonConfigFileContent(readTsConfig, ts.sys, normalizedCwd);
24
+ if (overrides?.tsConfig) {
25
+ if (typeof overrides.tsConfig === "function") {
26
+ config = overrides.tsConfig(config);
27
+ } else {
28
+ merge(config, overrides.tsConfig);
29
+ }
33
30
 
34
- const { projectReferences, options, fileNames, errors } = parsedJsonConfigFile;
31
+ if (debug) {
32
+ console.log(`"tsconfig.build.json" overridden. New config:`);
33
+ console.log(config);
34
+ }
35
+ }
35
36
 
36
- const filteredFileNames = fileNames.filter(fileName => !fileName.endsWith(".d.ts"));
37
+ if (outputDir) {
38
+ config.compilerOptions = config.compilerOptions || {};
39
+ config.compilerOptions.outDir = outputDir;
40
+ config.compilerOptions.declarationDir = outputDir;
41
+ }
37
42
 
38
- if (checkOnly) {
39
- options.noEmit = true;
43
+ resolvedOutDir = config.compilerOptions?.outDir;
44
+ tempConfigPath = writeTempTsConfig(normalizedCwd, config);
45
+ tsConfigPath = tempConfigPath;
40
46
  }
41
47
 
42
- const program = ts.createProgram({
43
- projectReferences,
44
- options,
45
- rootNames: filteredFileNames,
46
- configFileParsingDiagnostics: errors
47
- });
48
-
49
- if (checkOnly) {
50
- const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(errors);
48
+ try {
49
+ const args = ["-p", tsConfigPath];
50
+ args.push("--tsBuildInfoFile", join(normalizedCwd, "tsconfig.build.tsbuildinfo"));
51
51
 
52
- if (allDiagnostics.length) {
53
- const formatHost = {
54
- getCanonicalFileName: path => path,
55
- getCurrentDirectory: () => normalizedCwd,
56
- getNewLine: () => ts.sys.newLine
57
- };
58
- const message = ts.formatDiagnostics(allDiagnostics, formatHost);
59
- if (message) {
60
- throw { message };
61
- }
62
- }
63
- return;
64
- }
65
-
66
- const { diagnostics, emitSkipped } = program.emit(
67
- undefined, // targetSourceFile
68
- (fileName, data, writeByteOrderMark) => {
69
- // Only emit files within the current package directory.
70
- // Normalize path separators to handle Windows backslashes vs forward slashes.
71
- const normalizedFileName = fileName.replace(/\\/g, "/");
72
- const relativePath = normalizedFileName.replace(normalizedCwd, "");
73
- if (normalizedFileName.startsWith(normalizedCwd) && !relativePath.includes("../")) {
74
- ts.sys.writeFile(fileName, data, writeByteOrderMark);
75
- }
52
+ if (checkOnly) {
53
+ args.push("--noEmit");
76
54
  }
77
- );
78
55
 
79
- const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(diagnostics, errors);
80
-
81
- if (allDiagnostics.length) {
82
- const formatHost = {
83
- getCanonicalFileName: path => path,
84
- getCurrentDirectory: () => normalizedCwd,
85
- getNewLine: () => ts.sys.newLine
86
- };
87
- const message = ts.formatDiagnostics(allDiagnostics, formatHost);
88
- if (message) {
89
- throw { message };
56
+ runTsc(tscPath, args, normalizedCwd);
57
+ } finally {
58
+ if (tempConfigPath) {
59
+ fs.rmSync(tempConfigPath, { force: true });
90
60
  }
91
61
  }
92
62
 
93
- if (emitSkipped) {
94
- throw { message: "TypeScript compilation failed." };
63
+ if (!checkOnly) {
64
+ const distDir =
65
+ outputDir || resolveDistDir(normalizedCwd, originalConfigPath, resolvedOutDir);
66
+ await replaceTscAliases({ distDir, cwd: normalizedCwd, debug });
95
67
  }
96
-
97
- const distDir = outputDir || options.outDir || join(normalizedCwd, "dist");
98
- await replaceTscAliases({ distDir, cwd: normalizedCwd, debug });
99
68
  };
69
+
70
+ function resolveDistDir(cwd, configPath, overriddenOutDir) {
71
+ if (overriddenOutDir) {
72
+ return resolve(cwd, overriddenOutDir);
73
+ }
74
+ const config = readTsConfig(configPath);
75
+ const outDir = config.compilerOptions?.outDir;
76
+ if (outDir) {
77
+ return resolve(cwd, outDir);
78
+ }
79
+ return join(cwd, "dist");
80
+ }
@@ -0,0 +1,47 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, join } from "node:path";
3
+ import fs from "node:fs";
4
+
5
+ let cachedPath = null;
6
+
7
+ export function getTscBinaryPath() {
8
+ if (cachedPath) {
9
+ return cachedPath;
10
+ }
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const tsPackageJsonPath = require.resolve("typescript/package.json");
14
+ const tsDir = dirname(tsPackageJsonPath);
15
+
16
+ const platformPackage = "@typescript/typescript-" + process.platform + "-" + process.arch;
17
+
18
+ let platformPkgPath;
19
+ try {
20
+ const platformRequire = createRequire(join(tsDir, "package.json"));
21
+ platformPkgPath = platformRequire.resolve(platformPackage + "/package.json");
22
+ } catch {
23
+ throw new Error(
24
+ "Unable to resolve " +
25
+ platformPackage +
26
+ ". " +
27
+ "Either your platform is unsupported, or you are missing the package on disk."
28
+ );
29
+ }
30
+
31
+ const libDir = join(dirname(platformPkgPath), "lib");
32
+
33
+ let exe = join(libDir, "tsc");
34
+ if (process.platform === "win32") {
35
+ exe += ".exe";
36
+ if (exe.length >= 248) {
37
+ exe = "\\\\?\\" + exe;
38
+ }
39
+ }
40
+
41
+ if (!fs.existsSync(exe)) {
42
+ throw new Error("TypeScript native binary not found: " + exe);
43
+ }
44
+
45
+ cachedPath = exe;
46
+ return cachedPath;
47
+ }
@@ -0,0 +1,8 @@
1
+ import fs from "node:fs";
2
+ import stripJsonComments from "strip-json-comments";
3
+
4
+ export function readTsConfig(configPath) {
5
+ const content = fs.readFileSync(configPath, "utf8");
6
+ const stripped = stripJsonComments(content, { trailingCommas: true });
7
+ return JSON.parse(stripped);
8
+ }
@@ -0,0 +1,15 @@
1
+ import { execFileSync } from "node:child_process";
2
+
3
+ export function runTsc(tscPath, args, cwd) {
4
+ try {
5
+ execFileSync(tscPath, args, {
6
+ cwd,
7
+ stdio: ["pipe", "pipe", "pipe"],
8
+ encoding: "utf8",
9
+ maxBuffer: 10 * 1024 * 1024
10
+ });
11
+ } catch (error) {
12
+ const output = [error.stdout, error.stderr].filter(Boolean).join("\n").trim();
13
+ throw { message: output || error.message || "TypeScript compilation failed." };
14
+ }
15
+ }
@@ -0,0 +1,10 @@
1
+ import { join } from "node:path";
2
+ import fs from "node:fs";
3
+ import { randomBytes } from "node:crypto";
4
+
5
+ export function writeTempTsConfig(cwd, config) {
6
+ const suffix = randomBytes(4).toString("hex");
7
+ const tempPath = join(cwd, `tsconfig.build.tmp-${suffix}.json`);
8
+ fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), "utf8");
9
+ return tempPath;
10
+ }
@@ -69,7 +69,7 @@ async function buildWithSafeReplace(options, distDir) {
69
69
 
70
70
  const stagingOptions = { ...options, outputDir: stagingDir };
71
71
 
72
- await babelCompile(stagingOptions);
72
+ await rslibCompile(stagingOptions);
73
73
  await tsCompile(stagingOptions);
74
74
 
75
75
  stagingOptions.logs !== false && console.log("Copying meta files...");
@@ -1,6 +1,16 @@
1
+ import fs from "node:fs";
2
+ import { join } from "node:path";
3
+
1
4
  export default async options => {
2
5
  const { cwd } = options;
3
6
 
7
+ // Invalidate the build-cache freshness marker (see scripts/buildPackages
8
+ // distBuildHash.ts, ".webiny-build-hash"). Watch writes into dist without
9
+ // updating that marker, so its contents no longer match the last full
10
+ // build — a later `yarn build` must restore/rebuild instead of trusting the
11
+ // marker and skipping the cache→dist copy.
12
+ fs.rmSync(join(cwd, "dist", ".webiny-build-hash"), { force: true });
13
+
4
14
  // Must be a dynamic import — see rslibCompile.js for the reason.
5
15
  const [{ createRslib }, { pluginSvgr }] = await Promise.all([
6
16
  import("@rslib/core"),
@@ -1,12 +1,15 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import getYarnWorkspaces from "get-yarn-workspaces";
3
+ import { listWorkspaces } from "@webiny/stdlib/node";
4
4
  export { linkWorkspaces } from "./linkWorkspaces";
5
5
 
6
6
  const hasPackageJson = p => fs.existsSync(p + "/package.json");
7
7
 
8
8
  export const allWorkspaces = () => {
9
- return getYarnWorkspaces()
9
+ return listWorkspaces()
10
+ .map(pkg => {
11
+ return pkg.path;
12
+ })
10
13
  .filter(hasPackageJson)
11
14
  .map(pkg => pkg.replace(/\//g, path.sep));
12
15
  };
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import "tsx";
8
-
8
+ import { listWorkspaces } from "@webiny/stdlib/node";
9
9
  import path from "path";
10
10
  import get from "lodash/get.js";
11
11
  import fs from "fs-extra";
@@ -56,9 +56,13 @@ export const linkWorkspaces = async ({ whitelist, blacklist } = defaults) => {
56
56
  whitelist = (whitelist || []).map(p => path.resolve(p));
57
57
  blacklist = (blacklist || []).map(p => path.resolve(p));
58
58
  // Filter packages to only those in the whitelisted folders
59
- const getYarnWorkspaces = await import("get-yarn-workspaces").then(m => m.default ?? m);
60
- const packages = getYarnWorkspaces(process.cwd())
61
- .map(pkg => pkg.replace(/\//g, path.sep))
59
+
60
+ const packages = listWorkspaces({
61
+ cwd: process.cwd()
62
+ })
63
+ .map(pkg => {
64
+ return pkg.path.replace(/\//g, path.sep);
65
+ })
62
66
  .filter(pkg => {
63
67
  const isBlacklisted = blacklist.some(b => pkg.startsWith(b));
64
68
  if (isBlacklisted) {