@sanity/plugin-kit 1.1.0-ecosystem-preset.1 → 1.1.0-ecosystem-preset.4

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.
Files changed (80) hide show
  1. package/README.md +20 -1
  2. package/assets/{splat → inject}/LICENSE +0 -0
  3. package/assets/{splat → inject}/editorconfig +0 -0
  4. package/assets/{splat → inject}/gitignore +0 -0
  5. package/assets/{splat → inject}/npmignore +0 -0
  6. package/assets/{splat → inject}/prettierrc.js +0 -0
  7. package/assets/{splat/ecosystem → inject/renovatebot}/renovate.json +0 -0
  8. package/assets/{splat → inject}/sanity.json +0 -0
  9. package/assets/{splat/ecosystem → inject/semver-workflow}/.github/workflows/main.yml +2 -2
  10. package/assets/{splat/ecosystem → inject/semver-workflow}/.husky/commit-msg +0 -0
  11. package/assets/{splat/ecosystem → inject/semver-workflow}/.husky/pre-commit +0 -0
  12. package/assets/{splat/ecosystem → inject/semver-workflow}/.releaserc.json +1 -1
  13. package/assets/{splat/ecosystem/commitlint.config.js → inject/semver-workflow/commitlint.template.js} +0 -0
  14. package/assets/inject/semver-workflow/lint-staged.template.js +4 -0
  15. package/assets/inject/semver-workflow/renovate.json +7 -0
  16. package/assets/{splat → inject}/template-tsconfig.json +0 -0
  17. package/assets/{splat → inject}/v2-incompatible.js.template +0 -0
  18. package/lib/package.json +4 -2
  19. package/lib/src/actions/init.d.ts +8 -4
  20. package/lib/src/actions/init.js +9 -7
  21. package/lib/src/actions/init.js.map +1 -1
  22. package/lib/src/actions/{splat.d.ts → inject.d.ts} +3 -2
  23. package/lib/src/actions/{splat.js → inject.js} +60 -17
  24. package/lib/src/actions/inject.js.map +1 -0
  25. package/lib/src/cli.js +1 -0
  26. package/lib/src/cli.js.map +1 -1
  27. package/lib/src/cmds/index.d.ts +1 -0
  28. package/lib/src/cmds/index.js +1 -2
  29. package/lib/src/cmds/index.js.map +1 -1
  30. package/lib/src/cmds/init.js +5 -2
  31. package/lib/src/cmds/init.js.map +1 -1
  32. package/lib/src/cmds/{splat.d.ts → inject.d.ts} +0 -0
  33. package/lib/src/cmds/inject.js +76 -0
  34. package/lib/src/cmds/inject.js.map +1 -0
  35. package/lib/src/npm/package.d.ts +7 -4
  36. package/lib/src/npm/package.js +32 -30
  37. package/lib/src/npm/package.js.map +1 -1
  38. package/lib/src/presets/presets.d.ts +8 -0
  39. package/lib/src/presets/presets.js +52 -0
  40. package/lib/src/presets/presets.js.map +1 -0
  41. package/lib/src/presets/renovatebot.d.ts +2 -0
  42. package/lib/src/presets/renovatebot.js +24 -0
  43. package/lib/src/presets/renovatebot.js.map +1 -0
  44. package/lib/src/presets/semver-workflow.d.ts +2 -0
  45. package/lib/src/presets/semver-workflow.js +81 -0
  46. package/lib/src/presets/semver-workflow.js.map +1 -0
  47. package/lib/src/util/prompt.d.ts +3 -3
  48. package/lib/src/util/prompt.js.map +1 -1
  49. package/lib/src/util/readme.d.ts +1 -1
  50. package/lib/src/util/user.d.ts +6 -3
  51. package/lib/src/util/user.js +8 -4
  52. package/lib/src/util/user.js.map +1 -1
  53. package/lib/test/init.test.js +6 -5
  54. package/lib/test/init.test.js.map +1 -1
  55. package/lib/test/inject.test.d.ts +1 -0
  56. package/lib/test/inject.test.js +75 -0
  57. package/lib/test/inject.test.js.map +1 -0
  58. package/package.json +4 -2
  59. package/src/actions/init.ts +10 -8
  60. package/src/actions/{splat.ts → inject.ts} +62 -21
  61. package/src/cli.ts +1 -0
  62. package/src/cmds/index.ts +1 -2
  63. package/src/cmds/init.ts +5 -2
  64. package/src/cmds/inject.ts +67 -0
  65. package/src/npm/package.ts +33 -39
  66. package/src/presets/presets.ts +52 -0
  67. package/src/presets/renovatebot.ts +12 -0
  68. package/src/presets/semver-workflow.ts +84 -0
  69. package/src/util/prompt.ts +3 -3
  70. package/src/util/readme.ts +1 -1
  71. package/src/util/user.ts +17 -7
  72. package/assets/splat/eslint.config.js +0 -5
  73. package/lib/src/actions/splat.js.map +0 -1
  74. package/lib/src/cmds/splat.js +0 -63
  75. package/lib/src/cmds/splat.js.map +0 -1
  76. package/lib/src/ecosystem/ecosystem-preset.d.ts +0 -3
  77. package/lib/src/ecosystem/ecosystem-preset.js +0 -46
  78. package/lib/src/ecosystem/ecosystem-preset.js.map +0 -1
  79. package/src/cmds/splat.ts +0 -59
  80. package/src/ecosystem/ecosystem-preset.ts +0 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/plugin-kit",
3
- "version": "1.1.0-ecosystem-preset.1",
3
+ "version": "1.1.0-ecosystem-preset.4",
4
4
  "description": "Enhanced Sanity.io plugin development experience",
5
5
  "scripts": {
6
6
  "clean": "rimraf lib",
@@ -14,7 +14,7 @@
14
14
  "lint": "eslint .",
15
15
  "compile": "tsc --noEmit",
16
16
  "format": "prettier src -w",
17
- "prepublishOnly": "npm run test && npm run build"
17
+ "prepublishOnly": "npm run build"
18
18
  },
19
19
  "binname": "sanity-plugin",
20
20
  "source": "./src/index.ts",
@@ -88,6 +88,7 @@
88
88
  "@parcel/transformer-typescript-types": "^2.7.0",
89
89
  "@sanity/semantic-release-preset": "^2.0.0",
90
90
  "@types/eslint": "^8.4.6",
91
+ "@types/fs-extra": "^9.0.13",
91
92
  "@types/inquirer": "^8.2.3",
92
93
  "@types/node": "^17.0.40",
93
94
  "@types/nodemon": "^1.19.2",
@@ -98,6 +99,7 @@
98
99
  "eslint-config-prettier": "^8.5.0",
99
100
  "eslint-config-sanity": "^5.1.0",
100
101
  "eslint-plugin-prettier": "^4.2.1",
102
+ "fs-extra": "^10.1.0",
101
103
  "husky": "^7.0.4",
102
104
  "json5": "^2.2.1",
103
105
  "lint-staged": "^12.5.0",
@@ -1,5 +1,5 @@
1
1
  import path from 'path'
2
- import {splat} from './splat'
2
+ import {inject} from './inject'
3
3
  import {ensureDir, writeFile} from '../util/files'
4
4
  import {resolveLatestVersions} from '../npm/resolveLatestVersions'
5
5
  import sharedFlags from '../sharedFlags'
@@ -7,7 +7,6 @@ import {TypedFlags} from 'meow'
7
7
  import {getPackage} from '../npm/package'
8
8
  import {defaultSourceJs, defaultSourceTs} from '../configs/default-source'
9
9
  import {incompatiblePluginPackage} from '../constants'
10
- import {ecosystemDevDependencies} from '../ecosystem/ecosystem-preset'
11
10
 
12
11
  export const initFlags = {
13
12
  ...sharedFlags,
@@ -34,10 +33,6 @@ export const initFlags = {
34
33
  type: 'boolean',
35
34
  default: true,
36
35
  },
37
- ecosystemPreset: {
38
- type: 'boolean',
39
- default: false,
40
- },
41
36
  gitignore: {
42
37
  type: 'boolean',
43
38
  default: true,
@@ -59,6 +54,14 @@ export const initFlags = {
59
54
  repo: {
60
55
  type: 'string',
61
56
  },
57
+ presetOnly: {
58
+ type: 'boolean',
59
+ default: false,
60
+ },
61
+ preset: {
62
+ type: 'string',
63
+ isMultiple: true,
64
+ },
62
65
  } as const
63
66
 
64
67
  export type InitFlags = TypedFlags<typeof initFlags>
@@ -82,7 +85,6 @@ export async function init(options: InitOptions) {
82
85
  devDependencies = {
83
86
  ...devDependencies,
84
87
  ...defaultDevDependencies,
85
- ...(options.flags.ecosystemPreset ? await ecosystemDevDependencies() : []),
86
88
  ...(await resolveLatestVersions(['rimraf'])),
87
89
  }
88
90
  peerDependencies = {
@@ -90,7 +92,7 @@ export async function init(options: InitOptions) {
90
92
  ...defaultPeerDependencies,
91
93
  }
92
94
 
93
- await splat({
95
+ await inject({
94
96
  ...options,
95
97
  requireUserConfirmation: !options.flags.force,
96
98
  dependencies,
@@ -18,7 +18,8 @@ import {
18
18
  } from '../util/files'
19
19
  import {InitFlags} from './init'
20
20
  import {PackageJson} from './verify/types'
21
- import {ecosystemPresetFiles} from '../ecosystem/ecosystem-preset'
21
+ import outdent from 'outdent'
22
+ import {injectPresets} from '../presets/presets'
22
23
 
23
24
  const bannedFields = ['login', 'description', 'projecturl', 'email']
24
25
  const preferredLicenses = ['MIT', 'ISC', 'BSD-3-Clause']
@@ -32,7 +33,7 @@ const otherLicenses = Object.keys(licenses.list).filter((id) => {
32
33
 
33
34
  export type FromTo = {from: string | string[]; to: string | string[]}
34
35
 
35
- export interface SplatOptions {
36
+ export interface InjectOptions {
36
37
  basePath: string
37
38
  requireUserConfirmation?: boolean
38
39
  flags: InitFlags
@@ -51,7 +52,16 @@ export interface PackageData {
51
52
  gitOrigin?: string
52
53
  }
53
54
 
54
- export async function splat(options: SplatOptions) {
55
+ export async function inject(options: InjectOptions) {
56
+ if (options.flags.presetOnly) {
57
+ log.info('Only apply presets, skipping default inject.')
58
+ } else {
59
+ await injectBase(options)
60
+ }
61
+ await injectPresets(options)
62
+ }
63
+
64
+ async function injectBase(options: InjectOptions) {
55
65
  const {basePath, flags, requireUserConfirmation} = options
56
66
  const info = (write: boolean, msg: string, ...args: string[]) => write && log.info(msg, ...args)
57
67
  // Gather data
@@ -103,6 +113,8 @@ export async function splat(options: SplatOptions) {
103
113
 
104
114
  didWrite = await writeEslintrc(options)
105
115
  info(didWrite, 'Wrote .eslintrc.js')
116
+ didWrite = await writeEslintIgnore(options)
117
+ info(didWrite, 'Wrote .eslintignore')
106
118
 
107
119
  didWrite = await addBuildScripts(newPkg, options)
108
120
  info(didWrite, 'Added build scripts to package.json')
@@ -111,7 +123,7 @@ export async function splat(options: SplatOptions) {
111
123
  info(didWrite, 'Added compilation output directory to .gitignore')
112
124
  }
113
125
 
114
- async function writeReadme(data: PackageData, options: SplatOptions) {
126
+ async function writeReadme(data: PackageData, options: InjectOptions) {
115
127
  const {basePath} = options
116
128
 
117
129
  const readmePath = path.join(basePath, 'README.md')
@@ -128,7 +140,7 @@ async function writeReadme(data: PackageData, options: SplatOptions) {
128
140
  return true
129
141
  }
130
142
 
131
- async function writeEslintrc(options: SplatOptions) {
143
+ async function writeEslintrc(options: InjectOptions) {
132
144
  if (!options.flags.eslint) {
133
145
  return false
134
146
  }
@@ -159,9 +171,32 @@ async function writeEslintrc(options: SplatOptions) {
159
171
  return true
160
172
  }
161
173
 
174
+ async function writeEslintIgnore(options: InjectOptions) {
175
+ if (!options.flags.eslint) {
176
+ return false
177
+ }
178
+ const {basePath} = options
179
+
180
+ const eslintignore = path.join(basePath, '.eslintignore')
181
+
182
+ const content = outdent`
183
+ .eslintrc.js
184
+ commitlint.config.js
185
+ lib
186
+ lint-staged.config.js
187
+ ${options.flags.typescript ? '*.js' : ''}
188
+ `.trim()
189
+
190
+ await writeFileWithOverwritePrompt(eslintignore, content, {
191
+ encoding: 'utf8',
192
+ force: options.flags.force,
193
+ })
194
+ return true
195
+ }
196
+
162
197
  async function writeLicense(
163
198
  {license}: PackageData,
164
- options: SplatOptions,
199
+ options: InjectOptions,
165
200
  licenseChanged: boolean
166
201
  ) {
167
202
  const {basePath, flags} = options
@@ -287,24 +322,12 @@ async function resolveProjectDescription(basePath: string, pkg: PackageJson | un
287
322
  }
288
323
  }
289
324
 
290
- async function writeStaticAssets({basePath, flags}: SplatOptions) {
325
+ export async function writeAssets(files: FromTo[], {basePath, flags}: InjectOptions) {
291
326
  const assetsDir = await findAssetsDir()
292
327
 
293
- const from = (...segments: string[]) => path.join(assetsDir, 'splat', ...segments)
328
+ const from = (...segments: string[]) => path.join(assetsDir, 'inject', ...segments)
294
329
  const to = (...segments: string[]) => path.join(basePath, ...segments)
295
330
 
296
- const files: FromTo[] = [
297
- {from: 'editorconfig', to: '.editorconfig'},
298
- {from: 'npmignore', to: '.npmignore'},
299
- {from: 'sanity.json', to: 'sanity.json'},
300
- {from: 'v2-incompatible.js.template', to: 'v2-incompatible.js'},
301
- flags.gitignore && {from: 'gitignore', to: '.gitignore'},
302
- flags.typescript && {from: 'template-tsconfig.json', to: 'tsconfig.json'},
303
- flags.prettier && {from: 'prettierrc.js', to: '.prettierrc.js'},
304
-
305
- ...(flags.ecosystemPreset ? ecosystemPresetFiles() : []),
306
- ].filter((f: false | FromTo): f is FromTo => !!f)
307
-
308
331
  const writes: string[] = []
309
332
  for (const file of files) {
310
333
  const fromPath = asArray(file.from)
@@ -317,6 +340,24 @@ async function writeStaticAssets({basePath, flags}: SplatOptions) {
317
340
  return writes
318
341
  }
319
342
 
343
+ async function writeStaticAssets(options: InjectOptions) {
344
+ const {flags} = options
345
+
346
+ const files: FromTo[] = [
347
+ {from: 'editorconfig', to: '.editorconfig'},
348
+ {from: 'npmignore', to: '.npmignore'},
349
+ {from: 'sanity.json', to: 'sanity.json'},
350
+ {from: 'v2-incompatible.js.template', to: 'v2-incompatible.js'},
351
+ flags.gitignore && {from: 'gitignore', to: '.gitignore'},
352
+ flags.typescript && {from: 'template-tsconfig.json', to: 'tsconfig.json'},
353
+ flags.prettier && {from: 'prettierrc.js', to: '.prettierrc.js'},
354
+ ]
355
+ .map((f) => (f ? (f as FromTo) : undefined))
356
+ .filter((f): f is FromTo => !!f)
357
+
358
+ return writeAssets(files, options)
359
+ }
360
+
320
361
  function asArray(input: string | string[]): string[] {
321
362
  return typeof input === 'string' ? [input] : input
322
363
  }
@@ -345,7 +386,7 @@ async function findAssetsDir(): Promise<string> {
345
386
  return assetsDir
346
387
  }
347
388
 
348
- async function addCompileDirToGitIgnore(data: PackageData, options: SplatOptions) {
389
+ async function addCompileDirToGitIgnore(data: PackageData, options: InjectOptions) {
349
390
  const gitIgnorePath = path.join(options.basePath, '.gitignore')
350
391
  const gitignore = await readFile(gitIgnorePath, 'utf8').catch(errorToUndefined)
351
392
  if (!gitignore) {
package/src/cli.ts CHANGED
@@ -15,6 +15,7 @@ export async function cliEntry(argv = process.argv, autoExit = true) {
15
15
  These are common commands used in various situations:
16
16
 
17
17
  init Create a new Sanity plugin
18
+ inject Inject config into an existing Sanity v3 plugin
18
19
  verify-package Check that a Sanity plugin package follows V3 conventions. Prints upgrade steps.
19
20
  verify-studio Check that a Sanity Studio follows V3 conventions. Prints upgrade steps.
20
21
  link-watch Recompiles plugin automatically on changes and runs yalc push --publish
package/src/cmds/index.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  export default {
2
2
  init: require.resolve('./init'),
3
+ inject: require.resolve('./inject'),
3
4
  'link-watch': require.resolve('./link-watch'),
4
5
  'verify-package': require.resolve('./verify-package'),
5
6
  'verify-studio': require.resolve('./verify-studio'),
6
7
  version: require.resolve('./version'),
7
- // wont make it for initial release
8
- //splat: require.resolve('./splat'),
9
8
  }
package/src/cmds/init.ts CHANGED
@@ -7,6 +7,7 @@ import {installDependencies, promptForPackageManager} from '../npm/manager'
7
7
  import {findStudioV3Config, hasSanityJson} from '../sanity/manifest'
8
8
  import {prompt} from '../util/prompt'
9
9
  import {cliName} from '../constants'
10
+ import {presetHelpList} from '../presets/presets'
10
11
 
11
12
  const description = `Initialize a new Sanity plugin`
12
13
 
@@ -24,14 +25,16 @@ Options
24
25
  --no-scripts Disables scripts from being added to package.json
25
26
  --no-install Disables automatically running package manager install
26
27
 
27
- --ecosystem-preset [beta]: Adds opinionated files and dependencies for conventional-commits, githubworkflow, reonvatebot and semantic-release
28
-
29
28
  --name [package-name] Use the provided package-name
30
29
  --author [name] Use the provided author
31
30
  --repo [url] Use the provided repo url
32
31
  --license [spdx] Use the license with the given SPDX identifier
33
32
  --force No promt when overwriting files
34
33
 
34
+ --preset [preset-name] [beta] - Adds config and files from a named preset. --preset can be supplied multiple times.
35
+ The following presets are available:
36
+ ${presetHelpList(30)}
37
+
35
38
  Examples
36
39
  # Initialize a new plugin in the current directory
37
40
  $ ${cliName} init
@@ -0,0 +1,67 @@
1
+ import path from 'path'
2
+ import meow from 'meow'
3
+ import log from '../util/log'
4
+ import {inject} from '../actions/inject'
5
+ import {findStudioV3Config} from '../sanity/manifest'
6
+ import {initFlags} from '../actions/init'
7
+ import {cliName} from '../constants'
8
+ import {presetHelpList} from '../presets/presets'
9
+
10
+ const description = `Inject configuration into a Sanity plugin`
11
+
12
+ const help = `
13
+ Usage
14
+ $ ${cliName} inject [dir] [<args>]
15
+
16
+ Options
17
+ --no-eslint Disables ESLint config and dependencies from being added
18
+ --no-prettier Disables prettier config and dependencies from being added
19
+ --no-typescript Disables typescript config and dependencies from being added
20
+ --no-license Disables LICENSE + package.json license field from being added
21
+ --no-editorconfig Disables .editorconfig from being added
22
+ --no-gitignore Disables .gitignore from being added
23
+ --no-scripts Disables scripts from being added to package.json
24
+
25
+ --license [spdx] Use the license with the given SPDX identifier
26
+ --force No promt when overwriting files
27
+
28
+ --preset [preset-name] [beta] - Adds config and files from a named preset. --preset can be supplied multiple times.
29
+ The following presets are available:
30
+ ${presetHelpList(30)}
31
+ --preset-only Skips the default inject steps. Use this to apply a preset to an otherwise complete plugin.
32
+
33
+ Examples
34
+ # Inject configuration into the plugin in the current directory
35
+ $ ${cliName} inject
36
+
37
+ # Inject configuration into the plugin in ~/my-plugin
38
+ $ ${cliName} inject ~/my-plugin
39
+
40
+ # Don't inject eslint or prettier
41
+ $ ${cliName} inject --no-eslint --no-prettier
42
+
43
+ # Inject plugin configuration and semver-workflow into the plugin in the current directory
44
+ $ @sanity/plugin-kit inject --preset semver-workflow
45
+
46
+ # Only inject semver-workflow and renovatebot config from presets
47
+ $ ${cliName} inject --preset-only --preset semver-workflow --preset renovatebot
48
+
49
+ `
50
+
51
+ async function run({argv}: {argv: string[]}) {
52
+ const cli = meow(help, {flags: initFlags, argv, description})
53
+ const basePath = path.resolve(cli.input[0] || process.cwd())
54
+
55
+ const {v3ConfigFile} = await findStudioV3Config(basePath)
56
+ if (v3ConfigFile) {
57
+ throw new Error(
58
+ `${v3ConfigFile} exists - are you trying to INJECT into a studio instead of a plugin?`
59
+ )
60
+ }
61
+ log.info('Inject config into plugin in "%s"', basePath)
62
+
63
+ await inject({basePath, flags: cli.flags, validate: false})
64
+ log.info('Done!')
65
+ }
66
+
67
+ export default run
@@ -10,7 +10,7 @@ import {resolveLatestVersions} from './resolveLatestVersions'
10
10
  import {hasSourceEquivalent, writeJsonFile} from '../util/files'
11
11
  import log from '../util/log'
12
12
  import {cliName} from '../constants'
13
- import {PackageData, SplatOptions} from '../actions/splat'
13
+ import {InjectOptions, PackageData} from '../actions/inject'
14
14
  import {expectedScripts} from '../actions/verify/validations'
15
15
  import {PackageJson} from '../actions/verify/types'
16
16
 
@@ -181,23 +181,10 @@ function validateLockFiles(options: {basePath: string}) {
181
181
  }
182
182
  }
183
183
 
184
- export function getReferencedPaths(
185
- packageJson: Record<string, string>,
186
- basePath: string
187
- ): string[] {
188
- return pathKeys
189
- .filter((key) => key in packageJson)
190
- .map((key) =>
191
- path.isAbsolute(packageJson[key])
192
- ? packageJson[key]
193
- : path.resolve(basePath, packageJson[key])
194
- )
195
- }
196
-
197
- export async function writePackageJson(data: PackageData, options: SplatOptions) {
184
+ export async function writePackageJson(data: PackageData, options: InjectOptions) {
198
185
  const {user, pluginName, license, description, pkg: prevPkg, gitOrigin} = data
199
186
  const {peerDependencies: addPeers, dependencies: addDeps, devDependencies: addDevDeps} = options
200
- const {basePath, flags} = options
187
+ const {flags} = options
201
188
  const prev = prevPkg || {}
202
189
 
203
190
  const usePrettier = flags.prettier !== false
@@ -288,7 +275,7 @@ export async function writePackageJson(data: PackageData, options: SplatOptions)
288
275
  const differs = JSON.stringify(prev) !== JSON.stringify(manifest)
289
276
  log.debug('Does manifest differ? %s', differs ? 'yes' : 'no')
290
277
  if (differs) {
291
- await writeJsonFile(path.join(basePath, 'package.json'), manifest)
278
+ await writePackageJsonDirect(manifest, options)
292
279
  }
293
280
 
294
281
  return differs ? manifest : prev
@@ -321,14 +308,7 @@ function repoFromOrigin(gitOrigin?: string) {
321
308
  }
322
309
  }
323
310
 
324
- function withSanityKeywords(keywords: string[] = []) {
325
- const newKeywords = new Set(keywords)
326
- newKeywords.add('sanity')
327
- newKeywords.add('sanity-plugin')
328
- return Array.from(newKeywords)
329
- }
330
-
331
- function addScript(cmd: string, existing: string) {
311
+ export function addScript(cmd: string, existing: string) {
332
312
  if (existing && existing.includes(cmd)) {
333
313
  return existing
334
314
  }
@@ -336,30 +316,44 @@ function addScript(cmd: string, existing: string) {
336
316
  return existing ? `${existing} && ${cmd}` : cmd
337
317
  }
338
318
 
339
- export async function addBuildScripts(manifest: PackageJson, {basePath, flags}: SplatOptions) {
340
- if (!flags.scripts) {
341
- return false
342
- }
319
+ export async function addPackageJsonScripts(
320
+ manifest: PackageJson,
321
+ options: InjectOptions,
322
+ updateScripts: (currentScripts: Record<string, string>) => Record<string, string>
323
+ ) {
343
324
  const originalScripts = manifest.scripts || {}
344
- const scripts = {...originalScripts}
345
- scripts.clean = addScript(`rimraf lib`, scripts.clean)
346
- scripts.lint = addScript(`eslint .`, scripts.lint)
347
- scripts.prebuild = addScript('npm run clean && ' + expectedScripts.prebuild, scripts.prebuild)
348
- scripts.build = addScript(expectedScripts.build, scripts.build)
349
- scripts.watch = addScript(expectedScripts.watch, scripts.watch)
350
- scripts['link-watch'] = addScript(expectedScripts['link-watch'], scripts['link-watch'])
351
- scripts.prepublishOnly = addScript(expectedScripts.prepublishOnly, scripts.prepublishOnly)
325
+ const scripts = updateScripts({...originalScripts})
352
326
 
353
327
  const differs = Object.keys(scripts).some((key) => scripts[key] !== originalScripts[key])
354
328
 
355
329
  if (differs) {
356
- await writeJsonFile(path.join(basePath, 'package.json'), {...manifest, scripts})
330
+ await writePackageJsonDirect({...manifest, scripts}, options)
357
331
  }
358
332
 
359
333
  return differs
360
334
  }
361
335
 
362
- function sortKeys<T extends Record<string, unknown>>(unordered: T): T {
336
+ export async function writePackageJsonDirect(manifest: PackageJson, {basePath}: InjectOptions) {
337
+ await writeJsonFile(path.join(basePath, 'package.json'), manifest)
338
+ }
339
+
340
+ export async function addBuildScripts(manifest: PackageJson, options: InjectOptions) {
341
+ if (!options.flags.scripts) {
342
+ return false
343
+ }
344
+ return addPackageJsonScripts(manifest, options, (scripts) => {
345
+ scripts.clean = addScript(`rimraf lib`, scripts.clean)
346
+ scripts.lint = addScript(`eslint .`, scripts.lint)
347
+ scripts.prebuild = addScript('npm run clean && ' + expectedScripts.prebuild, scripts.prebuild)
348
+ scripts.build = addScript(expectedScripts.build, scripts.build)
349
+ scripts.watch = addScript(expectedScripts.watch, scripts.watch)
350
+ scripts['link-watch'] = addScript(expectedScripts['link-watch'], scripts['link-watch'])
351
+ scripts.prepublishOnly = addScript(expectedScripts.prepublishOnly, scripts.prepublishOnly)
352
+ return scripts
353
+ })
354
+ }
355
+
356
+ export function sortKeys<T extends Record<string, unknown>>(unordered: T): T {
363
357
  return Object.keys(unordered)
364
358
  .sort()
365
359
  .reduce((obj, key) => {
@@ -0,0 +1,52 @@
1
+ import {InjectOptions} from '../actions/inject'
2
+ import {semverWorkflowPreset} from './semver-workflow'
3
+ import {renovatePreset} from './renovatebot'
4
+
5
+ export interface Preset {
6
+ name: string
7
+ description: string
8
+ apply: (options: InjectOptions) => Promise<void>
9
+ }
10
+
11
+ const presets: Preset[] = [semverWorkflowPreset, renovatePreset]
12
+ const presetNames = presets.map((p) => p.name)
13
+
14
+ export function presetHelpList(padStart: number) {
15
+ return presets
16
+ .map((p) => `${''.padStart(padStart)}${p.name.padEnd(20)}${p.description}`)
17
+ .join('\n')
18
+ }
19
+
20
+ export async function injectPresets(options: InjectOptions) {
21
+ if (options.flags.presetOnly && !options.flags.preset?.length) {
22
+ throw new Error('--preset-only, but no --preset [preset-name] was provided.')
23
+ }
24
+
25
+ const applyPresets = presetsFromInput(options.flags.preset)
26
+ for (const preset of applyPresets) {
27
+ await preset.apply(options)
28
+ }
29
+ }
30
+
31
+ function presetsFromInput(inputPresets: string[] | undefined): Preset[] {
32
+ if (!inputPresets) {
33
+ return []
34
+ }
35
+ const unknownPresets = inputPresets.filter((p) => !presetNames.includes(p))
36
+ if (unknownPresets.length) {
37
+ throw new Error(
38
+ `Unknown --preset(s): [${unknownPresets.join(', ')}]. Must be one of: [${presetNames.join(
39
+ ', '
40
+ )}]`
41
+ )
42
+ }
43
+
44
+ return inputPresets
45
+ .filter(onlyUnique)
46
+ .map((presetName) => presets.find((p) => p.name === presetName))
47
+ .filter((p): p is Preset => !!p)
48
+ }
49
+
50
+ function onlyUnique(value: string, index: number, arr: string[]) {
51
+ return arr.indexOf(value) === index
52
+ }
@@ -0,0 +1,12 @@
1
+ import {Preset} from './presets'
2
+ import {InjectOptions, writeAssets} from '../actions/inject'
3
+
4
+ export const renovatePreset: Preset = {
5
+ name: 'renovatebot',
6
+ description: 'Files to enable renovatebot.',
7
+ apply: applyPreset,
8
+ }
9
+
10
+ async function applyPreset(options: InjectOptions) {
11
+ await writeAssets([{from: ['renovatebot', 'renovate.json'], to: 'renovate.json'}], options)
12
+ }
@@ -0,0 +1,84 @@
1
+ import {FromTo, InjectOptions, writeAssets} from '../actions/inject'
2
+ import {resolveLatestVersions} from '../npm/resolveLatestVersions'
3
+ import {Preset} from './presets'
4
+ import {
5
+ addBuildScripts,
6
+ addPackageJsonScripts,
7
+ addScript,
8
+ getPackage,
9
+ sortKeys,
10
+ writePackageJsonDirect,
11
+ } from '../npm/package'
12
+ import log from '../util/log'
13
+ import outdent from 'outdent'
14
+ import chalk from 'chalk'
15
+
16
+ export const semverWorkflowPreset: Preset = {
17
+ name: 'semver-workflow',
18
+ description:
19
+ 'Files and dependencies for conventional-commits, github workflow and semantic-release.',
20
+ apply: applyPreset,
21
+ }
22
+
23
+ const info = (write: boolean, msg: string, ...args: string[]) => write && log.info(msg, ...args)
24
+
25
+ async function applyPreset(options: InjectOptions) {
26
+ await writeAssets(semverWorkflowFiles(), options)
27
+ await addPrepareScript(options)
28
+ await addDevDependencies(options)
29
+ }
30
+
31
+ async function addPrepareScript(options: InjectOptions) {
32
+ const pkg = await getPackage(options)
33
+ const didWrite = await addPackageJsonScripts(pkg, options, (scripts) => {
34
+ scripts.prepare = addScript(`husky install`, scripts.prepare)
35
+ return scripts
36
+ })
37
+ info(didWrite, 'Added prepare script to package.json')
38
+ }
39
+
40
+ async function addDevDependencies(options: InjectOptions) {
41
+ const pkg = await getPackage(options)
42
+ const devDeps = sortKeys({
43
+ ...pkg.devDependencies,
44
+ ...(await semverWorkflowDependencies()),
45
+ })
46
+ const newPkg = {...pkg}
47
+ newPkg.devDependencies = devDeps
48
+ await writePackageJsonDirect(newPkg, options)
49
+ log.info('Updated devDependencies.')
50
+
51
+ log.info(
52
+ chalk.green(
53
+ outdent`
54
+ You must configure branch-config in the following files manually:
55
+ - .release.json
56
+ - .github/workflows/main.yml
57
+ `.trim()
58
+ )
59
+ )
60
+ }
61
+
62
+ function semverWorkflowFiles(): FromTo[] {
63
+ return [
64
+ {from: ['.github', 'workflows', 'main.yml'], to: ['.github', 'workflows', 'main.yml']},
65
+ {from: ['.husky', 'commit-msg'], to: ['.husky', 'commit-msg']},
66
+ {from: ['.husky', 'pre-commit'], to: ['.husky', 'pre-commit']},
67
+ {from: ['.releaserc.json'], to: '.releaserc.json'},
68
+ {from: ['commitlint.template.js'], to: 'commitlint.config.js'},
69
+ {from: ['lint-staged.template.js'], to: 'lint-staged.config.js'},
70
+ ].map((fromTo) => ({
71
+ ...fromTo,
72
+ from: ['semver-workflow', ...fromTo.from],
73
+ }))
74
+ }
75
+
76
+ async function semverWorkflowDependencies(): Promise<Record<string, string>> {
77
+ return resolveLatestVersions([
78
+ '@commitlint/cli',
79
+ '@commitlint/config-conventional',
80
+ '@sanity/semantic-release-preset',
81
+ 'husky',
82
+ 'lint-staged',
83
+ ])
84
+ }
@@ -5,7 +5,7 @@ import inquirer from 'inquirer'
5
5
  import validNpmName from 'validate-npm-package-name'
6
6
  // @ts-expect-error missing types
7
7
  import githubUrlToObject from 'github-url-to-object'
8
- import {SplatOptions} from '../actions/splat'
8
+ import {InjectOptions} from '../actions/inject'
9
9
 
10
10
  export async function prompt(
11
11
  message: string,
@@ -24,7 +24,7 @@ export async function prompt(
24
24
 
25
25
  prompt.separator = () => new inquirer.Separator()
26
26
 
27
- export function promptForPackageName({basePath}: SplatOptions, defaultVal?: string) {
27
+ export function promptForPackageName({basePath}: InjectOptions, defaultVal?: string) {
28
28
  return prompt('Plugin name (sanity-plugin-...)', {
29
29
  default: defaultVal || path.basename(basePath),
30
30
  filter: (name) => {
@@ -46,7 +46,7 @@ export function promptForPackageName({basePath}: SplatOptions, defaultVal?: stri
46
46
  })
47
47
  }
48
48
 
49
- export function promptForRepoOrigin(options: SplatOptions, defaultVal?: string) {
49
+ export function promptForRepoOrigin(options: InjectOptions, defaultVal?: string) {
50
50
  return prompt('Git repository URL', {
51
51
  default: defaultVal,
52
52
  filter: (raw) => {
@@ -1,7 +1,7 @@
1
1
  import outdent from 'outdent'
2
2
  // @ts-expect-error missing types
3
3
  import licenses from '@rexxars/choosealicense-list'
4
- import {PackageData} from '../actions/splat'
4
+ import {PackageData} from '../actions/inject'
5
5
  import {User} from './user'
6
6
 
7
7
  export function generateReadme(data: PackageData) {