jiek 2.0.2 → 2.1.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.
@@ -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