From b5f53777f16591f083956ac272c33d619ad1eb65 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Tue, 30 Aug 2022 00:02:36 +0200 Subject: [PATCH] feat: added inject command and support for --preset --- README.md | 21 +- assets/{splat => inject}/LICENSE | 0 assets/{splat => inject}/editorconfig | 0 assets/{splat => inject}/gitignore | 0 assets/{splat => inject}/npmignore | 0 assets/{splat => inject}/prettierrc.js | 0 .../renovatebot}/renovate.json | 0 assets/{splat => inject}/sanity.json | 0 .../.github/workflows/main.yml | 0 .../semver-workflow}/.husky/commit-msg | 0 .../semver-workflow}/.husky/pre-commit | 0 .../semver-workflow}/.releaserc.json | 0 .../semver-workflow}/commitlint.config.js | 0 assets/inject/semver-workflow/renovate.json | 7 + .../{splat => inject}/template-tsconfig.json | 0 .../v2-incompatible.js.template | 0 assets/splat/eslint.config.js | 5 - package-lock.json | 349 ++++++------------ package.json | 2 + src/actions/init.ts | 18 +- src/actions/{splat.ts => inject.ts} | 83 +++-- src/cli.ts | 1 + src/cmds/index.ts | 3 +- src/cmds/init.ts | 7 +- src/cmds/inject.ts | 67 ++++ src/cmds/splat.ts | 59 --- src/ecosystem/ecosystem-preset.ts | 32 -- src/npm/package.ts | 72 ++-- src/presets/presets.ts | 52 +++ src/presets/renovatebot.ts | 12 + src/presets/semver-workflow.ts | 71 ++++ src/util/prompt.ts | 6 +- src/util/readme.ts | 2 +- src/util/user.ts | 4 +- test/fixtures/inject/valid/.editorconfig | 13 + test/fixtures/inject/valid/.eslintrc | 12 + test/fixtures/inject/valid/.gitignore | 59 +++ test/fixtures/inject/valid/.npmignore | 9 + test/fixtures/inject/valid/.prettierrc.js | 6 + test/fixtures/inject/valid/LICENSE | 21 ++ test/fixtures/inject/valid/README.md | 32 ++ test/fixtures/inject/valid/package.json | 72 ++++ test/fixtures/inject/valid/sanity.json | 8 + test/fixtures/inject/valid/src/index.ts | 28 ++ test/fixtures/inject/valid/tsconfig.json | 23 ++ test/fixtures/inject/valid/v2-incompatible.js | 11 + test/init.test.ts | 20 +- test/inject.test.ts | 89 +++++ 48 files changed, 864 insertions(+), 412 deletions(-) rename assets/{splat => inject}/LICENSE (100%) rename assets/{splat => inject}/editorconfig (100%) rename assets/{splat => inject}/gitignore (100%) rename assets/{splat => inject}/npmignore (100%) rename assets/{splat => inject}/prettierrc.js (100%) rename assets/{splat/ecosystem => inject/renovatebot}/renovate.json (100%) rename assets/{splat => inject}/sanity.json (100%) rename assets/{splat/ecosystem => inject/semver-workflow}/.github/workflows/main.yml (100%) rename assets/{splat/ecosystem => inject/semver-workflow}/.husky/commit-msg (100%) rename assets/{splat/ecosystem => inject/semver-workflow}/.husky/pre-commit (100%) rename assets/{splat/ecosystem => inject/semver-workflow}/.releaserc.json (100%) rename assets/{splat/ecosystem => inject/semver-workflow}/commitlint.config.js (100%) create mode 100644 assets/inject/semver-workflow/renovate.json rename assets/{splat => inject}/template-tsconfig.json (100%) rename assets/{splat => inject}/v2-incompatible.js.template (100%) delete mode 100644 assets/splat/eslint.config.js rename src/actions/{splat.ts => inject.ts} (88%) create mode 100644 src/cmds/inject.ts delete mode 100644 src/cmds/splat.ts delete mode 100644 src/ecosystem/ecosystem-preset.ts create mode 100644 src/presets/presets.ts create mode 100644 src/presets/renovatebot.ts create mode 100644 src/presets/semver-workflow.ts create mode 100644 test/fixtures/inject/valid/.editorconfig create mode 100644 test/fixtures/inject/valid/.eslintrc create mode 100644 test/fixtures/inject/valid/.gitignore create mode 100644 test/fixtures/inject/valid/.npmignore create mode 100644 test/fixtures/inject/valid/.prettierrc.js create mode 100644 test/fixtures/inject/valid/LICENSE create mode 100644 test/fixtures/inject/valid/README.md create mode 100644 test/fixtures/inject/valid/package.json create mode 100644 test/fixtures/inject/valid/sanity.json create mode 100644 test/fixtures/inject/valid/src/index.ts create mode 100644 test/fixtures/inject/valid/tsconfig.json create mode 100644 test/fixtures/inject/valid/v2-incompatible.js create mode 100644 test/inject.test.ts diff --git a/README.md b/README.md index 4771a930..d3ca1255 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Check the [FAQ](#faq) fro more on these. * [Verify plugin package](#verify-plugin-package) * [Upgrading a v2 plugin](#upgrading-a-v2-plugin) * [Upgrade help in v2 Studio](#upgrade-help-in-v2-studio) +* [Inject config into existing v3 plugin](#inject-config-into-existing-v3) * [Testing a plugin in Sanity Studio](#testing-a-plugin-in-sanity-studio) * [FAQ](#faq) aka "Do I _have_ to use this plugin-kit?" aka No * [Configuration reference](#configuration-reference) @@ -83,6 +84,15 @@ sanity start Check browser console: the plugin should have logged `"hello from my-sanity-plugin"`. Since the plugin is running in watch mode, any changes you make to the plugin code will be reloaded in the studio. +### Init options + +The init commands has several config flags, run +``` +npx @sanity/plugin-kit init --help +``` + +for up-to-date specifics. + ## Verify plugin package Verify that the plugin package is configured correctly by running: @@ -147,6 +157,15 @@ npx @sanity/plugin-kit verify-package --studio --single This will only output the first validation that fails. Useful when working through the list of issues by fixing and rerunning the command. +### Inject config into existing v3 + +Consult the inject command CLI help: +``` +npx @sanity/plugin-kit inject --help +``` + +for up-to-date specifics. + ## Testing a plugin in Sanity Studio Ensure you have the following script setup in package.json: @@ -299,7 +318,7 @@ Usage init Create a new Sanity plugin verify-package Verify that a Sanity plugin follows plugin-kit conventions - splat Inject plugin-kit complatible package config into an existing plugin directory + inject Inject plugin-kit complatible package config into an existing plugin directory link-watch Recompile plugin automatically on changes and push to yalc version Show the version of ${cliName} currently installed diff --git a/assets/splat/LICENSE b/assets/inject/LICENSE similarity index 100% rename from assets/splat/LICENSE rename to assets/inject/LICENSE diff --git a/assets/splat/editorconfig b/assets/inject/editorconfig similarity index 100% rename from assets/splat/editorconfig rename to assets/inject/editorconfig diff --git a/assets/splat/gitignore b/assets/inject/gitignore similarity index 100% rename from assets/splat/gitignore rename to assets/inject/gitignore diff --git a/assets/splat/npmignore b/assets/inject/npmignore similarity index 100% rename from assets/splat/npmignore rename to assets/inject/npmignore diff --git a/assets/splat/prettierrc.js b/assets/inject/prettierrc.js similarity index 100% rename from assets/splat/prettierrc.js rename to assets/inject/prettierrc.js diff --git a/assets/splat/ecosystem/renovate.json b/assets/inject/renovatebot/renovate.json similarity index 100% rename from assets/splat/ecosystem/renovate.json rename to assets/inject/renovatebot/renovate.json diff --git a/assets/splat/sanity.json b/assets/inject/sanity.json similarity index 100% rename from assets/splat/sanity.json rename to assets/inject/sanity.json diff --git a/assets/splat/ecosystem/.github/workflows/main.yml b/assets/inject/semver-workflow/.github/workflows/main.yml similarity index 100% rename from assets/splat/ecosystem/.github/workflows/main.yml rename to assets/inject/semver-workflow/.github/workflows/main.yml diff --git a/assets/splat/ecosystem/.husky/commit-msg b/assets/inject/semver-workflow/.husky/commit-msg similarity index 100% rename from assets/splat/ecosystem/.husky/commit-msg rename to assets/inject/semver-workflow/.husky/commit-msg diff --git a/assets/splat/ecosystem/.husky/pre-commit b/assets/inject/semver-workflow/.husky/pre-commit similarity index 100% rename from assets/splat/ecosystem/.husky/pre-commit rename to assets/inject/semver-workflow/.husky/pre-commit diff --git a/assets/splat/ecosystem/.releaserc.json b/assets/inject/semver-workflow/.releaserc.json similarity index 100% rename from assets/splat/ecosystem/.releaserc.json rename to assets/inject/semver-workflow/.releaserc.json diff --git a/assets/splat/ecosystem/commitlint.config.js b/assets/inject/semver-workflow/commitlint.config.js similarity index 100% rename from assets/splat/ecosystem/commitlint.config.js rename to assets/inject/semver-workflow/commitlint.config.js diff --git a/assets/inject/semver-workflow/renovate.json b/assets/inject/semver-workflow/renovate.json new file mode 100644 index 00000000..1195e4ea --- /dev/null +++ b/assets/inject/semver-workflow/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>sanity-io/renovate-presets//ecosystem/auto", + "github>sanity-io/renovate-presets//ecosystem/studio-v3" + ] +} diff --git a/assets/splat/template-tsconfig.json b/assets/inject/template-tsconfig.json similarity index 100% rename from assets/splat/template-tsconfig.json rename to assets/inject/template-tsconfig.json diff --git a/assets/splat/v2-incompatible.js.template b/assets/inject/v2-incompatible.js.template similarity index 100% rename from assets/splat/v2-incompatible.js.template rename to assets/inject/v2-incompatible.js.template diff --git a/assets/splat/eslint.config.js b/assets/splat/eslint.config.js deleted file mode 100644 index 8487558e..00000000 --- a/assets/splat/eslint.config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - parser: 'sanipack/babel/eslint-parser', - extends: ['sanity', 'sanity/react', 'prettier'], - ignorePatterns: ['lib/**/'], -} diff --git a/package-lock.json b/package-lock.json index 62e8104c..c2700ad2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "@parcel/transformer-typescript-types": "^2.7.0", "@sanity/semantic-release-preset": "^2.0.0", "@types/eslint": "^8.4.6", + "@types/fs-extra": "^9.0.13", "@types/inquirer": "^8.2.3", "@types/node": "^17.0.40", "@types/nodemon": "^1.19.2", @@ -57,6 +58,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-config-sanity": "^5.1.0", "eslint-plugin-prettier": "^4.2.1", + "fs-extra": "^10.1.0", "husky": "^7.0.4", "json5": "^2.2.1", "lint-staged": "^12.5.0", @@ -754,41 +756,6 @@ "node": ">=v14" } }, - "node_modules/@commitlint/read/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/read/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@commitlint/read/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@commitlint/resolve-extends": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.0.3.tgz", @@ -3855,18 +3822,6 @@ "node": ">=10" } }, - "node_modules/@semantic-release/changelog/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/@semantic-release/changelog/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -3984,41 +3939,6 @@ "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", "dev": true }, - "node_modules/@semantic-release/github/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@semantic-release/github/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@semantic-release/github/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@semantic-release/npm": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.1.tgz", @@ -4046,41 +3966,6 @@ "semantic-release": ">=19.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@semantic-release/npm/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@semantic-release/npm/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@semantic-release/release-notes-generator": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz", @@ -4244,6 +4129,15 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -8397,16 +8291,26 @@ } }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" } }, "node_modules/fs.realpath": { @@ -10263,13 +10167,26 @@ } }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -20245,6 +20162,19 @@ "yalc": "src/yalc.js" } }, + "node_modules/yalc/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/yalc/node_modules/ignore-walk": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", @@ -20261,6 +20191,14 @@ "node": ">=10" } }, + "node_modules/yalc/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/yalc/node_modules/npm-packlist": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz", @@ -20903,35 +20841,6 @@ "@commitlint/types": "^17.0.0", "fs-extra": "^10.0.0", "git-raw-commits": "^2.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "@commitlint/resolve-extends": { @@ -23113,16 +23022,6 @@ "universalify": "^2.0.0" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -23211,33 +23110,6 @@ "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", "dev": true - }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true } } }, @@ -23260,35 +23132,6 @@ "registry-auth-token": "^4.0.0", "semver": "^7.1.2", "tempy": "^1.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "@semantic-release/release-notes-generator": { @@ -23437,6 +23280,15 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -26480,13 +26332,22 @@ "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==" }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } } }, "fs.realpath": { @@ -27886,11 +27747,21 @@ "dev": true }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } } }, "jsonparse": { @@ -35167,6 +35038,16 @@ "yargs": "^16.1.1" }, "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "ignore-walk": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", @@ -35180,6 +35061,14 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "npm-packlist": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz", diff --git a/package.json b/package.json index dcf10a2d..f3284bee 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@parcel/transformer-typescript-types": "^2.7.0", "@sanity/semantic-release-preset": "^2.0.0", "@types/eslint": "^8.4.6", + "@types/fs-extra": "^9.0.13", "@types/inquirer": "^8.2.3", "@types/node": "^17.0.40", "@types/nodemon": "^1.19.2", @@ -98,6 +99,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-config-sanity": "^5.1.0", "eslint-plugin-prettier": "^4.2.1", + "fs-extra": "^10.1.0", "husky": "^7.0.4", "json5": "^2.2.1", "lint-staged": "^12.5.0", diff --git a/src/actions/init.ts b/src/actions/init.ts index 2903ce54..b536bb88 100644 --- a/src/actions/init.ts +++ b/src/actions/init.ts @@ -1,5 +1,5 @@ import path from 'path' -import {splat} from './splat' +import {inject} from './inject' import {ensureDir, writeFile} from '../util/files' import {resolveLatestVersions} from '../npm/resolveLatestVersions' import sharedFlags from '../sharedFlags' @@ -7,7 +7,6 @@ import {TypedFlags} from 'meow' import {getPackage} from '../npm/package' import {defaultSourceJs, defaultSourceTs} from '../configs/default-source' import {incompatiblePluginPackage} from '../constants' -import {ecosystemDevDependencies} from '../ecosystem/ecosystem-preset' export const initFlags = { ...sharedFlags, @@ -34,10 +33,6 @@ export const initFlags = { type: 'boolean', default: true, }, - ecosystemPreset: { - type: 'boolean', - default: false, - }, gitignore: { type: 'boolean', default: true, @@ -59,6 +54,14 @@ export const initFlags = { repo: { type: 'string', }, + presetOnly: { + type: 'boolean', + default: false, + }, + preset: { + type: 'string', + isMultiple: true, + }, } as const export type InitFlags = TypedFlags @@ -82,7 +85,6 @@ export async function init(options: InitOptions) { devDependencies = { ...devDependencies, ...defaultDevDependencies, - ...(options.flags.ecosystemPreset ? await ecosystemDevDependencies() : []), ...(await resolveLatestVersions(['rimraf'])), } peerDependencies = { @@ -90,7 +92,7 @@ export async function init(options: InitOptions) { ...defaultPeerDependencies, } - await splat({ + await inject({ ...options, requireUserConfirmation: !options.flags.force, dependencies, diff --git a/src/actions/splat.ts b/src/actions/inject.ts similarity index 88% rename from src/actions/splat.ts rename to src/actions/inject.ts index c5c80448..ccd59d9b 100644 --- a/src/actions/splat.ts +++ b/src/actions/inject.ts @@ -18,7 +18,8 @@ import { } from '../util/files' import {InitFlags} from './init' import {PackageJson} from './verify/types' -import {ecosystemPresetFiles} from '../ecosystem/ecosystem-preset' +import outdent from 'outdent' +import {injectPresets} from '../presets/presets' const bannedFields = ['login', 'description', 'projecturl', 'email'] const preferredLicenses = ['MIT', 'ISC', 'BSD-3-Clause'] @@ -32,7 +33,7 @@ const otherLicenses = Object.keys(licenses.list).filter((id) => { export type FromTo = {from: string | string[]; to: string | string[]} -export interface SplatOptions { +export interface InjectOptions { basePath: string requireUserConfirmation?: boolean flags: InitFlags @@ -51,7 +52,16 @@ export interface PackageData { gitOrigin?: string } -export async function splat(options: SplatOptions) { +export async function inject(options: InjectOptions) { + if (options.flags.presetOnly) { + log.info('Only apply presets, skipping default inject.') + } else { + await injectBase(options) + } + await injectPresets(options) +} + +async function injectBase(options: InjectOptions) { const {basePath, flags, requireUserConfirmation} = options const info = (write: boolean, msg: string, ...args: string[]) => write && log.info(msg, ...args) // Gather data @@ -103,6 +113,8 @@ export async function splat(options: SplatOptions) { didWrite = await writeEslintrc(options) info(didWrite, 'Wrote .eslintrc.js') + didWrite = await writeEslintIgnore(options) + info(didWrite, 'Wrote .eslintignore') didWrite = await addBuildScripts(newPkg, options) info(didWrite, 'Added build scripts to package.json') @@ -111,7 +123,7 @@ export async function splat(options: SplatOptions) { info(didWrite, 'Added compilation output directory to .gitignore') } -async function writeReadme(data: PackageData, options: SplatOptions) { +async function writeReadme(data: PackageData, options: InjectOptions) { const {basePath} = options const readmePath = path.join(basePath, 'README.md') @@ -128,7 +140,7 @@ async function writeReadme(data: PackageData, options: SplatOptions) { return true } -async function writeEslintrc(options: SplatOptions) { +async function writeEslintrc(options: InjectOptions) { if (!options.flags.eslint) { return false } @@ -159,9 +171,32 @@ async function writeEslintrc(options: SplatOptions) { return true } +async function writeEslintIgnore(options: InjectOptions) { + if (!options.flags.eslint) { + return false + } + const {basePath} = options + + const eslintrc = path.join(basePath, '.eslintignore') + + const content = outdent` + .eslintrc.js + commitlint.config.js + lib + lint-staged.config.js + ${options.flags.typescript ? '*.js' : ''} + `.trim() + + await writeFileWithOverwritePrompt(eslintrc, content, { + encoding: 'utf8', + force: options.flags.force, + }) + return true +} + async function writeLicense( {license}: PackageData, - options: SplatOptions, + options: InjectOptions, licenseChanged: boolean ) { const {basePath, flags} = options @@ -287,24 +322,12 @@ async function resolveProjectDescription(basePath: string, pkg: PackageJson | un } } -async function writeStaticAssets({basePath, flags}: SplatOptions) { +export async function writeAssets(files: FromTo[], {basePath, flags}: InjectOptions) { const assetsDir = await findAssetsDir() - const from = (...segments: string[]) => path.join(assetsDir, 'splat', ...segments) + const from = (...segments: string[]) => path.join(assetsDir, 'inject', ...segments) const to = (...segments: string[]) => path.join(basePath, ...segments) - const files: FromTo[] = [ - {from: 'editorconfig', to: '.editorconfig'}, - {from: 'npmignore', to: '.npmignore'}, - {from: 'sanity.json', to: 'sanity.json'}, - {from: 'v2-incompatible.js.template', to: 'v2-incompatible.js'}, - flags.gitignore && {from: 'gitignore', to: '.gitignore'}, - flags.typescript && {from: 'template-tsconfig.json', to: 'tsconfig.json'}, - flags.prettier && {from: 'prettierrc.js', to: '.prettierrc.js'}, - - ...(flags.ecosystemPreset ? ecosystemPresetFiles() : []), - ].filter((f: false | FromTo): f is FromTo => !!f) - const writes: string[] = [] for (const file of files) { const fromPath = asArray(file.from) @@ -317,6 +340,24 @@ async function writeStaticAssets({basePath, flags}: SplatOptions) { return writes } +async function writeStaticAssets(options: InjectOptions) { + const {flags} = options + + const files: FromTo[] = [ + {from: 'editorconfig', to: '.editorconfig'}, + {from: 'npmignore', to: '.npmignore'}, + {from: 'sanity.json', to: 'sanity.json'}, + {from: 'v2-incompatible.js.template', to: 'v2-incompatible.js'}, + flags.gitignore && {from: 'gitignore', to: '.gitignore'}, + flags.typescript && {from: 'template-tsconfig.json', to: 'tsconfig.json'}, + flags.prettier && {from: 'prettierrc.js', to: '.prettierrc.js'}, + ] + .map((f) => (f ? (f as FromTo) : undefined)) + .filter((f): f is FromTo => !!f) + + return writeAssets(files, options) +} + function asArray(input: string | string[]): string[] { return typeof input === 'string' ? [input] : input } @@ -345,7 +386,7 @@ async function findAssetsDir(): Promise { return assetsDir } -async function addCompileDirToGitIgnore(data: PackageData, options: SplatOptions) { +async function addCompileDirToGitIgnore(data: PackageData, options: InjectOptions) { const gitIgnorePath = path.join(options.basePath, '.gitignore') const gitignore = await readFile(gitIgnorePath, 'utf8').catch(errorToUndefined) if (!gitignore) { diff --git a/src/cli.ts b/src/cli.ts index 64b08ec7..0e615547 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,6 +15,7 @@ export async function cliEntry(argv = process.argv, autoExit = true) { These are common commands used in various situations: init Create a new Sanity plugin + inject Inject config into an existing Sanity v3 plugin verify-package Check that a Sanity plugin package follows V3 conventions. Prints upgrade steps. verify-studio Check that a Sanity Studio follows V3 conventions. Prints upgrade steps. link-watch Recompiles plugin automatically on changes and runs yalc push --publish diff --git a/src/cmds/index.ts b/src/cmds/index.ts index 0ed498d2..7dd6dcc9 100644 --- a/src/cmds/index.ts +++ b/src/cmds/index.ts @@ -1,9 +1,8 @@ export default { init: require.resolve('./init'), + inject: require.resolve('./inject'), 'link-watch': require.resolve('./link-watch'), 'verify-package': require.resolve('./verify-package'), 'verify-studio': require.resolve('./verify-studio'), version: require.resolve('./version'), - // wont make it for initial release - //splat: require.resolve('./splat'), } diff --git a/src/cmds/init.ts b/src/cmds/init.ts index e9c3cc28..6f7bb6c0 100644 --- a/src/cmds/init.ts +++ b/src/cmds/init.ts @@ -7,6 +7,7 @@ import {installDependencies, promptForPackageManager} from '../npm/manager' import {findStudioV3Config, hasSanityJson} from '../sanity/manifest' import {prompt} from '../util/prompt' import {cliName} from '../constants' +import {presetHelpList} from '../presets/presets' const description = `Initialize a new Sanity plugin` @@ -24,14 +25,16 @@ Options --no-scripts Disables scripts from being added to package.json --no-install Disables automatically running package manager install - --ecosystem-preset [beta]: Adds opinionated files and dependencies for conventional-commits, githubworkflow, reonvatebot and semantic-release - --name [package-name] Use the provided package-name --author [name] Use the provided author --repo [url] Use the provided repo url --license [spdx] Use the license with the given SPDX identifier --force No promt when overwriting files + --preset [preset-name] [beta] - Adds config and files from a named preset. --preset can be supplied multiple times. + The following presets are available: +${presetHelpList(30)} + Examples # Initialize a new plugin in the current directory $ ${cliName} init diff --git a/src/cmds/inject.ts b/src/cmds/inject.ts new file mode 100644 index 00000000..2047a0fd --- /dev/null +++ b/src/cmds/inject.ts @@ -0,0 +1,67 @@ +import path from 'path' +import meow from 'meow' +import log from '../util/log' +import {inject} from '../actions/inject' +import {findStudioV3Config} from '../sanity/manifest' +import {initFlags} from '../actions/init' +import {cliName} from '../constants' +import {presetHelpList} from '../presets/presets' + +const description = `Inject configuration into a Sanity plugin` + +const help = ` +Usage + $ ${cliName} inject [dir] [] + +Options + --no-eslint Disables ESLint config and dependencies from being added + --no-prettier Disables prettier config and dependencies from being added + --no-typescript Disables typescript config and dependencies from being added + --no-license Disables LICENSE + package.json license field from being added + --no-editorconfig Disables .editorconfig from being added + --no-gitignore Disables .gitignore from being added + --no-scripts Disables scripts from being added to package.json + + --license [spdx] Use the license with the given SPDX identifier + --force No promt when overwriting files + + --preset [preset-name] [beta] - Adds config and files from a named preset. --preset can be supplied multiple times. + The following presets are available: +${presetHelpList(30)} + --preset-only Skips the default inject steps. Use this to apply a preset to an otherwise complete plugin. + +Examples + # Inject configuration into the plugin in the current directory + $ ${cliName} inject + + # Inject configuration into the plugin in ~/my-plugin + $ ${cliName} inject ~/my-plugin + + # Don't inject eslint or prettier + $ ${cliName} inject --no-eslint --no-prettier + + # Inject plugin configuration and semver-workflow into the plugin in the current directory + $ @sanity/plugin-kit inject --preset semver-workflow + + # Only inject semver-workflow and renovatebot config from presets + $ ${cliName} inject --preset-only --preset semver-workflow --preset renovatebot + +` + +async function run({argv}: {argv: string[]}) { + const cli = meow(help, {flags: initFlags, argv, description}) + const basePath = path.resolve(cli.input[0] || process.cwd()) + + const {v3ConfigFile} = await findStudioV3Config(basePath) + if (v3ConfigFile) { + throw new Error( + `${v3ConfigFile} exists - are you trying to INJECT into a studio instead of a plugin?` + ) + } + log.info('Inject config into plugin in "%s"', basePath) + + await inject({basePath, flags: cli.flags, validate: false}) + log.info('Done!') +} + +export default run diff --git a/src/cmds/splat.ts b/src/cmds/splat.ts deleted file mode 100644 index e3160717..00000000 --- a/src/cmds/splat.ts +++ /dev/null @@ -1,59 +0,0 @@ -import path from 'path' -import meow from 'meow' -import pkg from '../../package.json' -import log from '../util/log' -import {splat} from '../actions/splat' -import {hasSanityJson} from '../sanity/manifest' -import sharedFlags from '../sharedFlags' -import {initFlags} from '../actions/init' - -const description = `"Splat" configuration into a Sanity plugin` - -const help = ` -Usage - $ ${pkg.binname} splat [dir] [] - -Options - --no-eslint Disables ESLint config and dependencies from being added - --no-prettier Disables prettier config and dependencies from being added - --no-typescript Disables typescript config and dependencies from being added - --no-license Disables LICENSE + package.json license field from being added - --no-editorconfig Disables .editorconfig from being added - --no-gitignore Disables .gitignore from being added - --no-scripts Disables scripts from being added to package.json - --license [spdx] Use the license with the given SPDX identifier - -Examples - # Splat configuration into the plugin in the current directory - $ ${pkg.binname} splat - - # Splat configuration into the plugin in ~/my-plugin - $ ${pkg.binname} splat ~/my-plugin - - # Don't add eslint or prettier - $ ${pkg.binname} splat --no-eslint --no-prettier -` - -async function run({argv}: {argv: string[]}) { - const cli = meow(help, {flags: initFlags, argv, description}) - const basePath = path.resolve(cli.input[0] || process.cwd()) - - const {exists, isRoot} = await hasSanityJson(basePath) - if (exists && isRoot) { - throw new Error( - `sanity.json has a "root" property set to true - are you trying to splat into a studio instead of a plugin?` - ) - } - - if (!exists) { - throw new Error( - `sanity.json does not exist in this directory, maybe you want "${pkg.binname} init" instead?` - ) - } - - await splat({basePath, flags: cli.flags}) - - log.info('Done!') -} - -export default run diff --git a/src/ecosystem/ecosystem-preset.ts b/src/ecosystem/ecosystem-preset.ts deleted file mode 100644 index da8117f0..00000000 --- a/src/ecosystem/ecosystem-preset.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {FromTo} from '../actions/splat' -import {resolveLatestVersions} from '../npm/resolveLatestVersions' - -export function ecosystemPresetFiles(): FromTo[] { - return [ - { - from: ['ecosystem', '.github', 'workflows', 'main.yml'], - to: ['.github', 'workflows', 'main.yml'], - }, - { - from: ['ecosystem', '.husky', 'commit-msg'], - to: ['.husky', 'commit-msg'], - }, - { - from: ['ecosystem', '.husky', 'pre-commit'], - to: ['.husky', 'pre-commit'], - }, - {from: ['ecosystem', '.releaserc.json'], to: '.releaserc.json'}, - {from: ['ecosystem', 'commitlint.config.js'], to: 'commitlint.config.js'}, - {from: ['ecosystem', 'renovate.json'], to: 'renovate.json'}, - ] -} - -export async function ecosystemDevDependencies(): Promise> { - return resolveLatestVersions([ - '@commitlint/cli', - '@commitlint/config-conventional', - '@sanity/semantic-release-preset', - 'husky', - 'lint-staged', - ]) -} diff --git a/src/npm/package.ts b/src/npm/package.ts index a6fd30a7..97cf25ef 100644 --- a/src/npm/package.ts +++ b/src/npm/package.ts @@ -10,7 +10,7 @@ import {resolveLatestVersions} from './resolveLatestVersions' import {hasSourceEquivalent, writeJsonFile} from '../util/files' import log from '../util/log' import {cliName} from '../constants' -import {PackageData, SplatOptions} from '../actions/splat' +import {InjectOptions, PackageData} from '../actions/inject' import {expectedScripts} from '../actions/verify/validations' import {PackageJson} from '../actions/verify/types' @@ -181,23 +181,10 @@ function validateLockFiles(options: {basePath: string}) { } } -export function getReferencedPaths( - packageJson: Record, - basePath: string -): string[] { - return pathKeys - .filter((key) => key in packageJson) - .map((key) => - path.isAbsolute(packageJson[key]) - ? packageJson[key] - : path.resolve(basePath, packageJson[key]) - ) -} - -export async function writePackageJson(data: PackageData, options: SplatOptions) { +export async function writePackageJson(data: PackageData, options: InjectOptions) { const {user, pluginName, license, description, pkg: prevPkg, gitOrigin} = data const {peerDependencies: addPeers, dependencies: addDeps, devDependencies: addDevDeps} = options - const {basePath, flags} = options + const {flags} = options const prev = prevPkg || {} const usePrettier = flags.prettier !== false @@ -288,7 +275,7 @@ export async function writePackageJson(data: PackageData, options: SplatOptions) const differs = JSON.stringify(prev) !== JSON.stringify(manifest) log.debug('Does manifest differ? %s', differs ? 'yes' : 'no') if (differs) { - await writeJsonFile(path.join(basePath, 'package.json'), manifest) + await writePackageJsonDirect(manifest, options) } return differs ? manifest : prev @@ -321,14 +308,7 @@ function repoFromOrigin(gitOrigin?: string) { } } -function withSanityKeywords(keywords: string[] = []) { - const newKeywords = new Set(keywords) - newKeywords.add('sanity') - newKeywords.add('sanity-plugin') - return Array.from(newKeywords) -} - -function addScript(cmd: string, existing: string) { +export function addScript(cmd: string, existing: string) { if (existing && existing.includes(cmd)) { return existing } @@ -336,30 +316,44 @@ function addScript(cmd: string, existing: string) { return existing ? `${existing} && ${cmd}` : cmd } -export async function addBuildScripts(manifest: PackageJson, {basePath, flags}: SplatOptions) { - if (!flags.scripts) { - return false - } +export async function addPackageJsonScripts( + manifest: PackageJson, + options: InjectOptions, + updateScripts: (currentScripts: Record) => Record +) { const originalScripts = manifest.scripts || {} - const scripts = {...originalScripts} - scripts.clean = addScript(`rimraf lib`, scripts.clean) - scripts.lint = addScript(`eslint .`, scripts.lint) - scripts.prebuild = addScript('npm run clean && ' + expectedScripts.prebuild, scripts.prebuild) - scripts.build = addScript(expectedScripts.build, scripts.build) - scripts.watch = addScript(expectedScripts.watch, scripts.watch) - scripts['link-watch'] = addScript(expectedScripts['link-watch'], scripts['link-watch']) - scripts.prepublishOnly = addScript(expectedScripts.prepublishOnly, scripts.prepublishOnly) + const scripts = updateScripts({...originalScripts}) const differs = Object.keys(scripts).some((key) => scripts[key] !== originalScripts[key]) if (differs) { - await writeJsonFile(path.join(basePath, 'package.json'), {...manifest, scripts}) + await writePackageJsonDirect({...manifest, scripts}, options) } return differs } -function sortKeys>(unordered: T): T { +export async function writePackageJsonDirect(manifest: PackageJson, {basePath}: InjectOptions) { + await writeJsonFile(path.join(basePath, 'package.json'), manifest) +} + +export async function addBuildScripts(manifest: PackageJson, options: InjectOptions) { + if (!options.flags.scripts) { + return false + } + return addPackageJsonScripts(manifest, options, (scripts) => { + scripts.clean = addScript(`rimraf lib`, scripts.clean) + scripts.lint = addScript(`eslint .`, scripts.lint) + scripts.prebuild = addScript('npm run clean && ' + expectedScripts.prebuild, scripts.prebuild) + scripts.build = addScript(expectedScripts.build, scripts.build) + scripts.watch = addScript(expectedScripts.watch, scripts.watch) + scripts['link-watch'] = addScript(expectedScripts['link-watch'], scripts['link-watch']) + scripts.prepublishOnly = addScript(expectedScripts.prepublishOnly, scripts.prepublishOnly) + return scripts + }) +} + +export function sortKeys>(unordered: T): T { return Object.keys(unordered) .sort() .reduce((obj, key) => { diff --git a/src/presets/presets.ts b/src/presets/presets.ts new file mode 100644 index 00000000..af4a2dd2 --- /dev/null +++ b/src/presets/presets.ts @@ -0,0 +1,52 @@ +import {InjectOptions} from '../actions/inject' +import {semverWorkflowPreset} from './semver-workflow' +import {renovatePreset} from './renovatebot' + +export interface Preset { + name: string + description: string + apply: (options: InjectOptions) => Promise +} + +const presets: Preset[] = [semverWorkflowPreset, renovatePreset] +const presetNames = presets.map((p) => p.name) + +export function presetHelpList(padStart: number) { + return presets + .map((p) => `${''.padStart(padStart)}${p.name.padEnd(20)}${p.description}`) + .join('\n') +} + +export async function injectPresets(options: InjectOptions) { + if (options.flags.presetOnly && !options.flags.preset?.length) { + throw new Error('--preset-only, but no --preset [preset-name] was provided.') + } + + const applyPresets = presetsFromInput(options.flags.preset) + for (const preset of applyPresets) { + await preset.apply(options) + } +} + +function presetsFromInput(inputPresets: string[] | undefined): Preset[] { + if (!inputPresets) { + return [] + } + const unknownPresets = inputPresets.filter((p) => !presetNames.includes(p)) + if (unknownPresets.length) { + throw new Error( + `Unknown --preset(s): [${unknownPresets.join(', ')}]. Must be one of: [${presetNames.join( + ', ' + )}]` + ) + } + + return inputPresets + .filter(onlyUnique) + .map((presetName) => presets.find((p) => p.name === presetName)) + .filter((p): p is Preset => !!p) +} + +function onlyUnique(value: string, index: number, arr: string[]) { + return arr.indexOf(value) === index +} diff --git a/src/presets/renovatebot.ts b/src/presets/renovatebot.ts new file mode 100644 index 00000000..5e149870 --- /dev/null +++ b/src/presets/renovatebot.ts @@ -0,0 +1,12 @@ +import {Preset} from './presets' +import {InjectOptions, writeAssets} from '../actions/inject' + +export const renovatePreset: Preset = { + name: 'renovatebot', + description: 'Files to enable renovatebot.', + apply: applyPreset, +} + +async function applyPreset(options: InjectOptions) { + await writeAssets([{from: ['renovatebot', 'renovate.json'], to: 'renovate.json'}], options) +} diff --git a/src/presets/semver-workflow.ts b/src/presets/semver-workflow.ts new file mode 100644 index 00000000..f866e34c --- /dev/null +++ b/src/presets/semver-workflow.ts @@ -0,0 +1,71 @@ +import {FromTo, InjectOptions, writeAssets} from '../actions/inject' +import {resolveLatestVersions} from '../npm/resolveLatestVersions' +import {Preset} from './presets' +import { + addBuildScripts, + addPackageJsonScripts, + addScript, + getPackage, + sortKeys, + writePackageJsonDirect, +} from '../npm/package' +import log from '../util/log' + +export const semverWorkflowPreset: Preset = { + name: 'semver-workflow', + description: + 'Files and dependencies for conventional-commits, github workflow and semantic-release.', + apply: applyPreset, +} + +const info = (write: boolean, msg: string, ...args: string[]) => write && log.info(msg, ...args) + +async function applyPreset(options: InjectOptions) { + await writeAssets(semverWorkflowFiles(), options) + await addPrepareScript(options) + await addDevDependencies(options) +} + +async function addPrepareScript(options: InjectOptions) { + const pkg = await getPackage(options) + const didWrite = await addPackageJsonScripts(pkg, options, (scripts) => { + scripts.prepare = addScript(`husky install`, scripts.prepare) + return scripts + }) + info(didWrite, 'Added prepare script to package.json') +} + +async function addDevDependencies(options: InjectOptions) { + const pkg = await getPackage(options) + const devDeps = sortKeys({ + ...pkg.devDependencies, + ...(await semverWorkflowDependencies()), + }) + const newPkg = {...pkg} + newPkg.devDependencies = devDeps + await writePackageJsonDirect(newPkg, options) + log.info('Updated devDependencies.') +} + +function semverWorkflowFiles(): FromTo[] { + return [ + {from: ['.github', 'workflows', 'main.yml'], to: ['.github', 'workflows', 'main.yml']}, + {from: ['.husky', 'commit-msg'], to: ['.husky', 'commit-msg']}, + {from: ['.husky', 'pre-commit'], to: ['.husky', 'pre-commit']}, + {from: ['.releaserc.json'], to: '.releaserc.json'}, + {from: ['commitlint.config.js'], to: 'commitlint.config.js'}, + ].map((fromTo) => ({ + ...fromTo, + from: ['semver-workflow', ...fromTo.from], + })) +} + +async function semverWorkflowDependencies(): Promise> { + return resolveLatestVersions([ + '@commitlint/cli', + '@commitlint/config-conventional', + '@sanity/semantic-release-preset', + 'husky', + 'lint-staged', + ]) +} diff --git a/src/util/prompt.ts b/src/util/prompt.ts index 933d2f58..f9772409 100644 --- a/src/util/prompt.ts +++ b/src/util/prompt.ts @@ -5,7 +5,7 @@ import inquirer from 'inquirer' import validNpmName from 'validate-npm-package-name' // @ts-expect-error missing types import githubUrlToObject from 'github-url-to-object' -import {SplatOptions} from '../actions/splat' +import {InjectOptions} from '../actions/inject' export async function prompt( message: string, @@ -24,7 +24,7 @@ export async function prompt( prompt.separator = () => new inquirer.Separator() -export function promptForPackageName({basePath}: SplatOptions, defaultVal?: string) { +export function promptForPackageName({basePath}: InjectOptions, defaultVal?: string) { return prompt('Plugin name (sanity-plugin-...)', { default: defaultVal || path.basename(basePath), filter: (name) => { @@ -46,7 +46,7 @@ export function promptForPackageName({basePath}: SplatOptions, defaultVal?: stri }) } -export function promptForRepoOrigin(options: SplatOptions, defaultVal?: string) { +export function promptForRepoOrigin(options: InjectOptions, defaultVal?: string) { return prompt('Git repository URL', { default: defaultVal, filter: (raw) => { diff --git a/src/util/readme.ts b/src/util/readme.ts index d62de672..f0e0881d 100644 --- a/src/util/readme.ts +++ b/src/util/readme.ts @@ -1,7 +1,7 @@ import outdent from 'outdent' // @ts-expect-error missing types import licenses from '@rexxars/choosealicense-list' -import {PackageData} from '../actions/splat' +import {PackageData} from '../actions/inject' import {User} from './user' export function generateReadme(data: PackageData) { diff --git a/src/util/user.ts b/src/util/user.ts index fc2c578b..b3f52f32 100644 --- a/src/util/user.ts +++ b/src/util/user.ts @@ -6,7 +6,7 @@ import {validate as isValidEmail} from 'email-validator' import {readJsonFile} from './files' import {request} from './request' import {prompt} from './prompt' -import {SplatOptions} from '../actions/splat' +import {InjectOptions} from '../actions/inject' import {PackageJson} from '../actions/verify/types' export interface User { @@ -15,7 +15,7 @@ export interface User { } export async function getUserInfo( - {requireUserConfirmation, flags}: SplatOptions, + {requireUserConfirmation, flags}: InjectOptions, pkg?: PackageJson ): Promise { const userInfo = diff --git a/test/fixtures/inject/valid/.editorconfig b/test/fixtures/inject/valid/.editorconfig new file mode 100644 index 00000000..e55113fe --- /dev/null +++ b/test/fixtures/inject/valid/.editorconfig @@ -0,0 +1,13 @@ +; editorconfig.org +root = true +charset= utf8 + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/test/fixtures/inject/valid/.eslintrc b/test/fixtures/inject/valid/.eslintrc new file mode 100644 index 00000000..9153427b --- /dev/null +++ b/test/fixtures/inject/valid/.eslintrc @@ -0,0 +1,12 @@ +{ + "root": true, + "env": { + "node": true, + "browser": true + }, + "extends": [ + "sanity", + "sanity/typescript", + "plugin:prettier/recommended" + ] +} diff --git a/test/fixtures/inject/valid/.gitignore b/test/fixtures/inject/valid/.gitignore new file mode 100644 index 00000000..76ccb897 --- /dev/null +++ b/test/fixtures/inject/valid/.gitignore @@ -0,0 +1,59 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# macOS finder cache file +.DS_Store + +# VS Code settings +.vscode + +# IntelliJ +.idea +*.iml + +# Cache +.cache + +# Parcel +.parcel-cache + +test/fixtures/init/empty + +# Compiled plugin +lib + diff --git a/test/fixtures/inject/valid/.npmignore b/test/fixtures/inject/valid/.npmignore new file mode 100644 index 00000000..3b109af8 --- /dev/null +++ b/test/fixtures/inject/valid/.npmignore @@ -0,0 +1,9 @@ +/test +/coverage +.editorconfig +.eslintrc +.gitignore +.github +.prettierrc +.travis.yml +.nyc_output diff --git a/test/fixtures/inject/valid/.prettierrc.js b/test/fixtures/inject/valid/.prettierrc.js new file mode 100644 index 00000000..cf7b086d --- /dev/null +++ b/test/fixtures/inject/valid/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + semi: false, + printWidth: 100, + bracketSpacing: false, + singleQuote: true, +} diff --git a/test/fixtures/inject/valid/LICENSE b/test/fixtures/inject/valid/LICENSE new file mode 100644 index 00000000..fe6d75a9 --- /dev/null +++ b/test/fixtures/inject/valid/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Test Person + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/fixtures/inject/valid/README.md b/test/fixtures/inject/valid/README.md new file mode 100644 index 00000000..2b6efbeb --- /dev/null +++ b/test/fixtures/inject/valid/README.md @@ -0,0 +1,32 @@ +# sanity-plugin-test-plugin + +## Installation + +``` +npm install --save sanity-plugin-test-plugin +``` + +or + +``` +yarn add sanity-plugin-test-plugin +``` + +## Usage +Add it as a plugin in sanity.config.ts (or .js): + +``` + import {createConfig} from 'sanity' + import {myPlugin} from 'sanity-plugin-test-plugin' + + export const createConfig({ + /... + plugins: [ + myPlugin({}) + ] + }) +``` +## License + +MIT © Test Person +See LICENSE diff --git a/test/fixtures/inject/valid/package.json b/test/fixtures/inject/valid/package.json new file mode 100644 index 00000000..8d67ff15 --- /dev/null +++ b/test/fixtures/inject/valid/package.json @@ -0,0 +1,72 @@ +{ + "name": "sanity-plugin-test-plugin", + "version": "1.0.0", + "description": "", + "author": "Test Person ", + "license": "MIT", + "source": "./src/index.ts", + "main": "./lib/cjs/index.js", + "module": "./lib/esm/index.js", + "types": "./lib/types/index.d.ts", + "exports": { + ".": { + "require": "./lib/cjs/index.js", + "default": "./lib/esm/index.js" + } + }, + "files": [ + "src", + "lib", + "v2-incompatible.js", + "sanity.json" + ], + "sanityPlugin": { + "verifyPackage": { + "tsc": false + } + }, + "scripts": { + "clean": "rimraf lib", + "lint": "eslint .", + "prebuild": "npm run clean && plugin-kit verify-package --silent", + "build": "parcel build --no-cache", + "watch": "parcel watch", + "link-watch": "plugin-kit link-watch", + "prepublishOnly": "npm run build" + }, + "repository": { + "type": "git", + "url": "https://github.com/sanity-io/sanity" + }, + "engines": { + "node": ">=14.0.0" + }, + "dependencies": { + "@sanity/incompatible-plugin": "^0.0.1-studio-v3.1" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.27.1", + "@sanity/plugin-kit": "^0.0.1-studio-v3.1", + "@typescript-eslint/parser": "^5.27.1", + "eslint": "^8.17.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-sanity": "^6.0.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.30.0", + "eslint-plugin-react-hooks": "^4.5.0", + "parcel": "^2.6.0", + "prettier": "^2.6.2", + "react": "^17.0.0 || ^18.0.0", + "rimraf": "^3.0.2", + "sanity": "2.29.5-purple-unicorn.856", + "typescript": "^4.7.3" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "sanity": "purple-unicorn" + }, + "bugs": { + "url": "https://github.com/sanity-io/sanity/issues" + }, + "homepage": "https://github.com/sanity-io/sanity#readme" +} diff --git a/test/fixtures/inject/valid/sanity.json b/test/fixtures/inject/valid/sanity.json new file mode 100644 index 00000000..b87d48a6 --- /dev/null +++ b/test/fixtures/inject/valid/sanity.json @@ -0,0 +1,8 @@ +{ + "parts": [ + { + "implements": "part:@sanity/base/sanity-root", + "path": "./v2-incompatible.js" + } + ] +} diff --git a/test/fixtures/inject/valid/src/index.ts b/test/fixtures/inject/valid/src/index.ts new file mode 100644 index 00000000..74c7a171 --- /dev/null +++ b/test/fixtures/inject/valid/src/index.ts @@ -0,0 +1,28 @@ +import {createPlugin} from 'sanity' + +interface MyPluginConfig { + /* nothing here yet */ +} + +/** + * ## Usage in sanity.config.ts (or .js) + * + * ``` + * import {createConfig} from 'sanity' + * import {myPlugin} from 'sanity-plugin-test-plugin' + * + * export const createConfig({ + * /... + * plugins: [ + * myPlugin({}) + * ] + * }) + * ``` + */ +export const myPlugin = createPlugin((config = {}) => { + // eslint-disable-next-line no-console + console.log('hello from sanity-plugin-test-plugin') + return { + name: 'sanity-plugin-test-plugin', + } +}) diff --git a/test/fixtures/inject/valid/tsconfig.json b/test/fixtures/inject/valid/tsconfig.json new file mode 100644 index 00000000..3fdd342f --- /dev/null +++ b/test/fixtures/inject/valid/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "jsx": "preserve", + "moduleResolution": "node", + "target": "esnext", + "module": "esnext", + "esModuleInterop": true, + "lib": ["es2015", "es2016", "es2017", "dom"], + "strict": true, + "sourceMap": false, + "inlineSourceMap": false, + "downlevelIteration": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "lib", + "skipLibCheck": true, + "isolatedModules": true, + "checkJs": false + }, + "include": ["src/**/*"] +} diff --git a/test/fixtures/inject/valid/v2-incompatible.js b/test/fixtures/inject/valid/v2-incompatible.js new file mode 100644 index 00000000..426e8042 --- /dev/null +++ b/test/fixtures/inject/valid/v2-incompatible.js @@ -0,0 +1,11 @@ +const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin') +const {name, version, sanityExchangeUrl} = require('./package.json') + +export default showIncompatiblePluginDialog({ + name: name, + versions: { + v3: version, + v2: undefined, + }, + sanityExchangeUrl, +}) diff --git a/test/init.test.ts b/test/init.test.ts index 9c02a054..c17dd493 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -53,6 +53,14 @@ tap.test('plugin-kit init --force in empty directory', async (t) => { 'plugin:react-hooks/recommended', 'plugin:prettier/recommended' ) + await fileContains( + '.eslintignore', + '.eslintrc.js', + 'commitlint.config.js', + 'lib', + 'lint-staged.config.js', + '*.js' + ) await fileContains('.npmignore', '/test') await fileContains('.prettierrc.js', 'semi: false') await fileContains('sanity.json', '"path": "./v2-incompatible.js"') @@ -182,12 +190,12 @@ tap.test('plugin-kit init --force with all the opt-outs in empty directory', asy }) }) -tap.test('plugin-kit init --force --ecosystem-preset in empty directory', async (t) => { +tap.test('plugin-kit init --force --preset semver-workflow in empty directory', async (t) => { await testFixture({ fixturePath: 'init/empty', - relativeOutPath: 'defaults-ecosystem', + relativeOutPath: 'defaults-semver-workflow', command: ({outputDir}) => - runCliCommand('init', [outputDir, ...initTestArgs, '--ecosystem-preset']), + runCliCommand('init', [outputDir, ...initTestArgs, '--preset', 'semver-workflow']), assert: async ({result: {stdout, stderr}, outputDir}) => { t.equal(stderr, '', 'should have empty stderr') @@ -198,10 +206,6 @@ tap.test('plugin-kit init --force --ecosystem-preset in empty directory', async await fileContains(path.join('.husky', 'pre-commit'), 'npx lint-staged') await fileContains(path.join('.releaserc.json'), '@sanity/semantic-release-preset') await fileContains(path.join('commitlint.config.js'), '@commitlint/config-conventional') - await fileContains( - path.join('renovate.json'), - 'github>sanity-io/renovate-presets//ecosystem/auto' - ) const pkg: PackageJson = JSON.parse(await readFile(path.join(outputDir, 'package.json'))) @@ -217,6 +221,8 @@ tap.test('plugin-kit init --force --ecosystem-preset in empty directory', async ].sort(), 'should have expected devDependencies' ) + + t.strictSame(pkg.scripts?.prepare, 'husky install') }, }) }) diff --git a/test/inject.test.ts b/test/inject.test.ts new file mode 100644 index 00000000..f2d0bc08 --- /dev/null +++ b/test/inject.test.ts @@ -0,0 +1,89 @@ +import tap from 'tap' +import {fileContainsValidator, runCliCommand, testFixture} from './fixture-utils' +import path from 'path' +import {copySync} from 'fs-extra' + +tap.test('plugin-kit inject --preset semver-workflow into existing plugin directory', async (t) => { + await testFixture({ + fixturePath: 'inject/valid', + relativeOutPath: '../semver-workflow', + command: async ({fixtureDir, outputDir}) => { + copySync(fixtureDir, outputDir) + return runCliCommand('inject', [outputDir, '--preset-only', '--preset', 'semver-workflow']) + }, + assert: async ({result: {stdout, stderr}, outputDir}) => { + t.equal(stderr, '', 'should have empty stderr') + t.match(stdout, `Only apply presets, skipping default inject.`) + t.match(stdout, `Inject config into plugin in "${outputDir}"`) + + const fileContains = fileContainsValidator(t, outputDir) + + // only check for a single file from the preset: + // rest is covered by the init tests; it uses the same codepath + await fileContains(path.join('.github', 'workflows', 'main.yml'), 'CI & Release') + }, + }) +}) + +tap.test('plugin-kit inject --preset renovatebot into existing plugin directory', async (t) => { + await testFixture({ + fixturePath: 'inject/valid', + relativeOutPath: '../renovatebot', + command: async ({fixtureDir, outputDir}) => { + copySync(fixtureDir, outputDir) + return runCliCommand('inject', [outputDir, '--preset-only', '--preset', 'renovatebot']) + }, + assert: async ({result: {stdout, stderr}, outputDir}) => { + t.equal(stderr, '', 'should have empty stderr') + + const fileContains = fileContainsValidator(t, outputDir) + + await fileContains( + path.join('renovate.json'), + '"github>sanity-io/renovate-presets//ecosystem/auto"' + ) + }, + }) +}) + +tap.test('plugin-kit inject --preset jest into existing plugin directory', async (t) => { + await testFixture({ + fixturePath: 'inject/jest', + relativeOutPath: '../renovatebot', + command: async ({fixtureDir, outputDir}) => { + copySync(fixtureDir, outputDir) + return runCliCommand('inject', [outputDir, '--preset-only', '--preset', 'renovatebot']) + }, + assert: async ({result: {stdout, stderr}, outputDir}) => { + t.equal(stderr, '', 'should have empty stderr') + + const fileContains = fileContainsValidator(t, outputDir) + + await fileContains('renovate.json', '"github>sanity-io/renovate-presets//ecosystem/auto"') + }, + }) +}) + +tap.test('plugin-kit inject --preset-only requires --preset', async (t) => { + await testFixture({ + fixturePath: 'inject/valid', + command: async ({fixtureDir}) => { + return runCliCommand('inject', [fixtureDir, '--preset-only']) + }, + assert: async ({result: {stdout, stderr}, outputDir}) => { + t.match(stderr, '--preset-only, but no --preset [preset-name] was provided.') + }, + }) +}) + +tap.test('plugin-kit inject --preset-only --preset does-not-exist', async (t) => { + await testFixture({ + fixturePath: 'inject/valid', + command: async ({fixtureDir}) => { + return runCliCommand('inject', [fixtureDir, '--preset-only', '--preset', 'does-not-exist']) + }, + assert: async ({result: {stdout, stderr}, outputDir}) => { + t.match(stderr, 'Unknown --preset(s): [does-not-exist]. Must be one of: [') + }, + }) +})