pkgbld 1.35.1 → 2.0.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.
package/src/eject.js ADDED
@@ -0,0 +1,151 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import pkgbldPkg from '../package.json' with { type: 'json' };
5
+ import { camelCase } from './helpers.js';
6
+
7
+ /**
8
+ * @typedef {import('rollup').RollupOptions} RollupOptions
9
+ * @typedef {import('type-fest').PackageJson} PackageJson
10
+ * @typedef {import('./types.js').BuildConfiguration} BuildConfiguration
11
+ * @typedef {import('./types.js').PackageProcessingResult} PackageProcessingResult
12
+ * @typedef {import('./types.js').PkgbldRollupPlugin} PkgbldRollupPlugin
13
+ * @typedef {import('./types.js').Provider} Provider
14
+ */
15
+
16
+ const imports = new Map();
17
+ const setup = new Set();
18
+
19
+ /** @type {<T extends object>(object: T) => string | boolean | RegExp | null | undefined} */
20
+ let generate;
21
+ /** @type {() => string} */
22
+ let generateGlobals;
23
+
24
+ /**
25
+ * @returns {Promise<[Provider, PkgbldRollupPlugin[]]>}
26
+ */
27
+ export async function createEjectProvider() {
28
+ const createMockProvider = (await import('@slimlib/smart-mock')).default;
29
+ const provider = createMockProvider();
30
+ const createMock = provider.createMock;
31
+ generate = provider.generate;
32
+ generateGlobals = provider.generateGlobals;
33
+ /** @type {PkgbldRollupPlugin[]} */
34
+ const plugins = [];
35
+ return [
36
+ {
37
+ provide: (
38
+ /** @type {PkgbldRollupPlugin['plugin']} */ plugin,
39
+ /** @type {PkgbldRollupPlugin['priority']} */ priority,
40
+ /** @type {Omit<PkgbldRollupPlugin, 'plugin' | 'priority'>=} */ options
41
+ ) => {
42
+ plugins.push({ priority, plugin, format: options?.format, inputs: options?.inputs, outputPlugin: options?.outputPlugin });
43
+ },
44
+ import: async (/** @type {string} */ name, /** @type {string=} */ exportName) => {
45
+ const result = await import(name);
46
+ const exports = result[exportName ?? 'default'];
47
+ const mangledName = camelCase(name);
48
+ imports.set(name, mangledName);
49
+ return createMock(exports, mangledName);
50
+ },
51
+ globalImport: (/** @type {string} */ module, /** @type {string | string[]=} */ exportName) => {
52
+ imports.set(module, exportName ?? 'default');
53
+ },
54
+ globalSetup: (/** @type {((...args: any[]) => any) | string} */ code) => {
55
+ if (typeof code === 'function') {
56
+ setup.add(code.toString());
57
+ return createMock(code, code.name);
58
+ }
59
+ setup.add(String(code));
60
+ },
61
+ },
62
+ plugins,
63
+ ];
64
+ }
65
+
66
+ /**
67
+ * @param {RollupOptions[]} config
68
+ * @param {string} pkgPath
69
+ * @param {BuildConfiguration} configuration
70
+ * @param {PackageProcessingResult} packageResult
71
+ * @param {ReturnType<import('./helpers.js').getHelpers>} helpers
72
+ * @param {PackageJson} pkg
73
+ */
74
+ export async function ejectConfig(config, pkgPath, configuration, packageResult, helpers, pkg) {
75
+ const pkgName = /** @type {{ name: string }} */ (pkg).name;
76
+
77
+ const text = generate(config);
78
+ setup.add(generateGlobals());
79
+
80
+ setup.add(`const configuration = ${generate(configuration)}`);
81
+ setup.add(`const packageResult = { executableOutputs: ${generate(packageResult.executableOutputs)} }`);
82
+ setup.add(`const inputs = ${generate(packageResult.entries.values.map(entry => entry.sourcePath))}`);
83
+
84
+ if (configuration.outputs.formats.includes('umd')) {
85
+ imports.set('path', 'path');
86
+ imports.set('url', 'url');
87
+ setup.add(`const pkgName = ${generate(/** @type {never} */ (pkgName))}`);
88
+ setup.add(camelCase.toString());
89
+ setup.add(helpers.getGlobalName.toString());
90
+ setup.add("const __dirname = url.fileURLToPath(new URL('.', import.meta.url));");
91
+ }
92
+
93
+ const importsString = Array.from(imports)
94
+ .map(value => `import ${value[1]} from '${value[0]}';`)
95
+ .join('\n');
96
+ const setupString = Array.from(setup).join('\n');
97
+
98
+ const { minify } = await import('terser');
99
+
100
+ const result = await minify(`${importsString}\n${setupString}\nexport default ${text};`, {
101
+ module: true,
102
+ compress: {
103
+ booleans: false,
104
+ ecma: 2020,
105
+ module: true,
106
+ passes: 3,
107
+ unsafe: true,
108
+ },
109
+ mangle: false,
110
+ output: {
111
+ beautify: true,
112
+ ecma: 2020,
113
+ quote_style: 1,
114
+ },
115
+ });
116
+ await fs.writeFile(path.join(path.dirname(pkgPath), 'rollup.config.mjs'), /** @type {string} */ (result.code));
117
+
118
+ await updatePackageJson(pkg);
119
+ }
120
+
121
+ /**
122
+ * @param {PackageJson} pkg
123
+ */
124
+ async function updatePackageJson(pkg) {
125
+ if (typeof pkg.devDependencies !== 'object') {
126
+ pkg.devDependencies = {};
127
+ }
128
+ const devDependencies = pkg.devDependencies;
129
+ if ('pkgbld' in devDependencies) {
130
+ devDependencies.pkgbld = undefined;
131
+ }
132
+ devDependencies.rollup = /** @type {Record<string, string>} */ (pkgbldPkg.dependencies).rollup ?? '*';
133
+ const isBuiltin = (await import('is-builtin-module')).default;
134
+ for (const key of imports.keys()) {
135
+ const packageName = getPackageName(key);
136
+ if (!isBuiltin(packageName)) {
137
+ devDependencies[packageName] = /** @type {Record<string, string>} */ (pkgbldPkg.dependencies)[packageName] ?? '*';
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @param {string} key
144
+ * @returns {string}
145
+ */
146
+ function getPackageName(key) {
147
+ return key
148
+ .split('/')
149
+ .slice(0, key.startsWith('@') ? 2 : 1)
150
+ .join('/');
151
+ }
@@ -0,0 +1,16 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * @typedef {import('type-fest').JsonObject} JsonObject
6
+ */
7
+
8
+ /**
9
+ * @param {string} fileName
10
+ * @returns {Promise<[string, JsonObject]>}
11
+ */
12
+ export async function getJson(fileName) {
13
+ const pkgPath = path.resolve(fileName);
14
+ const buffer = await fs.readFile(pkgPath);
15
+ return [pkgPath, /** @type {JsonObject} */ (JSON.parse(buffer.toString()))];
16
+ }
@@ -0,0 +1,43 @@
1
+ import binify from './builtin-plugins/binify.js';
2
+ import clean from './builtin-plugins/clean.js';
3
+ import commonjs from './builtin-plugins/commonjs.js';
4
+ import externals from './builtin-plugins/externals.js';
5
+ import json from './builtin-plugins/json.js';
6
+ import preprocess from './builtin-plugins/preprocess.js';
7
+ import resolve from './builtin-plugins/resolve.js';
8
+ import terser from './builtin-plugins/terser.js';
9
+
10
+ /**
11
+ * @typedef {import('./types.js').PkgbldRollupPlugin} PkgbldRollupPlugin
12
+ * @typedef {import('./types.js').Provider} Provider
13
+ */
14
+
15
+ export const plugins = [clean, commonjs, externals, preprocess, resolve, terser, binify, json];
16
+
17
+ const noop = () => undefined;
18
+
19
+ /**
20
+ * @returns {[Provider, PkgbldRollupPlugin[]]}
21
+ */
22
+ export function createProvider() {
23
+ /** @type {PkgbldRollupPlugin[]} */
24
+ const plugins = [];
25
+ return [
26
+ {
27
+ provide: (
28
+ /** @type {PkgbldRollupPlugin['plugin']} */ plugin,
29
+ /** @type {PkgbldRollupPlugin['priority']} */ priority,
30
+ /** @type {Omit<PkgbldRollupPlugin, 'plugin' | 'priority'>=} */ options
31
+ ) => {
32
+ plugins.push({ priority, plugin, format: options?.format, inputs: options?.inputs, outputPlugin: options?.outputPlugin });
33
+ },
34
+ import: async (/** @type {string} */ name, /** @type {string=} */ exportName) => {
35
+ const result = await import(name);
36
+ return result[exportName ?? 'default'];
37
+ },
38
+ globalImport: noop,
39
+ globalSetup: code => (typeof code === 'function' ? code : undefined),
40
+ },
41
+ plugins,
42
+ ];
43
+ }
@@ -0,0 +1,224 @@
1
+ import refiner from '@slimlib/refine-partition';
2
+
3
+ import { plugins as pluginFactories } from './get-plugins.js';
4
+ import { areSetsEqual, toArray } from './helpers.js';
5
+
6
+ /**
7
+ * @typedef {import('rollup').InternalModuleFormat} InternalModuleFormat
8
+ * @typedef {import('rollup').OutputOptions} OutputOptions
9
+ * @typedef {import('./types.js').BuildConfiguration} BuildConfiguration
10
+ * @typedef {import('./types.js').PackageProcessingResult} PackageProcessingResult
11
+ * @typedef {import('./types.js').PkgbldRollupPlugin} PkgbldRollupPlugin
12
+ * @typedef {import('./types.js').Provider} Provider
13
+ * @typedef {ReturnType<typeof import('./build-plugin-lifecycle.js').createBuildPluginLifecycle>} BuildPluginLifecycle
14
+ */
15
+
16
+ /**
17
+ * @param {[Provider, PkgbldRollupPlugin[]]} providerAndPlugins
18
+ * @param {PackageProcessingResult} packageResult
19
+ * @param {BuildConfiguration} configuration
20
+ * @param {ReturnType<import('./helpers.js').getHelpers>} helpers
21
+ * @param {BuildPluginLifecycle} pluginLifecycle
22
+ */
23
+ export async function getRollupConfigs([provider, plugins], packageResult, configuration, helpers, pluginLifecycle) {
24
+ const inputs = packageResult.entries.values.map(entry => entry.sourcePath);
25
+ const entriesBySourcePath = new Map(packageResult.entries.values.map(entry => [entry.sourcePath, entry]));
26
+ const factoryInProgress = [];
27
+
28
+ const fileNamePatterns = /** @type {{ [key in InternalModuleFormat]: string }} */ ({
29
+ es: configuration.outputs.patterns.es,
30
+ cjs: configuration.outputs.patterns.cjs,
31
+ umd: configuration.outputs.patterns.umd,
32
+ });
33
+
34
+ for (const factory of pluginFactories) {
35
+ factoryInProgress.push(factory(provider, configuration, packageResult));
36
+ }
37
+
38
+ factoryInProgress.push(pluginLifecycle.provideRollupPlugins(provider, configuration, packageResult));
39
+
40
+ await Promise.all(factoryInProgress);
41
+
42
+ /** @type {Set<string>} */
43
+ const expandInputs = new Set();
44
+
45
+ for (const plugin of plugins) {
46
+ if (plugin.format && plugin.inputs?.length && !plugin.outputPlugin) {
47
+ for (const format of toArray(plugin.format)) {
48
+ expandInputs.add(format);
49
+ }
50
+ }
51
+ }
52
+
53
+ const refineNext = refiner();
54
+
55
+ refineNext(doExpandInputs(/** @type {InternalModuleFormat[]} */ ([...configuration.outputs.formats])));
56
+
57
+ for (const plugin of plugins) {
58
+ if (plugin.format && !plugin.outputPlugin) {
59
+ const formats = toArray(plugin.format);
60
+ if (!plugin.inputs || plugin.inputs.length === 0) {
61
+ refineNext(doExpandInputs(formats));
62
+ } else if (inputs.length === 1) {
63
+ refineNext(formats);
64
+ } else {
65
+ const expanded = [];
66
+ for (const format of formats) {
67
+ for (const input of plugin.inputs) {
68
+ expanded.push(`${format}.${input}`);
69
+ }
70
+ }
71
+ refineNext(expanded);
72
+ }
73
+ }
74
+ }
75
+
76
+ const refined = refineNext();
77
+ /** @type {{ formats: InternalModuleFormat[]; inputs: string[] }[]} */
78
+ const partitions = [];
79
+
80
+ for (const partition of refined) {
81
+ /** @type {{ format: InternalModuleFormat; input?: string }[]} */
82
+ const result = [];
83
+ for (const format of partition) {
84
+ if (format.includes('.')) {
85
+ const [, realFormat, input] = format.split(/(.*?)\.(.*)/gm);
86
+ result.push({ format: /** @type {InternalModuleFormat} */ (realFormat), input });
87
+ } else {
88
+ result.push({ format: /** @type {InternalModuleFormat} */ (format) });
89
+ }
90
+ }
91
+ /** @type {Map<InternalModuleFormat, Set<string>>} */
92
+ const mapFormatInputs = new Map();
93
+ /** @type {Set<InternalModuleFormat>} */
94
+ const formatsWithoutInputs = new Set();
95
+ for (const { format, input } of result) {
96
+ if (input) {
97
+ if (mapFormatInputs.has(format)) {
98
+ /** @type {Set<string>} */ (mapFormatInputs.get(format)).add(input);
99
+ } else {
100
+ mapFormatInputs.set(format, new Set([input]));
101
+ }
102
+ } else {
103
+ formatsWithoutInputs.add(format);
104
+ }
105
+ }
106
+ for (const format of formatsWithoutInputs) {
107
+ if (mapFormatInputs.has(format)) {
108
+ throw new Error(
109
+ `${format} is both used with inputs and without in plugins configuration and was not expanded / handled correctly. Please file an issue for pkgbld.`
110
+ );
111
+ }
112
+ mapFormatInputs.set(format, new Set(inputs));
113
+ }
114
+ /** @type {Set<string> | undefined} */
115
+ let prevInputs;
116
+ for (const inputs of mapFormatInputs.values()) {
117
+ if (prevInputs) {
118
+ if (!areSetsEqual(inputs, prevInputs)) {
119
+ throw new Error(`unbalanced inputs for partition: ${JSON.stringify(partition)}`);
120
+ }
121
+ }
122
+ prevInputs = inputs;
123
+ }
124
+ partitions.push({ formats: [...mapFormatInputs.keys()], inputs: [.../** @type {Set<string>} */ (prevInputs)] });
125
+ }
126
+
127
+ return partitions.map(({ formats, inputs }) => {
128
+ return {
129
+ input: Object.fromEntries(
130
+ inputs.map(input => [/** @type {import('./types.js').BuildEntry} */ (entriesBySourcePath.get(input)).name, input])
131
+ ),
132
+
133
+ output: formats.map(format => ({
134
+ format,
135
+ dir: configuration.paths.outputDir,
136
+ entryFileNames: fileNamePatterns[format],
137
+ plugins: getPlugins([format], inputs, true),
138
+ sourcemap: configuration.outputs.sourcemaps.some(value => value === format),
139
+ ...getExtraOutputSettings(format, inputs),
140
+ })),
141
+
142
+ plugins: getPlugins(formats, inputs, false),
143
+ };
144
+ });
145
+
146
+ /**
147
+ * @param {InternalModuleFormat} format
148
+ * @param {string[]} inputs
149
+ * @returns {Partial<OutputOptions>}
150
+ */
151
+ function getExtraOutputSettings(format, inputs) {
152
+ let result = {};
153
+ switch (format) {
154
+ case 'cjs':
155
+ case 'es':
156
+ result = { chunkFileNames: fileNamePatterns[format] };
157
+ break;
158
+ case 'umd':
159
+ if (inputs.length <= 0) {
160
+ break;
161
+ }
162
+ if (inputs.length > 1) {
163
+ throw new Error(`Cannot produce global name for multiple umd inputs in one output: ${inputs}`);
164
+ }
165
+ result = {
166
+ name: helpers.getGlobalName(inputs.join('_')),
167
+ globals: helpers.getExternalGlobalName,
168
+ };
169
+ break;
170
+ }
171
+ pluginLifecycle.extendOutputSettings(result, format, inputs, configuration);
172
+ return result;
173
+ }
174
+
175
+ /**
176
+ * @param {InternalModuleFormat[]} formats
177
+ * @param {string[]} inputs
178
+ * @param {boolean} outputPlugin
179
+ */
180
+ function getPlugins(formats, inputs, outputPlugin) {
181
+ const filteredPlugins = [];
182
+ for (const plugin of plugins) {
183
+ if (!!plugin.outputPlugin === outputPlugin) {
184
+ if (
185
+ (!plugin.format || toArray(plugin.format).some(format => formats.includes(format))) &&
186
+ (!plugin.inputs || plugin.inputs.every(input => inputs.includes(input)))
187
+ ) {
188
+ filteredPlugins.push({
189
+ instance: plugin.plugin(),
190
+ priority: plugin.priority,
191
+ });
192
+ }
193
+ }
194
+ }
195
+ filteredPlugins.sort((a, b) => a.priority - b.priority);
196
+ return filteredPlugins.map(plugin => plugin.instance);
197
+ }
198
+
199
+ /**
200
+ * @param {InternalModuleFormat[]} formats
201
+ */
202
+ function doExpandInputs(formats) {
203
+ if (inputs.length === 1) {
204
+ return formats;
205
+ }
206
+ const expanded = [];
207
+ for (const format of formats) {
208
+ if (expandInputs.has(format)) {
209
+ if (format !== 'umd') {
210
+ for (const input of inputs) {
211
+ expanded.push(`${format}.${input}`);
212
+ }
213
+ } else {
214
+ for (const entryName of configuration.outputs.umdEntries) {
215
+ expanded.push(`${format}.${packageResult.entries.require(entryName).sourcePath}`);
216
+ }
217
+ }
218
+ } else {
219
+ expanded.push(format);
220
+ }
221
+ }
222
+ return expanded;
223
+ }
224
+ }
package/src/helpers.js ADDED
@@ -0,0 +1,229 @@
1
+ import { access, constants, readFile } from 'node:fs/promises';
2
+ import path, { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { cyan, magenta } from '@niceties/ansi';
6
+
7
+ import { processPackageJson } from './options/index.js';
8
+
9
+ /**
10
+ * @typedef {import('rollup').OutputOptions} OutputOptions
11
+ * @typedef {import('type-fest').PackageJson} PackageJson
12
+ * @typedef {import('./options/index.js').PackageJson} PackageJsonO
13
+ */
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+
17
+ /**
18
+ * @param {unknown} value
19
+ * @returns {string}
20
+ */
21
+ export function camelCase(value) {
22
+ const words = String(value)
23
+ .normalize('NFKD')
24
+ .replaceAll(/\p{Mark}/gu, '')
25
+ .replaceAll(/([\p{Ll}\d])(\p{Lu})/gu, '$1 $2')
26
+ .replaceAll(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1 $2')
27
+ .match(/[\p{L}\d]+/gu);
28
+
29
+ return (words ?? [])
30
+ .map((word, index) => {
31
+ const lowerCaseWord = word.toLowerCase();
32
+ return index === 0 ? lowerCaseWord : lowerCaseWord[0].toUpperCase() + lowerCaseWord.slice(1);
33
+ })
34
+ .join('');
35
+ }
36
+
37
+ /**
38
+ * @param {string} pkgName
39
+ */
40
+ export function getHelpers(pkgName) {
41
+ /**
42
+ * @param {string} anInput
43
+ */
44
+ function getGlobalName(anInput) {
45
+ return camelCase(
46
+ path.join(
47
+ pkgName,
48
+ path.basename(anInput, path.extname(anInput)) !== 'index' ? path.basename(anInput, path.extname(anInput)) : ''
49
+ )
50
+ );
51
+ }
52
+
53
+ /**
54
+ * @param {string} id
55
+ */
56
+ function getExternalGlobalName(id) {
57
+ if (path.isAbsolute(id)) {
58
+ return getGlobalName(path.relative(__dirname, id));
59
+ }
60
+ return camelCase(id);
61
+ }
62
+
63
+ return {
64
+ getGlobalName,
65
+ getExternalGlobalName,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * @template T
71
+ * @param {T | T[] | undefined} object
72
+ * @returns {T[]}
73
+ */
74
+ export function toArray(object) {
75
+ if (Array.isArray(object)) {
76
+ return object;
77
+ }
78
+ if (object == null) {
79
+ return [];
80
+ }
81
+ return [object];
82
+ }
83
+
84
+ /**
85
+ * @param {string[] | string} input
86
+ * @returns {string}
87
+ */
88
+ export function formatInput(input) {
89
+ return (Array.isArray(input) ? input : [input ?? '']).map(item => magenta(path.basename(item, path.extname(item)))).join(', ');
90
+ }
91
+
92
+ /**
93
+ * @param {OutputOptions | OutputOptions[] | undefined} output
94
+ * @param {'dir' | 'format'} field
95
+ * @returns {string}
96
+ */
97
+ export function formatOutput(output, field) {
98
+ if (output == null) {
99
+ return '';
100
+ }
101
+ return (Array.isArray(output) ? output : [output ?? '']).map(item => cyan(/** @type {string} */ (item[field]))).join(', ');
102
+ }
103
+
104
+ /**
105
+ * @param {number} starting
106
+ * @returns {string}
107
+ */
108
+ export function getTimeDiff(starting) {
109
+ const diff = Date.now() - starting;
110
+ return diff >= 1000 ? `${(diff / 1000).toFixed(1)}s` : `${diff}ms`;
111
+ }
112
+
113
+ /**
114
+ * @template T
115
+ * @param {Set<T>} a
116
+ * @param {Set<T>} b
117
+ * @returns {boolean}
118
+ */
119
+ export const areSetsEqual = (a, b) => (a.size === b.size ? [...a].every(value => b.has(value)) : false);
120
+
121
+ /**
122
+ * @param {PackageJson} pkg
123
+ * @returns {PackageJson}
124
+ */
125
+ export function formatPackageJson(pkg) {
126
+ return /** @type {PackageJson} */ (
127
+ processPackageJson(
128
+ /** @type {PackageJsonO} */ (pkg),
129
+ key => key in pkg,
130
+ key => /** @type {Record<string, unknown>} */ (pkg)[key]
131
+ )
132
+ );
133
+ }
134
+
135
+ /**
136
+ * @param {string} file
137
+ * @returns {Promise<string | false>}
138
+ */
139
+ export async function isExists(file) {
140
+ try {
141
+ await access(file);
142
+ } catch (e) {
143
+ if (typeof e === 'object' && e != null && 'code' in e && e.code === 'ENOENT') {
144
+ return /** @type {const} */ (false);
145
+ }
146
+ throw e;
147
+ }
148
+ return file;
149
+ }
150
+
151
+ /**
152
+ * @param {string} file
153
+ * @returns {Promise<boolean>}
154
+ */
155
+ export async function isReadable(file) {
156
+ try {
157
+ await access(file, constants.R_OK);
158
+ return true;
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * @param {string} root
166
+ * @param {string} file
167
+ */
168
+ function hasFile(root, file) {
169
+ const path = join(root, file);
170
+ return isExists(path);
171
+ }
172
+
173
+ /**
174
+ * @param {string} root
175
+ * @returns {Promise<boolean>}
176
+ */
177
+ async function hasWorkspacePackageJson(root) {
178
+ const path = join(root, 'package.json');
179
+ if (!(await isReadable(path))) {
180
+ return false;
181
+ }
182
+ try {
183
+ const content = /** @type {PackageJson} */ (JSON.parse(await readFile(path, 'utf-8')) || {});
184
+ return !!content.workspaces;
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * @param {string} current
192
+ * @returns {Promise<string>}
193
+ */
194
+ export async function searchForPackageRoot(current) {
195
+ const root = current;
196
+ let dir = current;
197
+
198
+ while (dir) {
199
+ if (await hasFile(dir, 'package.json')) return dir;
200
+
201
+ const parentDir = dirname(dir);
202
+ if (parentDir === dir) break;
203
+
204
+ dir = parentDir;
205
+ }
206
+
207
+ return root;
208
+ }
209
+
210
+ /**
211
+ * @param {string} current
212
+ * @returns {Promise<string>}
213
+ */
214
+ export async function searchForWorkspaceRoot(current) {
215
+ const root = await searchForPackageRoot(current);
216
+ let dir = current;
217
+
218
+ while (dir) {
219
+ if (await hasFile(dir, 'pnpm-workspace.yaml')) return dir;
220
+ if (await hasWorkspacePackageJson(dir)) return dir;
221
+
222
+ const parentDir = dirname(dir);
223
+ if (parentDir === dir) break;
224
+
225
+ dir = parentDir;
226
+ }
227
+
228
+ return root;
229
+ }