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/index.js ADDED
@@ -0,0 +1,133 @@
1
+ /// <reference path="./rollup-plugin-preprocess.d.ts" />
2
+ import '@niceties/draftlog-appender';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ import { rollup } from 'rollup';
6
+
7
+ import { green } from '@niceties/ansi';
8
+ import { createLogger, LogLevel } from '@niceties/logger';
9
+
10
+ import { resolveBuildConfiguration } from './build-configuration.js';
11
+ import { createBuildPluginLifecycle } from './build-plugin-lifecycle.js';
12
+ import { createEjectProvider, ejectConfig } from './eject.js';
13
+ import { getJson } from './get-json.js';
14
+ import { createProvider } from './get-plugins.js';
15
+ import { getRollupConfigs } from './get-rollup-configs.js';
16
+ import { formatInput, formatOutput, formatPackageJson, getHelpers, getTimeDiff, searchForWorkspaceRoot, toArray } from './helpers.js';
17
+ import { loadPlugins } from './load-plugins.js';
18
+ import { mainLoggerText } from './messages.js';
19
+ import { processPackage } from './process-pkg.js';
20
+ import { checkTsConfig } from './process-ts-config.js';
21
+ import { writeJson } from './write-json.js';
22
+
23
+ /**
24
+ * @typedef {import('rollup').RollupOptions} RollupOptions
25
+ * @typedef {import('type-fest').PackageJson} PackageJson
26
+ * @typedef {import('./types.js').PkgbldPlugin} PkgbldPlugin
27
+ */
28
+
29
+ execute();
30
+
31
+ async function execute() {
32
+ const time = Date.now();
33
+ const mainLogger = createLogger();
34
+ mainLogger.update('preparing..');
35
+ try {
36
+ /** @type {PackageJson} */
37
+ let pkg;
38
+ /** @type {string} */
39
+ let pkgPath;
40
+ [pkgPath, pkg] = /** @type {[string, PackageJson]} */ (await getJson('package.json'));
41
+ /** @type {Set<string>} */
42
+ const loadedPlugins = new Set();
43
+ const plugins = await loadPlugins(pkg, loadedPlugins, pkgPath);
44
+ const [rootPackagePath, rootPkg] = await getJson(join(await searchForWorkspaceRoot(dirname(pkgPath)), 'package.json'));
45
+ if (rootPackagePath !== pkgPath) {
46
+ plugins.push(...(await loadPlugins(rootPkg, loadedPlugins, rootPackagePath)));
47
+ }
48
+ const pluginLifecycle = createBuildPluginLifecycle(plugins);
49
+ mainLogger.update('');
50
+ process.stdout.moveCursor?.(0, -1);
51
+ const configuration = resolveBuildConfiguration({ packageJson: pkg, pluginLifecycle });
52
+ process.stdout.moveCursor?.(0, 1);
53
+ mainLogger.update('preparing...');
54
+ await checkTsConfig(configuration, mainLogger, pluginLifecycle);
55
+ const packageResult = await processPackage(pkg, configuration, pluginLifecycle);
56
+ if (configuration.packageJson.format) {
57
+ pkg = formatPackageJson(pkg);
58
+ }
59
+ const helpers = getHelpers(/** @type {{ name: string }} */ (pkg).name);
60
+ const provider = configuration.execution.eject ? await createEjectProvider() : createProvider();
61
+ const rollupConfigs = await getRollupConfigs(provider, packageResult, configuration, helpers, pluginLifecycle);
62
+
63
+ if (!configuration.execution.bundle) {
64
+ rollupConfigs.length = 0;
65
+ }
66
+
67
+ if (configuration.execution.eject) {
68
+ await ejectConfig(rollupConfigs, pkgPath, configuration, packageResult, helpers, pkg);
69
+ mainLogger.finish(`ejected config in ${getTimeDiff(time)}`);
70
+ if (configuration.packageJson.update) {
71
+ await writeJson(pkgPath, pkg);
72
+ }
73
+ } else {
74
+ const updater = mainLoggerText(configuration.paths.sourceDir, configuration.paths.outputDir, rollupConfigs.length, time);
75
+ mainLogger.start(updater());
76
+
77
+ await Promise.all(rollupConfigs.map(config => buildConfig(config, updater)));
78
+
79
+ if (configuration.packageJson.update) {
80
+ await writeJson(pkgPath, pkg);
81
+ }
82
+ await pluginLifecycle.buildEnd(configuration);
83
+
84
+ mainLogger.finish(updater(true));
85
+ }
86
+ } catch (e) {
87
+ mainLogger.finish(String(e), LogLevel.error);
88
+ process.exit(-1);
89
+ }
90
+
91
+ /**
92
+ * @param {RollupOptions} config
93
+ * @param {() => string} updater
94
+ */
95
+ async function buildConfig(config, updater) {
96
+ const bundle = await rollup(config);
97
+ await Promise.all(toArray(config.output).map(config => bundle.write(config)));
98
+ await bundle.close();
99
+ mainLogger(
100
+ `${green('✓')} ${formatInput(
101
+ /** @type {string | string[]} */ (
102
+ typeof config.input === 'object' && !Array.isArray(config.input) ? Object.values(config.input) : config.input
103
+ )
104
+ )} [${formatOutput(config.output, 'format')}]`
105
+ );
106
+ mainLogger.update(updater());
107
+ }
108
+ }
109
+
110
+ /** @typedef {import('./types.js').Json} Json */
111
+ /** @typedef {import('./types.js').BuildConfiguration} BuildConfiguration */
112
+ /** @typedef {import('./types.js').BuildFormat} BuildFormat */
113
+ /** @typedef {import('./types.js').BuildConfigurationDraft} BuildConfigurationDraft */
114
+ /** @typedef {import('./types.js').BuildConfigurationSources} BuildConfigurationSources */
115
+ /** @typedef {import('./types.js').BuildEntry} BuildEntry */
116
+ /** @typedef {import('./types.js').BuildEntries} BuildEntries */
117
+ /** @typedef {import('./types.js').BuildEntryContribution} BuildEntryContribution */
118
+ /** @typedef {import('./types.js').BuildEntryContributions} BuildEntryContributions */
119
+ /** @typedef {import('./types.js').BuildEntryIssue} BuildEntryIssue */
120
+ /** @typedef {import('./types.js').ParsedOptions} ParsedOptions */
121
+ /** @typedef {import('./types.js').PackageProcessingResult} PackageProcessingResult */
122
+ /** @typedef {import('./types.js').PluginSharedState} PluginSharedState */
123
+ /** @typedef {import('./types.js').PluginConfigureContext} PluginConfigureContext */
124
+ /** @typedef {import('./types.js').PluginContributeEntriesContext} PluginContributeEntriesContext */
125
+ /** @typedef {import('./types.js').PluginPackageContext} PluginPackageContext */
126
+ /** @typedef {import('./types.js').PluginTsConfigContext} PluginTsConfigContext */
127
+ /** @typedef {import('./types.js').PluginRollupContext} PluginRollupContext */
128
+ /** @typedef {import('./types.js').PluginOutputContext} PluginOutputContext */
129
+ /** @typedef {import('./types.js').PluginBuildEndContext} PluginBuildEndContext */
130
+ /** @typedef {import('./types.js').PkgbldPluginFactory} PkgbldPluginFactory */
131
+ /** @typedef {import('./types.js').Provider} Provider */
132
+ /** @typedef {import('./types.js').ProvideFunction} ProvideFunction */
133
+ /** @typedef {import('./types.js').PkgbldRollupPlugin} PkgbldRollupPlugin */
@@ -0,0 +1,37 @@
1
+ import { createRequire } from 'node:module';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ /**
5
+ * @typedef {import('type-fest').PackageJson} PackageJson
6
+ */
7
+
8
+ import { isPluginPackageName } from './plugin-name.js';
9
+
10
+ /**
11
+ * @param {PackageJson} pkg
12
+ * @param {Set<string>} loaded
13
+ * @param {string} packageJsonPath
14
+ */
15
+ export async function loadPlugins(pkg, loaded, packageJsonPath) {
16
+ const resolveFromPackage = createRequire(packageJsonPath).resolve;
17
+ return await Promise.all(
18
+ [
19
+ ...new Set([
20
+ ...Object.keys(pkg.devDependencies || {}),
21
+ ...Object.keys(pkg.dependencies || {}),
22
+ ...Object.keys(pkg.peerDependencies || {}),
23
+ ]),
24
+ ]
25
+ .filter(packageName => isPluginPackageName(packageName) && !loaded.has(packageName))
26
+ .map(async packageName => {
27
+ loaded.add(packageName);
28
+ try {
29
+ const pluginPath = resolveFromPackage(packageName);
30
+ const pluginFactory = await import(pathToFileURL(pluginPath).href);
31
+ return await pluginFactory.create();
32
+ } catch (cause) {
33
+ throw new Error(`Failed to load Build plugin ${JSON.stringify(packageName)} from ${packageJsonPath}`, { cause });
34
+ }
35
+ })
36
+ );
37
+ }
@@ -0,0 +1,13 @@
1
+ import { getTimeDiff } from './helpers.js';
2
+
3
+ /**
4
+ * @param {string} sourceDir
5
+ * @param {string} dir
6
+ * @param {number} configsCount
7
+ * @param {number} startingTime
8
+ * @param {number} [finishedCount]
9
+ */
10
+ export const mainLoggerText =
11
+ (sourceDir, dir, configsCount, startingTime, finishedCount = 0) =>
12
+ (final = false) =>
13
+ `${sourceDir} → ${dir} ${final ? configsCount : finishedCount++} / ${configsCount}${final ? ` in ${getTimeDiff(startingTime)}` : ''}`;
@@ -0,0 +1,266 @@
1
+ /** @typedef {import('./types.js').PackageJson} PackageJson */
2
+
3
+ /**
4
+ * @param {unknown} value
5
+ * @returns {value is PackageJson}
6
+ */
7
+ export function isPackageJson(value) {
8
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
9
+ return false;
10
+ }
11
+ const obj = /** @type {Record<string, unknown>} */ (value);
12
+ if (obj.private !== undefined && typeof obj.private !== 'boolean') return false;
13
+ if (obj.version !== undefined && typeof obj.version !== 'string') return false;
14
+ if (obj.name !== undefined && typeof obj.name !== 'string') return false;
15
+ if (obj.main !== undefined && typeof obj.main !== 'string') return false;
16
+ if (obj.license !== undefined && typeof obj.license !== 'string') return false;
17
+ if (obj.readme !== undefined && typeof obj.readme !== 'string') return false;
18
+ if (obj.description !== undefined && typeof obj.description !== 'string') return false;
19
+ if (obj.bugs !== undefined && typeof obj.bugs !== 'string') return false;
20
+ if (obj.homepage !== undefined && typeof obj.homepage !== 'string') return false;
21
+ if (obj.bin !== undefined && typeof obj.bin !== 'string' && (typeof obj.bin !== 'object' || obj.bin === null || Array.isArray(obj.bin)))
22
+ return false;
23
+ if (
24
+ obj.author !== undefined &&
25
+ typeof obj.author !== 'string' &&
26
+ (typeof obj.author !== 'object' || obj.author === null || Array.isArray(obj.author))
27
+ )
28
+ return false;
29
+ return true;
30
+ }
31
+
32
+ /**
33
+ * @param {string} value
34
+ * @returns {string[]}
35
+ */
36
+ function CommaSeparatedString(value) {
37
+ if (value === '') return [];
38
+ return value.split(',').map((/** @type {string} */ arg) => arg.trim());
39
+ }
40
+
41
+ /**
42
+ * @param {string} value
43
+ * @returns {true | string[]}
44
+ */
45
+ function CommaSeparatedStringOrBoolean(value) {
46
+ if (value === '') {
47
+ return true;
48
+ }
49
+ return CommaSeparatedString(value);
50
+ }
51
+
52
+ export const cliFlagsDefaults = {
53
+ formats: /** @type {string[]} */ (['es', 'cjs']),
54
+ umd: /** @type {string[]} */ ([]),
55
+ compress: /** @type {string[]} */ (['umd']),
56
+ sourcemaps: /** @type {string[]} */ (['umd']),
57
+ preprocess: /** @type {string[]} */ ([]),
58
+ dest: 'dist',
59
+ src: 'src',
60
+ bin: /** @type {string[] | undefined} */ (undefined),
61
+ includeExternals: /** @type {boolean | string[]} */ (false),
62
+ eject: false,
63
+ tsConfig: false,
64
+ updatePackageJson: true,
65
+ commonjsPattern: '[name].cjs',
66
+ esmPattern: '[name].mjs',
67
+ umdPattern: '[name].umd.js',
68
+ formatPackageJson: false,
69
+ };
70
+
71
+ /**
72
+ * @typedef {{
73
+ * type: 'string' | 'boolean' | ((value: string) => unknown);
74
+ * description: string;
75
+ * default?: string | boolean | string[] | boolean[];
76
+ * optionalValue?: boolean;
77
+ * }} CliFlag
78
+ */
79
+
80
+ /** @type {Record<string, CliFlag>} */
81
+ export const cliFlags = {
82
+ umd: {
83
+ type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
84
+ description: 'Package subpath exports in UMD format',
85
+ },
86
+ compress: {
87
+ type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
88
+ description: 'Compress formats using terser',
89
+ },
90
+ sourcemaps: {
91
+ type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
92
+ description: 'Emit sourcemaps for the specified formats',
93
+ },
94
+ formats: {
95
+ type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
96
+ description: 'Formats to emit',
97
+ },
98
+ preprocess: {
99
+ type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
100
+ description: 'Preprocess entry points / subpath exports',
101
+ },
102
+ dest: {
103
+ type: /** @type {'string'} */ ('string'),
104
+ description: 'Output directory',
105
+ default: cliFlagsDefaults.dest,
106
+ },
107
+ src: {
108
+ type: /** @type {'string'} */ ('string'),
109
+ description: 'Source directory',
110
+ default: cliFlagsDefaults.src,
111
+ },
112
+ bin: {
113
+ type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
114
+ description: 'Executable files',
115
+ },
116
+ includeExternals: {
117
+ type: /** @type {(value: string) => true | string[]} */ (CommaSeparatedStringOrBoolean),
118
+ description: 'Include all/specified externals into the result bundle(s)',
119
+ optionalValue: true,
120
+ },
121
+ eject: {
122
+ type: /** @type {'boolean'} */ ('boolean'),
123
+ description: 'Eject config',
124
+ default: cliFlagsDefaults.eject,
125
+ },
126
+ tsConfig: {
127
+ type: /** @type {'boolean'} */ ('boolean'),
128
+ description: 'Create / update tsconfig.json',
129
+ default: cliFlagsDefaults.tsConfig,
130
+ },
131
+ updatePackageJson: {
132
+ type: /** @type {'boolean'} */ ('boolean'),
133
+ description: 'Create / update package.json',
134
+ default: cliFlagsDefaults.updatePackageJson,
135
+ },
136
+ commonjsPattern: {
137
+ type: /** @type {'string'} */ ('string'),
138
+ description: 'CommonJS output file name pattern',
139
+ default: cliFlagsDefaults.commonjsPattern,
140
+ },
141
+ esmPattern: {
142
+ type: /** @type {'string'} */ ('string'),
143
+ description: 'ES output file name pattern',
144
+ default: cliFlagsDefaults.esmPattern,
145
+ },
146
+ umdPattern: {
147
+ type: /** @type {'string'} */ ('string'),
148
+ description: 'UMD output file name pattern',
149
+ default: cliFlagsDefaults.umdPattern,
150
+ },
151
+ formatPackageJson: {
152
+ type: /** @type {'boolean'} */ ('boolean'),
153
+ description: 'Format package.json',
154
+ default: cliFlagsDefaults.formatPackageJson,
155
+ },
156
+ pack: {
157
+ type: /** @type {'boolean'} */ ('boolean'),
158
+ description: 'Pack',
159
+ default: true,
160
+ },
161
+ exports: {
162
+ type: /** @type {'boolean'} */ ('boolean'),
163
+ description: 'Add exports field to package.json',
164
+ default: true,
165
+ },
166
+ clean: {
167
+ type: /** @type {'boolean'} */ ('boolean'),
168
+ description: 'Clean the output directory',
169
+ default: true,
170
+ },
171
+ bundle: {
172
+ type: /** @type {'boolean'} */ ('boolean'),
173
+ description: 'Bundle',
174
+ default: true,
175
+ },
176
+ removeLegalComments: {
177
+ type: /** @type {'boolean'} */ ('boolean'),
178
+ description: 'Remove legal comments',
179
+ default: false,
180
+ },
181
+ };
182
+
183
+ export const packageJsonFieldsOrder = new Set([
184
+ 'private',
185
+ 'type',
186
+ 'version',
187
+ 'name',
188
+ 'scope', // custom
189
+ 'description',
190
+ 'license',
191
+ 'author',
192
+ 'contributors',
193
+ 'funding',
194
+ 'bin',
195
+ 'main',
196
+ 'browser',
197
+ 'unpkg',
198
+ 'module',
199
+ 'svelte',
200
+ 'exports',
201
+ 'imports',
202
+ 'types',
203
+ 'typings',
204
+ 'typesVersions', // non standard but required for typescript with resolution other than nodenext
205
+ 'files',
206
+ 'packageManager',
207
+ 'sideEffects',
208
+ 'engines',
209
+ 'os',
210
+ 'cpu',
211
+ 'man',
212
+ 'directories',
213
+ 'repository',
214
+ 'bugs',
215
+ 'homepage',
216
+ 'readme',
217
+ 'keywords',
218
+ 'scripts',
219
+ 'config',
220
+ 'dependencies',
221
+ 'devDependencies',
222
+ 'peerDependencies',
223
+ 'peerDependenciesMeta',
224
+ 'bundleDependencies',
225
+ 'bundledDependencies',
226
+ 'optionalDependencies',
227
+ 'overrides',
228
+ 'publishConfig',
229
+ 'workspaces',
230
+ ]);
231
+
232
+ /**
233
+ * @param {PackageJson} pkg
234
+ * @param {(key: string) => boolean} needTreatment
235
+ * @param {(key: string) => unknown} treatKey
236
+ * @returns {PackageJson}
237
+ */
238
+ export function processPackageJson(pkg, needTreatment, treatKey) {
239
+ /** @type {PackageJson} */
240
+ const newPkg = {};
241
+
242
+ for (const key of packageJsonFieldsOrder) {
243
+ if (needTreatment(key)) {
244
+ /** @type {Record<string, unknown>} */ (newPkg)[key] = treatKey(key);
245
+ }
246
+ }
247
+
248
+ for (const key in pkg) {
249
+ if (!packageJsonFieldsOrder.has(key)) {
250
+ /** @type {Record<string, unknown>} */ (newPkg)[key] = /** @type {Record<string, unknown>} */ (pkg)[key];
251
+ }
252
+ }
253
+
254
+ return newPkg;
255
+ }
256
+
257
+ /**
258
+ * @template {object | null | number | string | boolean} T
259
+ * @param {T} json
260
+ * @param {string | null} [current] Existing file contents used to detect indentation.
261
+ * @returns {string}
262
+ */
263
+ export function toFormattedJson(json, current) {
264
+ const indent = current?.match(/^[ \t]+(?=\S)/m)?.[0] ?? 2;
265
+ return `${JSON.stringify(json, null, indent)}\n`;
266
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @typedef {{
3
+ * private?: boolean;
4
+ * version?: string;
5
+ * name?: string;
6
+ * bin?: string | Record<string, string>;
7
+ * main?: string;
8
+ * license?: string;
9
+ * readme?: string;
10
+ * author?: string | { name?: string; email?: string; url?: string; };
11
+ * description?: string;
12
+ * scripts?: { [key: string]: string; };
13
+ * repository?: { type?: string; url?: string; };
14
+ * files?: string[];
15
+ * bugs?: string;
16
+ * homepage?: string;
17
+ * dependencies?: Record<string, string>;
18
+ * devDependencies?: Record<string, string>;
19
+ * peerDependencies?: Record<string, string>;
20
+ * exports?: Record<string, string | Record<string, string>>;
21
+ * }} PackageJson
22
+ */
23
+
24
+ export {};
@@ -0,0 +1,6 @@
1
+ const PLUGIN_PACKAGE_RE = /^(?:@[^/]+\/)?pkgbld-plugin-/;
2
+
3
+ /** @param {string} packageName */
4
+ export function isPluginPackageName(packageName) {
5
+ return PLUGIN_PACKAGE_RE.test(packageName);
6
+ }
@@ -0,0 +1,10 @@
1
+ export const Priority = /** @type {const} */ ({
2
+ preprocess: 1000,
3
+ cleanup: 1000,
4
+ externals: 2000,
5
+ resolve: 3000,
6
+ commonjs: 4000,
7
+ transpile: 6000,
8
+ compress: 10000,
9
+ finalize: 20000,
10
+ });