jiek 2.0.2 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,7 @@ import { program } from 'commander'
7
7
  import detectIndent from 'detect-indent'
8
8
  import { applyEdits, modify } from 'jsonc-parser'
9
9
 
10
- import { actionDone, actionRestore } from '../inner'
10
+ import type { ProjectsGraph } from '../utils/filterSupport'
11
11
  import { getSelectedProjectsGraph } from '../utils/filterSupport'
12
12
  import { getExports } from '../utils/getExports'
13
13
  import { loadConfig } from '../utils/loadConfig'
@@ -28,177 +28,390 @@ declare module 'jiek' {
28
28
  }
29
29
  }
30
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
+
31
49
  program
32
50
  .command('publish')
51
+ .description(description)
33
52
  .aliases(['pub', 'p'])
34
53
  .option('-b, --bumper <bumper>', 'bump version', 'patch')
35
54
  .option('-no-b, --no-bumper', 'no bump version')
36
55
  .option('-o, --outdir <OUTDIR>', outdirDescription, String, 'dist')
37
- .option('-s, --silent', 'no output')
38
- .option('-p, --preview', 'preview publish')
39
- .action(async ({ outdir, preview, silent, bumper, ...options }: {
56
+ .action(async ({ outdir, bumper }: {
40
57
  outdir?: string
41
- preview?: boolean
42
- silent?: boolean
43
58
  bumper: false | BumperType
44
59
  }) => {
45
- actionRestore()
46
-
47
- const { value = {} } = await getSelectedProjectsGraph() ?? {}
48
- const selectedProjectsGraphEntries = Object.entries(value)
49
- if (selectedProjectsGraphEntries.length === 0) {
50
- throw new Error('no packages selected')
51
- }
52
- const manifests = selectedProjectsGraphEntries
53
- .map(([dir, manifest]) => {
54
- const { name, type, exports: entrypoints = {} } = manifest
55
- if (!name) {
56
- throw new Error(`package.json in ${dir} must have a name field`)
57
- }
60
+ let shouldPassThrough = false
58
61
 
59
- const pkgIsModule = type === 'module'
60
- const newManifest = { ...manifest }
61
- const [resolvedEntrypoints, exports, resolvedOutdir] = getExports({
62
- entrypoints,
63
- pkgIsModule,
64
- pkgName: name,
65
- config: loadConfig(dir),
66
- dir,
67
- defaultOutdir: outdir,
68
- noFilter: true,
69
- isPublish: true
70
- })
71
- newManifest.exports = {
72
- ...resolvedEntrypoints,
73
- ...exports
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)
74
89
  }
75
- return [dir, newManifest, resolvedOutdir] as const
76
90
  })
77
- const passArgs = Object
78
- .entries(options)
79
- .reduce((acc, [key, value]) => {
80
- if (value) {
81
- acc.push(`--${key}`, value as string)
82
- }
83
- return acc
84
- }, [] as string[])
85
- for (const [dir, manifest, resolvedOutdir] of manifests) {
86
- const oldJSONString = fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')
87
- const oldJSON = JSON.parse(oldJSONString) ?? '0.0.0'
88
- const newVersion = bumper ? bump(oldJSON.version, bumper) : oldJSON.version
89
- // TODO detectIndent by editorconfig
90
- const { indent = ' ' } = detectIndent(oldJSONString)
91
- const formattingOptions = {
92
- tabSize: indent.length,
93
- insertSpaces: true
94
- }
95
- let newJSONString = oldJSONString
96
- newJSONString = applyEdits(
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(
97
145
  newJSONString,
98
- modify(
99
- newJSONString,
100
- ['version'],
101
- newVersion,
102
- { formattingOptions }
103
- )
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 }
104
159
  )
105
- for (const [key, value] of Object.entries(manifest)) {
106
- if (JSON.stringify(value) === JSON.stringify(oldJSON[key])) continue
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
107
164
 
108
- if (key !== 'exports') {
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)) {
109
178
  newJSONString = applyEdits(
110
179
  newJSONString,
111
180
  modify(
112
181
  newJSONString,
113
- ['publishConfig', key],
114
- value,
182
+ ['publishConfig', 'exports', k],
183
+ v,
115
184
  { formattingOptions }
116
185
  )
117
186
  )
118
- } else {
119
- const exports = value as Record<string, unknown>
120
- for (const [k, v] of Object.entries(exports)) {
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
121
206
  newJSONString = applyEdits(
122
207
  newJSONString,
123
208
  modify(
124
209
  newJSONString,
125
- ['publishConfig', 'exports', k],
210
+ ['publishConfig', k],
126
211
  v,
127
212
  { formattingOptions }
128
213
  )
129
214
  )
130
215
  }
131
- const index = exports?.['.']
132
- const indexPublishConfig: Record<string, string> = {}
133
- if (index) {
134
- switch (typeof index) {
135
- case 'string':
136
- indexPublishConfig[
137
- manifest?.type === 'module' ? 'module' : 'main'
138
- ] = index
139
- break
140
- case 'object': {
141
- const indexExports = index as Record<string, string>
142
- indexPublishConfig.main = indexExports['require'] ?? indexExports['default']
143
- indexPublishConfig.module = indexExports['import'] ?? indexExports['module'] ?? indexExports['default']
144
- break
145
- }
146
- }
147
- for (const [k, v] of Object.entries(indexPublishConfig)) {
148
- if (v === undefined) continue
149
- newJSONString = applyEdits(
150
- newJSONString,
151
- modify(
152
- newJSONString,
153
- ['publishConfig', k],
154
- v,
155
- { formattingOptions }
156
- )
157
- )
158
- }
159
- }
160
216
  }
161
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
+ )
162
227
  newJSONString = applyEdits(
163
228
  newJSONString,
164
229
  modify(
165
230
  newJSONString,
166
- ['publishConfig', 'typesVersions'],
167
- {
168
- '<5.0': {
169
- '*': [
170
- '*',
171
- `${resolvedOutdir}/*`,
172
- `${resolvedOutdir}/*/index.d.ts`,
173
- `${resolvedOutdir}/*/index.d.mts`,
174
- `${resolvedOutdir}/*/index.d.cts`
175
- ]
176
- }
177
- },
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,
178
244
  { formattingOptions }
179
245
  )
180
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
+ const allBuildFiles = fs
302
+ .readdirSync(resolveByDir(resolvedOutdir), { recursive: true })
303
+ .filter(file => typeof file === 'string')
304
+ .filter(file => file !== 'package.json')
305
+ for (const file of allBuildFiles) {
306
+ const filepath = resolveByDir(resolvedOutdir, file)
307
+ const stat = fs.statSync(filepath)
308
+ if (stat.isDirectory()) {
309
+ const existsIndexFile = allBuildFiles
310
+ .some(f =>
311
+ [
312
+ path.join(file, 'index.js'),
313
+ path.join(file, 'index.mjs'),
314
+ path.join(file, 'index.cjs')
315
+ ].includes(f)
316
+ )
317
+ if (existsIndexFile) {
318
+ const cpDistPath = resolveByDir(resolvedOutdir, resolvedOutdir, file)
319
+ const pkgJSONPath = resolveByDir(resolvedOutdir, file, 'package.json')
320
+ const relativePath = path.relative(filepath, cpDistPath)
321
+ const { type } = manifest
322
+ fs.writeFileSync(
323
+ pkgJSONPath,
324
+ JSON.stringify({
325
+ type,
326
+ main: [relativePath, `index.${type === 'module' ? 'c' : ''}js`].join('/'),
327
+ module: [relativePath, `index.${type === 'module' ? '' : 'm'}js`].join('/')
328
+ })
329
+ )
330
+ }
331
+ }
332
+ }
333
+ fs.mkdirSync(resolveByDir(resolvedOutdir, resolvedOutdir))
334
+ for (const file of allBuildFiles) {
335
+ const filepath = resolveByDir(resolvedOutdir, file)
336
+ const newFilepath = resolveByDir(resolvedOutdir, resolvedOutdir, file)
337
+ const stat = fs.statSync(filepath)
338
+ if (stat.isDirectory()) {
339
+ fs.mkdirSync(newFilepath, { recursive: true })
340
+ continue
341
+ }
342
+ if (stat.isFile()) {
343
+ fs.cpSync(filepath, newFilepath)
344
+ fs.rmSync(filepath)
345
+ }
346
+ }
347
+
348
+ if (oldJSON.files) {
349
+ if (!Array.isArray(oldJSON.files)) {
350
+ throw new Error(`${dir}/package.json files field must be an array`)
351
+ }
352
+ if (Array.isArray(oldJSON.files) && oldJSON.files.every((file: unknown) => typeof file !== 'string')) {
353
+ throw new Error(`${dir}/package.json files field must be an array of string`)
354
+ }
355
+ }
356
+ const resolvedOutdirAbs = resolveByDir(resolvedOutdir)
357
+ const files = (
358
+ (oldJSON.files as undefined | string[]) ?? fs.readdirSync(resolveByDir('.'))
359
+ ).filter(file => file === 'node_modules' || resolveByDir(file) !== resolvedOutdirAbs)
360
+
361
+ for (const file of files) {
362
+ const path = resolveByDir(file)
181
363
  try {
182
- fs.renameSync(path.join(dir, 'package.json'), path.join(dir, 'package.json.bak'))
183
- fs.writeFileSync(path.join(dir, 'package.json'), newJSONString)
184
- !silent && console.log(newJSONString)
185
- if (preview) {
364
+ const stat = fs.statSync(path)
365
+ if (stat.isDirectory()) {
366
+ fs.cpSync(path, resolveByDir(resolvedOutdir, file), { recursive: true })
186
367
  continue
187
368
  }
188
- const args = ['pnpm', 'publish', '--access', 'public', '--no-git-checks', ...passArgs]
189
- if (bumper && TAGS.includes(bumper)) {
190
- args.push('--tag', bumper)
369
+ if (stat.isFile()) {
370
+ fs.cpSync(path, resolveByDir(resolvedOutdir, file))
371
+ continue
191
372
  }
192
- childProcess.execSync(args.join(' '), {
193
- cwd: dir,
194
- stdio: 'inherit'
195
- })
196
- const modifyVersionPackageJSON = applyEdits(oldJSONString, modify(oldJSONString, ['version'], newVersion, {}))
197
- fs.writeFileSync(path.join(dir, 'package.json.bak'), modifyVersionPackageJSON)
198
- } finally {
199
- fs.unlinkSync(path.join(dir, 'package.json'))
200
- fs.renameSync(path.join(dir, 'package.json.bak'), path.join(dir, 'package.json'))
373
+ } catch (e) {
374
+ console.warn(String(e))
375
+ continue
201
376
  }
377
+ throw new Error(`file type of ${path} is not supported`)
202
378
  }
203
- actionDone()
204
379
  })
380
+ }
381
+
382
+ async function postpublish() {
383
+ await forEachSelectedProjectsGraphEntries(dir => {
384
+ const jiekTempDir = path.resolve(dir, 'node_modules/.jiek/.tmp')
385
+ const packageJSON = path.resolve(dir, 'package.json')
386
+ const jiekTempPackageJSON = path.resolve(jiekTempDir, 'package.json')
387
+ if (fs.existsSync(jiekTempPackageJSON)) {
388
+ fs.copyFileSync(jiekTempPackageJSON, packageJSON)
389
+ fs.rmSync(jiekTempPackageJSON)
390
+ console.log(`${dir}/package.json has been restored`)
391
+ } else {
392
+ throw new Error(
393
+ `jiek temp \`${dir}/package.json\` not found, please confirm the jiek pre-publish command has been executed`
394
+ )
395
+ }
396
+ })
397
+ }
398
+
399
+ program
400
+ .action(async () => {
401
+ const {
402
+ npm_lifecycle_event: NPM_LIFECYCLE_EVENT
403
+ } = process.env
404
+ switch (NPM_LIFECYCLE_EVENT) {
405
+ case 'prepublish':
406
+ await prepublish()
407
+ break
408
+ case 'postpublish':
409
+ await postpublish()
410
+ break
411
+ default:
412
+ program.help()
413
+ }
414
+ })
415
+
416
+ program.command('prepublish').action(prepublish)
417
+ program.command('postpublish').action(postpublish)
@@ -39,7 +39,7 @@ const {
39
39
  JIEK_WITHOUT_DTS,
40
40
  JIEK_WITHOUT_MINIFY,
41
41
  JIEK_MINIFY_TYPE,
42
- JIEK_NO_CLEAN,
42
+ JIEK_CLEAN,
43
43
  JIEK_ONLY_MINIFY,
44
44
  JIEK_TSCONFIG,
45
45
  JIEK_DTSCONFIG
@@ -74,7 +74,7 @@ const WITHOUT_MINIFY = JIEK_WITHOUT_MINIFY === 'true'
74
74
 
75
75
  const ONLY_MINIFY = JIEK_ONLY_MINIFY === 'true'
76
76
 
77
- const CLEAN = JIEK_NO_CLEAN !== 'true'
77
+ const CLEAN = JIEK_CLEAN === 'true'
78
78
 
79
79
  const MINIFY_DEFAULT_VALUE = WITHOUT_MINIFY
80
80
  ? false