@webiny/build-tools 6.4.11 → 6.6.0-alpha.1

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.
@@ -45,7 +45,12 @@ export const createRsbuildConfig = ({ cwd }) => {
45
45
  }
46
46
  }
47
47
  },
48
- server: { port: process.env.PORT || 3001, host: "0.0.0.0" },
48
+ // Port precedence matches the served admin app (`webiny serve admin`): an explicit
49
+ // WEBINY_ADMIN_PORT wins, then a PORT injected by the environment, then the 3001 default.
50
+ server: {
51
+ port: process.env.WEBINY_ADMIN_PORT || process.env.PORT || 3001,
52
+ host: "0.0.0.0"
53
+ },
49
54
  html: {
50
55
  template: paths.projectRootFolder + "/public/index.html"
51
56
  },
@@ -159,7 +164,9 @@ const getEnvVars = () => {
159
164
  // Provide values one by one, not as a single process.env object,
160
165
  // because otherwise plugin will put a big JSON object every time process.env is used in code.
161
166
  // This way minifier also removes redundant code on prod (like if(process.env.NODE_ENV === 'development')).
162
- const envVarsAsStrings = {};
167
+ const envVarsAsStrings = {
168
+ "process.env": "{}"
169
+ };
163
170
  for (const key of Object.keys(raw)) {
164
171
  envVarsAsStrings[`process.env.${key}`] = JSON.stringify(raw[key]);
165
172
  }
@@ -2,7 +2,7 @@ import path from "path";
2
2
  import { pluginTypeCheck } from "@rsbuild/plugin-type-check";
3
3
  import { createImportValidatorPlugin } from "../importValidatorPlugin.js";
4
4
 
5
- const DEFAULT_WEBINY_INFRA_API_MAX_BUNDLE_SIZE = 4_718_592; // 4.5 MB
5
+ const DEFAULT_WEBINY_INFRA_API_MAX_BUNDLE_SIZE = 6_291_456; // 6 MB
6
6
 
7
7
  export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
8
8
  // Must be a dynamic import — see rslibCompile.js for the reason.
@@ -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 => {
@@ -41,6 +57,21 @@ export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
41
57
  },
42
58
  tools: {
43
59
  rspack: {
60
+ output: {
61
+ // Declares the entry's exports as the bundle's public API, so ALL of them survive.
62
+ //
63
+ // Nothing imports an entry's exports, so without this rspack treats any unused one
64
+ // as dead: it dropped `streamHandler` AND every module reachable only from it. The
65
+ // api bundle then exported just `handler` and contained zero streaming code, so the
66
+ // response-streaming Lambda (`handler.streamHandler`) had no handler to load and
67
+ // failed at cold start. `handler` survived only by accident of being first.
68
+ //
69
+ // To verify after changing anything here, grep the built bundle:
70
+ // .webiny/workspace/apps/api/graphql/build/_handler.mjs
71
+ // It must export BOTH `handler` and `streamHandler`. Removing this line takes the
72
+ // export count back to one — silently, with a green build.
73
+ library: { type: "module" }
74
+ },
44
75
  ...(enforceMaxBundleSize && {
45
76
  performance: {
46
77
  hints: "error",
@@ -48,13 +79,21 @@ export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
48
79
  maxAssetSize: maxBundleSize
49
80
  }
50
81
  }),
51
- externals: [/^@aws-sdk/, /^aws-sdk$/, /^sharp$/],
82
+ // Both hosting types bundle; externalize only what genuinely can't be bundled.
83
+ // sharp is a native .node binary; knex statically require()s a driver for every SQL
84
+ // dialect (bundling pulls in uninstalled ones) and lazily loads only the configured
85
+ // one at runtime (e.g. better-sqlite3). AWS additionally externalizes aws-sdk (the
86
+ // Lambda runtime provides it). Server ships these in build/node_modules — the build's
87
+ // packaging step (CI/Linux) copies them, natives included.
88
+ externals: isServer
89
+ ? [/^sharp$/, /^knex(\/|$)/]
90
+ : [/^@aws-sdk/, /^aws-sdk$/, /^sharp$/, /^knex(\/|$)/],
52
91
  plugins: [
53
- // This is necessary to enable JSDOM usage in Lambda.
92
+ // Ignore optional `canvas` native module required by jsdom.
54
93
  // https://rspack.dev/plugins/webpack/ignore-plugin
55
94
  new rspack.IgnorePlugin({
56
- resourceRegExp: /canvas/,
57
- contextRegExp: /jsdom$/
95
+ resourceRegExp: /^canvas$/,
96
+ contextRegExp: /jsdom/
58
97
  })
59
98
  ],
60
99
  resolve: {
@@ -63,7 +102,19 @@ export const createRsbuildConfig = async ({ cwd, enforceMaxBundleSize }) => {
63
102
  // Not needed in Lambda environment and can cause bundling/deployment issues.
64
103
  bufferutil: false
65
104
  }
66
- }
105
+ },
106
+ // bree's root-jobs loader does `await import(importUrl)` on a path it builds at runtime,
107
+ // which rspack can't resolve statically, so it reports a critical dependency. That import
108
+ // sits behind `if (this.config.root && ...)` (node_modules/bree/src/index.js), and
109
+ // BreeSchedulerService constructs Bree with `root: false` — the branch never runs, and
110
+ // nothing is missing from the bundle. Matched narrowly so a genuine expression-based
111
+ // import anywhere else still gets reported.
112
+ ignoreWarnings: [
113
+ {
114
+ module: /node_modules[\\/]bree[\\/]/,
115
+ message: /Critical dependency: the request of a dependency is an expression/
116
+ }
117
+ ]
67
118
  }
68
119
  },
69
120
  mode,
@@ -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.11",
3
+ "version": "6.6.0-alpha.1",
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.13",
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.2",
26
26
  "@rslib/core": "0.23.2",
27
- "@rspack/core": "2.1.3",
28
- "@swc/plugin-emotion": "14.15.0",
29
- "@tailwindcss/postcss": "4.3.2",
30
- "chalk": "5.6.2",
27
+ "@rspack/core": "2.1.10",
28
+ "@swc/plugin-emotion": "15.0.0",
29
+ "@tailwindcss/postcss": "4.3.3",
30
+ "@webiny/stdlib": "0.0.17",
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,14 +40,16 @@
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.103.1",
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.12",
49
+ "typescript": "7.0.2",
49
50
  "url-loader": "4.1.1",
50
- "utf-8-validate": "6.0.6"
51
+ "utf-8-validate": "6.0.6",
52
+ "yargs": "18.1.0"
51
53
  },
52
54
  "license": "MIT",
53
55
  "adio": {
@@ -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"),
@@ -0,0 +1,6 @@
1
+ interface Preset {
2
+ setupFiles?: string[];
3
+ setupFilesAfterEnv?: string[];
4
+ }
5
+
6
+ export function getPresets(...keywords: string[][]): Promise<Preset[]>;
@@ -0,0 +1,162 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { listWorkspaces } from "@webiny/stdlib/node";
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
6
+ import { PackageJson } from "@webiny/build-tools/utils/PackageJson.js";
7
+
8
+ const MIN_STORAGE_LENGTH = 1;
9
+ const DEFAULT_STORAGE = "ddb";
10
+ /**
11
+ * @param argv {string[]}
12
+ * @returns {string}
13
+ */
14
+ const getStorage = argv => {
15
+ /**
16
+ * Storage is available in process.env?
17
+ */
18
+ const envValue = process.env.WEBINY_STORAGE;
19
+ if (typeof envValue === "string" && envValue.length > 2) {
20
+ return envValue;
21
+ }
22
+ /**
23
+ * This is if storage is available in args.
24
+ */
25
+ const args = yargs(argv);
26
+ const argsValue = args.storage;
27
+ if (typeof argsValue === "string" && argsValue.length > 2) {
28
+ return argsValue;
29
+ }
30
+ /**
31
+ * Then we try to get --storage=([a-z])
32
+ */
33
+ const matched = argv
34
+ .map(item => {
35
+ const matched = item.match(/^--storage=([^\s]+)$/);
36
+ return matched ? matched[1] : null;
37
+ })
38
+ .find(item => {
39
+ return !!item;
40
+ });
41
+ if (typeof matched === "string" && matched.length > MIN_STORAGE_LENGTH) {
42
+ return matched;
43
+ }
44
+ /**
45
+ * Last attempt is to find --storage and then take next index.
46
+ */
47
+ const index = argv.findIndex(item => {
48
+ return item === "--storage";
49
+ });
50
+ if (index === -1) {
51
+ return DEFAULT_STORAGE;
52
+ }
53
+ const value = argv[index + 1];
54
+ if (typeof value === "string" && value.length > MIN_STORAGE_LENGTH) {
55
+ return value;
56
+ }
57
+ return DEFAULT_STORAGE;
58
+ };
59
+
60
+ const getAllPackages = targetKeywords => {
61
+ const storage = getStorage(hideBin(process.argv));
62
+
63
+ if (!storage) {
64
+ throw Error(`Missing required --storage parameter!`);
65
+ }
66
+
67
+ // Set the storage type as an environment variable.
68
+ process.env.WEBINY_STORAGE_OPS = storage;
69
+
70
+ const storagePriority = storage.split(",");
71
+
72
+ const packages = listWorkspaces({
73
+ cwd: process.cwd()
74
+ })
75
+ .map(pkg => {
76
+ return pkg.path.replace(/\\/g, "/");
77
+ })
78
+ .filter(pkg => pkg.match(/\/packages\//) !== null);
79
+
80
+ // Find packages that match the given sets of tags.
81
+ const packageJsons = [];
82
+ for (const pkg of packages) {
83
+ const pkgJson = PackageJson.fromFile(pkg + "/package.json");
84
+ const { name, keywords = [] } = pkgJson.getJson();
85
+
86
+ for (const set of targetKeywords) {
87
+ if (set.every(tag => keywords.includes(tag))) {
88
+ packageJsons.push({
89
+ path: pkg,
90
+ name,
91
+ keywords
92
+ });
93
+ }
94
+ }
95
+ }
96
+ // Now we need to filter based on the required storage type, but also use fallback, if possible.
97
+ const results = [];
98
+
99
+ for (const set of targetKeywords) {
100
+ for (const storage of storagePriority) {
101
+ const matchingPackage = packageJsons.find(pkg => {
102
+ return [...set, storage].every(tag => pkg.keywords.includes(tag));
103
+ });
104
+
105
+ if (matchingPackage) {
106
+ results.push(matchingPackage);
107
+ break;
108
+ }
109
+ }
110
+ }
111
+
112
+ return results;
113
+ };
114
+
115
+ const removeEmptyPreset = preset => {
116
+ if (!preset || Object.keys(preset).length === 0) {
117
+ return false;
118
+ }
119
+ return true;
120
+ };
121
+
122
+ const getPackagesPresets = async targetKeywords => {
123
+ if (!targetKeywords || targetKeywords.length === 0) {
124
+ throw new Error(`You must pass keywords to search for in the packages.`);
125
+ }
126
+
127
+ if (!Array.isArray(targetKeywords[0])) {
128
+ targetKeywords = [targetKeywords];
129
+ }
130
+
131
+ const packages = getAllPackages(targetKeywords);
132
+ if (packages.length === 0) {
133
+ return [];
134
+ }
135
+ const items = [];
136
+ /**
137
+ * We go through all available packages to build presets for them.
138
+ */
139
+ for (const pkg of packages) {
140
+ const presetsPath = path.join(pkg.path, "__tests__/__api__/presets.js");
141
+ if (!fs.existsSync(presetsPath)) {
142
+ throw new Error(`Missing presets.js of the "${pkg.name}" package: ${presetsPath}`);
143
+ }
144
+ /**
145
+ * We expect presets file to contain an array of presets.
146
+ * We do not check for the actual contents of the presets arrays since they can be quite different per package.
147
+ */
148
+ const presets = await import(presetsPath).then(m => m.default ?? m);
149
+ if (Array.isArray(presets) === false) {
150
+ throw new Error(`Presets in package "${pkg.name}" must be defined as an array.`);
151
+ } else if (presets.length === 0) {
152
+ throw new Error(`There are no presets in the file "${presetsPath}".`);
153
+ }
154
+
155
+ items.push(...presets.filter(removeEmptyPreset));
156
+ }
157
+ return items;
158
+ };
159
+
160
+ export const getPresets = async (...targetKeywords) => {
161
+ return getPackagesPresets(targetKeywords);
162
+ };
@@ -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) {