jiek 2.0.2-alpha.13 → 2.0.2-alpha.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,370 @@
1
+ import * as childProcess from 'node:child_process'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ import { bump, type BumperType, TAGS } from '@jiek/utils/bumper'
6
+ import { program } from 'commander'
7
+ import detectIndent from 'detect-indent'
8
+ import { applyEdits, modify } from 'jsonc-parser'
9
+
10
+ import type { ProjectsGraph } from '../utils/filterSupport'
11
+ import { getSelectedProjectsGraph } from '../utils/filterSupport'
12
+ import { getExports } from '../utils/getExports'
13
+ import { loadConfig } from '../utils/loadConfig'
14
+ import { outdirDescription } from './descriptions'
15
+
16
+ declare module 'jiek' {
17
+ export interface Config {
18
+ publish?: {
19
+ /**
20
+ * @default false
21
+ */
22
+ withSuffix?: boolean
23
+ /**
24
+ * @default true
25
+ */
26
+ withSource?: boolean
27
+ }
28
+ }
29
+ }
30
+
31
+ const description = `
32
+ Publish package to npm registry, and auto generate exports field and other fields in published package.json.
33
+ If you want to through the options to the \`pnpm publish\` command, you can pass the options after '--'.
34
+ `.trim()
35
+
36
+ async function forEachSelectedProjectsGraphEntries(
37
+ callback: (dir: string, manifest: NonNullable<ProjectsGraph['value']>[string]) => void
38
+ ) {
39
+ const { value = {} } = await getSelectedProjectsGraph() ?? {}
40
+ const selectedProjectsGraphEntries = Object.entries(value)
41
+ if (selectedProjectsGraphEntries.length === 0) {
42
+ throw new Error('no packages selected')
43
+ }
44
+ for (const [dir, manifest] of selectedProjectsGraphEntries) {
45
+ callback(dir, manifest)
46
+ }
47
+ }
48
+
49
+ program
50
+ .command('publish')
51
+ .description(description)
52
+ .aliases(['pub', 'p'])
53
+ .option('-b, --bumper <bumper>', 'bump version', 'patch')
54
+ .option('-no-b, --no-bumper', 'no bump version')
55
+ .option('-o, --outdir <OUTDIR>', outdirDescription, String, 'dist')
56
+ .action(async ({ outdir, bumper }: {
57
+ outdir?: string
58
+ bumper: false | BumperType
59
+ }) => {
60
+ let shouldPassThrough = false
61
+
62
+ const passThroughOptions = process.argv
63
+ .reduce(
64
+ (acc, value) => {
65
+ if (shouldPassThrough) {
66
+ acc.push(value)
67
+ }
68
+ if (value === '--') {
69
+ shouldPassThrough = true
70
+ }
71
+ return acc
72
+ },
73
+ [] as string[]
74
+ )
75
+
76
+ await forEachSelectedProjectsGraphEntries(dir => {
77
+ const args = ['pnpm', 'publish', '--access', 'public', '--no-git-checks']
78
+ if (bumper && TAGS.includes(bumper)) {
79
+ args.push('--tag', bumper)
80
+ }
81
+ args.push(...passThroughOptions)
82
+ childProcess.execSync(args.join(' '), {
83
+ cwd: dir,
84
+ stdio: 'inherit',
85
+ env: {
86
+ ...process.env,
87
+ JIEK_PUBLISH_OUTDIR: JSON.stringify(outdir),
88
+ JIEK_PUBLISH_BUMPER: JSON.stringify(bumper)
89
+ }
90
+ })
91
+ })
92
+ })
93
+
94
+ async function prepublish() {
95
+ const {
96
+ JIEK_PUBLISH_OUTDIR: outdirEnv,
97
+ JIEK_PUBLISH_BUMPER: bumperEnv
98
+ } = process.env
99
+ const outdir = outdirEnv ? JSON.parse(outdirEnv) : 'dist'
100
+ const bumper = bumperEnv ? JSON.parse(bumperEnv) : false
101
+
102
+ const generateNewManifest = (dir: string, manifest: NonNullable<ProjectsGraph['value']>[string]) => {
103
+ const { name, type, exports: entrypoints = {} } = manifest
104
+ if (!name) {
105
+ throw new Error(`package.json in ${dir} must have a name field`)
106
+ }
107
+
108
+ const pkgIsModule = type === 'module'
109
+ const newManifest = { ...manifest }
110
+ const [resolvedEntrypoints, exports, resolvedOutdir] = getExports({
111
+ entrypoints,
112
+ pkgIsModule,
113
+ pkgName: name,
114
+ config: loadConfig(dir),
115
+ dir,
116
+ defaultOutdir: outdir,
117
+ noFilter: true,
118
+ isPublish: true
119
+ })
120
+ newManifest.exports = {
121
+ ...resolvedEntrypoints,
122
+ ...exports
123
+ }
124
+ return [newManifest, resolvedOutdir] as const
125
+ }
126
+
127
+ const generateNewPackageJSONString = ({
128
+ oldJSONString,
129
+ oldJSON,
130
+ manifest,
131
+ formattingOptions
132
+ }: {
133
+ oldJSONString: string
134
+ oldJSON: Record<string, unknown>
135
+ manifest: NonNullable<ProjectsGraph['value']>[string]
136
+ formattingOptions: {
137
+ tabSize: number
138
+ insertSpaces: boolean
139
+ }
140
+ }) => {
141
+ let newJSONString = oldJSONString
142
+ newJSONString = applyEdits(
143
+ newJSONString,
144
+ modify(
145
+ newJSONString,
146
+ ['publishConfig', 'typesVersions'],
147
+ {
148
+ '<5.0': {
149
+ '*': [
150
+ '*',
151
+ `./*`,
152
+ `./*/index.d.ts`,
153
+ `./*/index.d.mts`,
154
+ `./*/index.d.cts`
155
+ ]
156
+ }
157
+ },
158
+ { formattingOptions }
159
+ )
160
+ )
161
+ for (const [key, value] of Object.entries(manifest)) {
162
+ if (key === 'version') continue
163
+ if (JSON.stringify(value) === JSON.stringify(oldJSON[key])) continue
164
+
165
+ if (key !== 'exports') {
166
+ newJSONString = applyEdits(
167
+ newJSONString,
168
+ modify(
169
+ newJSONString,
170
+ ['publishConfig', key],
171
+ value,
172
+ { formattingOptions }
173
+ )
174
+ )
175
+ } else {
176
+ const exports = value as Record<string, unknown>
177
+ for (const [k, v] of Object.entries(exports)) {
178
+ newJSONString = applyEdits(
179
+ newJSONString,
180
+ modify(
181
+ newJSONString,
182
+ ['publishConfig', 'exports', k],
183
+ v,
184
+ { formattingOptions }
185
+ )
186
+ )
187
+ }
188
+ const index = exports?.['.']
189
+ const indexPublishConfig: Record<string, string> = {}
190
+ if (index) {
191
+ switch (typeof index) {
192
+ case 'string':
193
+ indexPublishConfig[
194
+ manifest?.type === 'module' ? 'module' : 'main'
195
+ ] = index
196
+ break
197
+ case 'object': {
198
+ const indexExports = index as Record<string, string>
199
+ indexPublishConfig.main = indexExports['require'] ?? indexExports['default']
200
+ indexPublishConfig.module = indexExports['import'] ?? indexExports['module'] ?? indexExports['default']
201
+ break
202
+ }
203
+ }
204
+ for (const [k, v] of Object.entries(indexPublishConfig)) {
205
+ if (v === undefined) continue
206
+ newJSONString = applyEdits(
207
+ newJSONString,
208
+ modify(
209
+ newJSONString,
210
+ ['publishConfig', k],
211
+ v,
212
+ { formattingOptions }
213
+ )
214
+ )
215
+ }
216
+ }
217
+ }
218
+ }
219
+ if (oldJSON['peerDependencies']) {
220
+ const peerDependenciesMeta = Object.keys(oldJSON['peerDependencies']).reduce(
221
+ (acc, key) => {
222
+ acc[key] = { optional: true }
223
+ return acc
224
+ },
225
+ {} as Record<string, { optional: boolean }>
226
+ )
227
+ newJSONString = applyEdits(
228
+ newJSONString,
229
+ modify(
230
+ newJSONString,
231
+ ['peerDependenciesMeta'],
232
+ peerDependenciesMeta,
233
+ { formattingOptions }
234
+ )
235
+ )
236
+ }
237
+ if (oldJSON['files']) {
238
+ newJSONString = applyEdits(
239
+ newJSONString,
240
+ modify(
241
+ newJSONString,
242
+ ['files'],
243
+ undefined,
244
+ { formattingOptions }
245
+ )
246
+ )
247
+ }
248
+ return newJSONString
249
+ }
250
+
251
+ await forEachSelectedProjectsGraphEntries((dir, originalManifest) => {
252
+ const [manifest, resolvedOutdir] = generateNewManifest(dir, originalManifest)
253
+ const resolveByDir = (...paths: string[]) => path.resolve(dir, ...paths)
254
+
255
+ const oldJSONString = fs.readFileSync(resolveByDir('package.json'), 'utf-8')
256
+ const oldJSON = JSON.parse(oldJSONString) ?? '0.0.0'
257
+ if (typeof oldJSON.version !== 'string') {
258
+ throw new Error(`${dir}/package.json must have a version field with a string value`)
259
+ }
260
+
261
+ // TODO detectIndent by editorconfig
262
+ const { indent = ' ' } = detectIndent(oldJSONString)
263
+ const formattingOptions = {
264
+ tabSize: indent.length,
265
+ insertSpaces: true
266
+ }
267
+
268
+ const newVersion = bumper ? bump(oldJSON.version, bumper) : oldJSON.version
269
+ const modifyVersionPackageJSON = applyEdits(
270
+ oldJSONString,
271
+ modify(oldJSONString, ['version'], newVersion, { formattingOptions })
272
+ )
273
+
274
+ const newJSONString = generateNewPackageJSONString({
275
+ oldJSONString: modifyVersionPackageJSON,
276
+ oldJSON: {
277
+ ...oldJSON,
278
+ version: newVersion
279
+ },
280
+ manifest,
281
+ formattingOptions
282
+ })
283
+
284
+ const withPublishConfigDirectoryOldJSONString = applyEdits(
285
+ modifyVersionPackageJSON,
286
+ modify(modifyVersionPackageJSON, ['publishConfig', 'directory'], resolvedOutdir, { formattingOptions })
287
+ )
288
+
289
+ if (!fs.existsSync(resolveByDir(resolvedOutdir))) {
290
+ fs.mkdirSync(resolveByDir(resolvedOutdir))
291
+ }
292
+ const jiekTempDir = resolveByDir('node_modules/.jiek/.tmp')
293
+ if (!fs.existsSync(resolveByDir(jiekTempDir))) {
294
+ fs.mkdirSync(resolveByDir(jiekTempDir), { recursive: true })
295
+ }
296
+
297
+ fs.writeFileSync(resolveByDir(resolvedOutdir, 'package.json'), newJSONString)
298
+ fs.writeFileSync(resolveByDir(jiekTempDir, 'package.json'), modifyVersionPackageJSON)
299
+ fs.writeFileSync(resolveByDir('package.json'), withPublishConfigDirectoryOldJSONString)
300
+
301
+ if (oldJSON.files) {
302
+ if (!Array.isArray(oldJSON.files)) {
303
+ throw new Error(`${dir}/package.json files field must be an array`)
304
+ }
305
+ if (Array.isArray(oldJSON.files) && oldJSON.files.every((file: unknown) => typeof file !== 'string')) {
306
+ throw new Error(`${dir}/package.json files field must be an array of string`)
307
+ }
308
+ }
309
+ const resolvedOutdirAbs = resolveByDir(resolvedOutdir)
310
+ const files = (
311
+ (oldJSON.files as undefined | string[]) ?? fs.readdirSync(resolveByDir('.'))
312
+ ).filter(file => file === 'node_modules' || resolveByDir(file) !== resolvedOutdirAbs)
313
+
314
+ for (const file of files) {
315
+ const path = resolveByDir(file)
316
+ try {
317
+ const stat = fs.statSync(path)
318
+ if (stat.isDirectory()) {
319
+ fs.cpSync(path, resolveByDir(resolvedOutdir, file), { recursive: true })
320
+ continue
321
+ }
322
+ if (stat.isFile()) {
323
+ fs.cpSync(path, resolveByDir(resolvedOutdir, file))
324
+ continue
325
+ }
326
+ } catch (e) {
327
+ console.warn(String(e))
328
+ continue
329
+ }
330
+ throw new Error(`file type of ${path} is not supported`)
331
+ }
332
+ })
333
+ }
334
+
335
+ async function postpublish() {
336
+ await forEachSelectedProjectsGraphEntries(dir => {
337
+ const jiekTempDir = path.resolve(dir, 'node_modules/.jiek/.tmp')
338
+ const packageJSON = path.resolve(dir, 'package.json')
339
+ const jiekTempPackageJSON = path.resolve(jiekTempDir, 'package.json')
340
+ if (fs.existsSync(jiekTempPackageJSON)) {
341
+ fs.copyFileSync(jiekTempPackageJSON, packageJSON)
342
+ fs.rmSync(jiekTempPackageJSON)
343
+ console.log(`${dir}/package.json has been restored`)
344
+ } else {
345
+ throw new Error(
346
+ `jiek temp \`${dir}/package.json\` not found, please confirm the jiek pre-publish command has been executed`
347
+ )
348
+ }
349
+ })
350
+ }
351
+
352
+ program
353
+ .action(async () => {
354
+ const {
355
+ npm_lifecycle_event: NPM_LIFECYCLE_EVENT
356
+ } = process.env
357
+ switch (NPM_LIFECYCLE_EVENT) {
358
+ case 'prepublish':
359
+ await prepublish()
360
+ break
361
+ case 'postpublish':
362
+ await postpublish()
363
+ break
364
+ default:
365
+ program.help()
366
+ }
367
+ })
368
+
369
+ program.command('prepublish').action(prepublish)
370
+ program.command('postpublish').action(postpublish)
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type {} from './commands/base'
2
+ import type {} from './commands/build'
3
+ import type {} from './commands/init'
4
+ import type {} from './commands/publish'
5
+
6
+ export interface Config {}
7
+
8
+ export const defineConfig = (config: Config) => config
package/src/inner.ts ADDED
@@ -0,0 +1,11 @@
1
+ let resolve: () => void
2
+
3
+ export let actionFuture: Promise<void>
4
+
5
+ export function actionDone() {
6
+ resolve()
7
+ }
8
+
9
+ export function actionRestore() {
10
+ actionFuture = new Promise<void>(r => resolve = r)
11
+ }
@@ -0,0 +1,137 @@
1
+ import type { InputPluginOption, OutputOptions } from 'rollup'
2
+
3
+ export type Mapping2ROO<K extends keyof OutputOptions> = OutputOptions[K] | {
4
+ js?: OutputOptions[K]
5
+ dts?: OutputOptions[K]
6
+ }
7
+
8
+ export interface ConfigGenerateContext {
9
+ path: string
10
+ name: string
11
+ input: string
12
+ output: string
13
+ external: (string | RegExp)[]
14
+ pkgIsModule: boolean
15
+ conditionals: string[]
16
+ }
17
+
18
+ export type OutputControl = boolean | ((context: ConfigGenerateContext) => boolean)
19
+
20
+ export const BUILDER_TYPES = ['esbuild', 'swc'] as const
21
+
22
+ export const BUILDER_TYPE_PACKAGE_NAME_MAP = {
23
+ esbuild: 'rollup-plugin-esbuild',
24
+ swc: 'rollup-plugin-swc3'
25
+ }
26
+
27
+ export interface TemplateOptions {
28
+ /**
29
+ * When the user configures type: module, the generated output from entry points that don't
30
+ * have cts as a suffix will automatically include the CJS version.
31
+ * if it is not configured, and the generated output from entry points that do not have mts
32
+ * as a suffix will automatically include the ESM version.
33
+ *
34
+ * @default true
35
+ */
36
+ crossModuleConvertor?: boolean
37
+ /**
38
+ * Auto-detect the builder from the installed dependencies.
39
+ * If the builder is not installed, it will prompt the user to install it.
40
+ * If exists multiple builders, it will fall back to the 'esbuild'.
41
+ *
42
+ * @default 'esbuild'
43
+ */
44
+ builder?:
45
+ | typeof BUILDER_TYPES[number]
46
+ | ({
47
+ type: 'esbuild'
48
+ } & import('rollup-plugin-esbuild').Options)
49
+ | ({
50
+ type: 'swc'
51
+ } & import('rollup-plugin-swc3').PluginOptions)
52
+ output?: {
53
+ /**
54
+ * @default true
55
+ *
56
+ * When minify is set to true, the output will with minified files.
57
+ * When minify is set to 'only-minify', the output will direct output minified files.
58
+ */
59
+ minify?: boolean | 'only-minify'
60
+ minifyOptions?:
61
+ | typeof BUILDER_TYPES[number]
62
+ | 'terser'
63
+ | (
64
+ {
65
+ type: 'terser'
66
+ } & import('@rollup/plugin-terser').Options
67
+ )
68
+ | (
69
+ {
70
+ type: 'esbuild'
71
+ } & Parameters<typeof import('rollup-plugin-esbuild').minify>[0]
72
+ )
73
+ | (
74
+ {
75
+ type: 'swc'
76
+ } & Parameters<typeof import('rollup-plugin-swc3').minify>[0]
77
+ )
78
+ /**
79
+ * @default 'dist'
80
+ */
81
+ dir?: Mapping2ROO<'dir'>
82
+ sourcemap?: Mapping2ROO<'sourcemap'>
83
+ strict?: Mapping2ROO<'strict'>
84
+ js?: OutputControl
85
+ dts?: OutputControl
86
+ }
87
+ /**
88
+ * Set the external dependencies of the package.
89
+ */
90
+ external?: (string | RegExp)[]
91
+ plugins?:
92
+ | InputPluginOption
93
+ | ((type: 'js' | 'dts', context: ConfigGenerateContext) => InputPluginOption)
94
+ | {
95
+ js: InputPluginOption
96
+ dts?: InputPluginOption
97
+ }
98
+ | {
99
+ js?: InputPluginOption
100
+ dts: InputPluginOption
101
+ }
102
+ }
103
+
104
+ export type RollupProgressEvent =
105
+ | {
106
+ type: 'init'
107
+ data: {
108
+ leafMap: Map<string, string[][]>
109
+ targetsLength: number
110
+ }
111
+ }
112
+ | {
113
+ type: 'watchChange'
114
+ data: {
115
+ id: string
116
+ name: string
117
+ path: string
118
+ input: string
119
+ }
120
+ }
121
+ | {
122
+ type: 'debug'
123
+ data: unknown
124
+ }
125
+ | {
126
+ type: 'progress'
127
+ data: {
128
+ // name, path, exportConditions, input
129
+ name: string
130
+ path: string
131
+ exportConditions: string[]
132
+ input: string
133
+ tags?: string[]
134
+ event?: string
135
+ message?: string
136
+ }
137
+ }