From 13ef06ef0787d7cb9341924c346882410a69fa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Lundga=CC=8Ard?= Date: Tue, 20 Dec 2022 17:40:10 +0100 Subject: [PATCH] feat: optional `outDir` directory --- assets/inject/gitignore | 57 ------- assets/inject/package.config.template | 17 -- assets/inject/template-tsconfig.json | 10 -- assets/inject/template-tsconfig.lib.json | 16 -- assets/inject/template-tsconfig.settings.json | 15 -- src/actions/init.ts | 2 + src/actions/inject.ts | 146 +++++++----------- src/actions/link-watch.ts | 6 +- src/actions/verify-package.ts | 7 +- src/actions/verify/validations.ts | 67 ++++---- src/cmds/inject.ts | 7 +- src/configs/eslint.ts | 50 ++++++ src/configs/git.ts | 68 ++++++++ src/configs/pkg-config.ts | 31 ++++ src/configs/tsconfig.ts | 79 ++++++++++ src/constants.ts | 2 + src/npm/package.ts | 32 ++-- src/presets/renovatebot.ts | 11 +- src/presets/semver-workflow.ts | 40 +++-- src/presets/ui-workshop.ts | 38 +++-- src/sanity/manifest.ts | 4 +- .../test/verify-package.test.ts.test.cjs | 4 +- test/fixtures/inject/valid/package.json | 6 +- .../every-failure-possible/.gitignore | 1 - .../every-failure-possible/package.json | 4 +- .../verify-package/invalid-eslint/.gitignore | 1 - .../invalid-eslint/package.json | 6 +- test/fixtures/verify-package/valid/.gitignore | 1 - .../verify-package/valid/package.config.ts | 7 + test/init.test.ts | 2 +- 30 files changed, 448 insertions(+), 289 deletions(-) delete mode 100644 assets/inject/gitignore delete mode 100644 assets/inject/package.config.template delete mode 100644 assets/inject/template-tsconfig.json delete mode 100644 assets/inject/template-tsconfig.lib.json delete mode 100644 assets/inject/template-tsconfig.settings.json create mode 100644 src/configs/eslint.ts create mode 100644 src/configs/git.ts create mode 100644 src/configs/pkg-config.ts create mode 100644 src/configs/tsconfig.ts create mode 100644 test/fixtures/verify-package/valid/package.config.ts diff --git a/assets/inject/gitignore b/assets/inject/gitignore deleted file mode 100644 index 8e1948f0..00000000 --- a/assets/inject/gitignore +++ /dev/null @@ -1,57 +0,0 @@ -# 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 - -# Yalc -.yalc -yalc.lock - -# npm package zips -*.tgz diff --git a/assets/inject/package.config.template b/assets/inject/package.config.template deleted file mode 100644 index dfd58aff..00000000 --- a/assets/inject/package.config.template +++ /dev/null @@ -1,17 +0,0 @@ -import {defineConfig} from '@sanity/pkg-utils' - -export default defineConfig({ - dist: 'lib', - legacyExports: true, - tsconfig: 'tsconfig.lib.json', - - // Remove this block to enable strict export validation - extract: { - rules: { - 'ae-forgotten-export': 'off', - 'ae-incompatible-release-tags': 'off', - 'ae-internal-missing-underscore': 'off', - 'ae-missing-release-tag': 'off', - }, - }, -}) diff --git a/assets/inject/template-tsconfig.json b/assets/inject/template-tsconfig.json deleted file mode 100644 index 1d58b71d..00000000 --- a/assets/inject/template-tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.settings", - "include": ["./src", "./package.config.ts"], - "compilerOptions": { - "rootDir": ".", - "outDir": "./lib", - "jsx": "preserve", - "noEmit": true - } -} diff --git a/assets/inject/template-tsconfig.lib.json b/assets/inject/template-tsconfig.lib.json deleted file mode 100644 index 6ecc53bf..00000000 --- a/assets/inject/template-tsconfig.lib.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "./tsconfig.settings", - "include": ["./src"], - "exclude": [ - "./src/**/__fixtures__", - "./src/**/__mocks__", - "./src/**/*.test.ts", - "./src/**/*.test.tsx" - ], - "compilerOptions": { - "rootDir": ".", - "outDir": "./lib", - "jsx": "preserve", - "emitDeclarationOnly": true - } -} diff --git a/assets/inject/template-tsconfig.settings.json b/assets/inject/template-tsconfig.settings.json deleted file mode 100644 index 11a161f1..00000000 --- a/assets/inject/template-tsconfig.settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "node", - "target": "esnext", - "module": "esnext", - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "esModuleInterop": true, - "strict": true, - "downlevelIteration": true, - "declaration": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true, - "isolatedModules": true - } -} diff --git a/src/actions/init.ts b/src/actions/init.ts index d2d95fac..3933bbfe 100644 --- a/src/actions/init.ts +++ b/src/actions/init.ts @@ -5,6 +5,7 @@ import sharedFlags from '../sharedFlags' import {TypedFlags} from 'meow' import {getPackage} from '../npm/package' import {defaultSourceJs, defaultSourceTs} from '../configs/default-source' +import {defaultOutDir} from '../constants' export const initFlags = { ...sharedFlags, @@ -76,6 +77,7 @@ export async function init(options: InitOptions) { await inject({ ...options, + outDir: defaultOutDir, requireUserConfirmation: !options.flags.force, dependencies, devDependencies, diff --git a/src/actions/inject.ts b/src/actions/inject.ts index e48ab999..f646a62d 100644 --- a/src/actions/inject.ts +++ b/src/actions/inject.ts @@ -18,8 +18,11 @@ import { } from '../util/files' import {InitFlags} from './init' import {PackageJson} from './verify/types' -import outdent from 'outdent' import {injectPresets} from '../presets/presets' +import {tsconfigTemplateDist, tsconfigTemplate, tsconfigTemplateSettings} from '../configs/tsconfig' +import {pkgConfigTemplate} from '../configs/pkg-config' +import {gitignoreTemplate} from '../configs/git' +import {eslintignoreTemplate, eslintrcTemplate} from '../configs/eslint' const bannedFields = ['login', 'description', 'projecturl', 'email'] const preferredLicenses = ['MIT', 'ISC', 'BSD-3-Clause'] @@ -31,7 +34,20 @@ const otherLicenses = Object.keys(licenses.list).filter((id) => { ) }) -export type FromTo = {from: string | string[]; to: string | string[]} +export interface InjectCopyFromTo { + type: 'copy' + from: string | string[] + to: string | string[] +} + +export interface InjectTemplate { + type: 'template' + to: string | string[] + value: string + force?: boolean +} + +export type Injectable = InjectCopyFromTo | InjectTemplate export interface InjectOptions { basePath: string @@ -41,6 +57,7 @@ export interface InjectOptions { devDependencies?: Record peerDependencies?: Record validate?: boolean + outDir: string } export interface PackageData { @@ -111,11 +128,6 @@ async function injectBase(options: InjectOptions) { didWrite = await writeStaticAssets(options) info(didWrite.length > 0, 'Wrote static asset files: %s', didWrite.join(', ')) - 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') @@ -140,62 +152,6 @@ async function writeReadme(data: PackageData, options: InjectOptions) { return true } -async function writeEslintrc(options: InjectOptions) { - if (!options.flags.eslint) { - return false - } - const {basePath} = options - - const eslintrc = path.join(basePath, '.eslintrc') - - const config = { - root: true, - env: { - node: true, - browser: true, - }, - extends: [ - 'sanity', - options.flags.typescript && 'sanity/typescript', - 'sanity/react', - 'plugin:react-hooks/recommended', - options.flags.prettier && 'plugin:prettier/recommended', - ].filter(Boolean), - } - - const content = JSON.stringify(config, null, 2) + '\n' - await writeFileWithOverwritePrompt(eslintrc, content, { - encoding: 'utf8', - force: options.flags.force, - }) - return true -} - -async function writeEslintIgnore(options: InjectOptions) { - if (!options.flags.eslint) { - return false - } - const {basePath} = options - - const eslintignore = path.join(basePath, '.eslintignore') - - const content = - outdent` - .eslintrc.js - commitlint.config.js - lib - lint-staged.config.js - package.config.ts - ${options.flags.typescript ? '*.js' : ''} - `.trim() + '\n' - - await writeFileWithOverwritePrompt(eslintignore, content, { - encoding: 'utf8', - force: options.flags.force, - }) - return true -} - async function writeLicense( {license}: PackageData, options: InjectOptions, @@ -324,45 +280,63 @@ async function resolveProjectDescription(basePath: string, pkg: PackageJson | un } } -export async function writeAssets(files: FromTo[], {basePath, flags}: InjectOptions) { +export async function writeAssets(injectables: Injectable[], {basePath, flags}: InjectOptions) { const assetsDir = await findAssetsDir() const from = (...segments: string[]) => path.join(assetsDir, 'inject', ...segments) const to = (...segments: string[]) => path.join(basePath, ...segments) const writes: string[] = [] - for (const file of files) { - const fromPath = asArray(file.from) - const toPath = asArray(file.to) - if (await copyFileWithOverwritePrompt(from(...fromPath), to(...toPath), flags)) { + for (const injectable of injectables) { + if (injectable.type === 'copy') { + const fromPath = asArray(injectable.from) + const toPath = asArray(injectable.to) + if (await copyFileWithOverwritePrompt(from(...fromPath), to(...toPath), flags)) { + writes.push(path.join(...toPath)) + } + continue + } + + if (injectable.type === 'template') { + const toPath = asArray(injectable.to) + + await writeFileWithOverwritePrompt(to(...toPath), `${injectable.value.trim()}\n`, { + default: 'n', + force: injectable.force || flags.force, + }) + writes.push(path.join(...toPath)) + continue } + + throw new Error(`Unknown operation type "${(injectable as any).type}"`) } return writes } async function writeStaticAssets(options: InjectOptions) { - const {flags} = options - - const files: FromTo[] = [ - {from: 'editorconfig', to: '.editorconfig'}, - {from: 'sanity.json', to: 'sanity.json'}, - {from: 'v2-incompatible.js.template', to: 'v2-incompatible.js'}, - { - from: 'package.config.template', - to: options.flags.typescript ? 'package.config.ts' : 'package.config.js', - }, - flags.gitignore && {from: 'gitignore', to: '.gitignore'}, - flags.typescript && {from: 'template-tsconfig.json', to: 'tsconfig.json'}, - flags.typescript && {from: 'template-tsconfig.lib.json', to: 'tsconfig.lib.json'}, - flags.typescript && {from: 'template-tsconfig.settings.json', to: 'tsconfig.settings.json'}, - flags.prettier && {from: 'prettierrc.json', to: '.prettierrc.json'}, + const {outDir, flags} = options + + const files: Injectable[] = [ + flags.eslint && eslintrcTemplate({flags: options.flags}), + flags.eslint && eslintignoreTemplate({outDir, flags: options.flags}), + {type: 'copy', from: 'editorconfig', to: '.editorconfig'}, + {type: 'copy', from: 'sanity.json', to: 'sanity.json'}, + {type: 'copy', from: 'v2-incompatible.js.template', to: 'v2-incompatible.js'}, + pkgConfigTemplate({outDir, flags: options.flags}), + flags.gitignore && gitignoreTemplate(), + flags.typescript && tsconfigTemplate({flags: options.flags}), + flags.typescript && tsconfigTemplateDist({outDir, flags: options.flags}), + flags.typescript && tsconfigTemplateSettings({flags: options.flags}), + flags.prettier && {type: 'copy', from: 'prettierrc.json', to: '.prettierrc'}, ] - .map((f) => (f ? (f as FromTo) : undefined)) - .filter((f): f is FromTo => !!f) + .map((f) => (f ? (f as Injectable) : undefined)) + .filter((f): f is Injectable => !!f) + + const result = writeAssets(files, options) - return writeAssets(files, options) + return result } function asArray(input: string | string[]): string[] { diff --git a/src/actions/link-watch.ts b/src/actions/link-watch.ts index 69435c8b..02b4e9c5 100644 --- a/src/actions/link-watch.ts +++ b/src/actions/link-watch.ts @@ -26,6 +26,8 @@ import log from '../util/log' import {getPackage} from '../npm/package' import outdent from 'outdent' import {fileExists, mkdir} from '../util/files' +import {loadConfig as loadPackageConfig} from '@sanity/pkg-utils' +import {defaultOutDir} from '../constants' interface YalcWatchConfig { folder?: string @@ -42,8 +44,10 @@ export async function linkWatch({basePath}: {basePath: string}) { fs.readFileSync(path.join(basePath, 'package.json'), 'utf8') ) + const packageConfig = await loadPackageConfig({cwd: basePath}) + const watch: Required = { - folder: 'lib', + folder: packageConfig?.dist ?? defaultOutDir, command: 'npm run watch', extensions: 'ts,js,png,svg,gif,jpeg,css', ...packageJson.sanityPlugin?.linkWatch, diff --git a/src/actions/verify-package.ts b/src/actions/verify-package.ts index 0d253815..3e7d83a3 100644 --- a/src/actions/verify-package.ts +++ b/src/actions/verify-package.ts @@ -1,7 +1,7 @@ import {loadConfig as loadPackageConfig} from '@sanity/pkg-utils' import {getPackage} from '../npm/package' import log from '../util/log' -import {cliName, urls} from '../constants' +import {cliName, defaultOutDir, urls} from '../constants' import {validateImports} from '../dependencies/import-linter' import outdent from 'outdent' import { @@ -33,6 +33,7 @@ export async function verifyPackage({basePath, flags}: {basePath: string; flags: const packageJson: PackageJson = await getPackage({basePath, validate: false}) const verifyConfig: VerifyPackageConfig = packageJson.sanityPlugin?.verifyPackage || {} const packageConfig = await loadPackageConfig({cwd: basePath}) + const outDir = packageConfig?.dist ?? defaultOutDir const tsconfig = packageConfig?.tsconfig ?? 'tsconfig.json' const validation = createValidator(verifyConfig, flags, errors) @@ -43,11 +44,11 @@ export async function verifyPackage({basePath, flags}: {basePath: string; flags: await validation('pkg-utils', async () => validatePkgUtilsDependency(packageJson)) await validation('srcIndex', async () => validateSrcIndexFile(basePath)) await validation('scripts', async () => validateScripts(packageJson)) - await validation('module', async () => validateModule(packageJson)) + await validation('module', async () => validateModule(packageJson, {outDir})) await validation('nodeEngine', async () => validateNodeEngine(packageJson)) if (ts) { - await validation('tsconfig', async () => validateTsConfig(ts, {basePath})) + await validation('tsconfig', async () => validateTsConfig(ts, {basePath, outDir, tsconfig})) } await validation('sanityV2Json', async () => validatePluginSanityJson({basePath, packageJson})) diff --git a/src/actions/verify/validations.ts b/src/actions/verify/validations.ts index efedad69..964a4047 100644 --- a/src/actions/verify/validations.ts +++ b/src/actions/verify/validations.ts @@ -20,20 +20,6 @@ export const expectedScripts = { } const expectedModulesFields = ['source', 'exports', 'main', 'module', 'files'] -const expectedCompilerOptions = { - jsx: 'preserve', - moduleResolution: 'node', - target: 'esnext', - module: 'esnext', - esModuleInterop: true, - skipLibCheck: true, - isolatedModules: true, - downlevelIteration: true, - declaration: true, - allowSyntheticDefaultImports: true, - rootDir: '.', - outDir: 'lib', -} export function validateNodeEngine(packageJson: PackageJson) { const nodeVersionRange = '>=14' @@ -52,7 +38,8 @@ export function validateNodeEngine(packageJson: PackageJson) { } } -export function validateModule(packageJson: PackageJson): string[] { +export function validateModule(packageJson: PackageJson, options: {outDir: string}): string[] { + const {outDir} = options const errors: string[] = [] const missingFields = expectedModulesFields.filter((field) => !packageJson[field]) @@ -67,24 +54,24 @@ export function validateModule(packageJson: PackageJson): string[] { Example: Given a plugin with entry-point in src/index.ts, using default @sanity/pkg-utils build command, - package.json should contain the following entries to ensure that commonjs and esm outputs are built into lib: + package.json should contain the following entries to ensure that commonjs and esm outputs are built into ${outDir}: "source": "./src/index.ts", "exports": { ".": { - "types": "./lib/index.d.ts", + "types": "./${outDir}/index.d.ts", "source": "./src/index.ts", - "import": "./lib/index.esm.js", - "require": "./lib/index.js", - "default": "./lib/index.js" + "import": "./${outDir}/index.esm.js", + "require": "./${outDir}/index.js", + "default": "./${outDir}/index.js" } }, - "main": "./lib/index.js", - "module": "./lib/index.esm.js", - "types": "./lib/index.d.ts", + "main": "./${outDir}/index.js", + "module": "./${outDir}/index.esm.js", + "types": "./${outDir}/index.d.ts", "files": [ - "src", - "lib" + "${outDir}", + "src" ], Refer to @sanity/pkg-utils for more: https://github.com/sanity-io/pkg-utils#sanitypkg-utils @@ -125,18 +112,38 @@ export function validateScripts(packageJson: PackageJson): string[] { return errors } -export async function validateTsConfig(ts: ParsedCommandLine, options: {basePath: string}) { +export async function validateTsConfig( + ts: ParsedCommandLine, + options: {basePath: string; outDir: string; tsconfig: string} +) { + const {basePath, outDir, tsconfig} = options + const errors: string[] = [] + const expectedCompilerOptions = { + jsx: 'preserve', + moduleResolution: 'node', + target: 'esnext', + module: 'esnext', + esModuleInterop: true, + skipLibCheck: true, + isolatedModules: true, + downlevelIteration: true, + declaration: true, + allowSyntheticDefaultImports: true, + rootDir: '.', + outDir, + } + const wrongEntries = Object.entries(expectedCompilerOptions).filter(([key, value]) => { let option: any = ts.options[key] if (key === 'rootDir' && typeof option === 'string') { - option = path.relative(options.basePath, option) || '.' + option = path.relative(basePath, option) || '.' } if (key === 'outDir' && typeof option === 'string') { - option = path.relative(options.basePath, option) || '.' + option = path.relative(basePath, option) || '.' } if (key === 'jsx' && option === 1) { @@ -167,13 +174,13 @@ export async function validateTsConfig(ts: ParsedCommandLine, options: {basePath errors.push( outdent` - Recommended tsconfig.json compilerOptions missing: + Recommended ${tsconfig} compilerOptions missing: The following fields had unexpected values: [${wrongEntries.map(([key]) => key).join(', ')}] Expected to find these values: ${expectedOutput} - Please update your tsconfig.json accordingly. + Please update your ${tsconfig} accordingly. `.trimStart() ) } diff --git a/src/cmds/inject.ts b/src/cmds/inject.ts index dd56e9ac..4a66d575 100644 --- a/src/cmds/inject.ts +++ b/src/cmds/inject.ts @@ -1,10 +1,11 @@ +import {loadConfig as loadPackageConfig} from '@sanity/pkg-utils' 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 {cliName, defaultOutDir} from '../constants' import {presetHelpList} from '../presets/presets' const description = `Inject configuration into a Sanity plugin` @@ -51,6 +52,8 @@ Examples async function run({argv}: {argv: string[]}) { const cli = meow(help, {flags: initFlags, argv, description}) const basePath = path.resolve(cli.input[0] || process.cwd()) + const packageConfig = await loadPackageConfig({cwd: basePath}) + const outDir = packageConfig?.dist ?? defaultOutDir const {v3ConfigFile} = await findStudioV3Config(basePath) if (v3ConfigFile) { @@ -60,7 +63,7 @@ async function run({argv}: {argv: string[]}) { } log.info('Inject config into plugin in "%s"', basePath) - await inject({basePath, flags: cli.flags, validate: false}) + await inject({basePath, outDir, flags: cli.flags, validate: false}) log.info('Done!') } diff --git a/src/configs/eslint.ts b/src/configs/eslint.ts new file mode 100644 index 00000000..e9cb9945 --- /dev/null +++ b/src/configs/eslint.ts @@ -0,0 +1,50 @@ +import {InjectTemplate} from '../actions/inject' +import {InitFlags} from '../actions/init' + +export function eslintrcTemplate(options: {flags: InitFlags}): InjectTemplate { + const {flags} = options + + const eslintConfig = { + root: true, + env: { + node: true, + browser: true, + }, + extends: [ + 'sanity', + flags.typescript && 'sanity/typescript', + 'sanity/react', + 'plugin:react-hooks/recommended', + flags.prettier && 'plugin:prettier/recommended', + ].filter(Boolean), + } + + return { + type: 'template', + force: flags.force, + to: '.eslintrc', + value: JSON.stringify(eslintConfig, null, 2), + } +} + +export function eslintignoreTemplate(options: {flags: InitFlags; outDir: string}): InjectTemplate { + const {flags, outDir} = options + + const patterns = [ + '.eslintrc.js', + 'commitlint.config.js', + outDir, + 'lint-staged.config.js', + flags.typescript ? 'package.config.ts' : 'package.config.js', + flags.typescript ? '*.js' : '', + ].filter(Boolean) + + patterns.sort() + + return { + type: 'template', + force: flags.force, + to: '.eslintignore', + value: patterns.join('\n'), + } +} diff --git a/src/configs/git.ts b/src/configs/git.ts new file mode 100644 index 00000000..9b689dd7 --- /dev/null +++ b/src/configs/git.ts @@ -0,0 +1,68 @@ +import {outdent} from 'outdent' +import {InjectTemplate} from '../actions/inject' + +export function gitignoreTemplate(): InjectTemplate { + return { + type: 'template', + to: '.gitignore', + value: outdent` + # 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 + + # Yalc + .yalc + yalc.lock + + # npm package zips + *.tgz + `, + } +} diff --git a/src/configs/pkg-config.ts b/src/configs/pkg-config.ts new file mode 100644 index 00000000..79f811b9 --- /dev/null +++ b/src/configs/pkg-config.ts @@ -0,0 +1,31 @@ +import {outdent} from 'outdent' +import {InjectTemplate} from '../actions/inject' +import {InitFlags} from '../actions/init' + +export function pkgConfigTemplate(options: {outDir: string; flags: InitFlags}): InjectTemplate { + const {flags, outDir} = options + + return { + type: 'template', + force: flags.force, + to: flags.typescript ? 'package.config.ts' : 'package.config.js', + value: outdent` + import {defineConfig} from '@sanity/pkg-utils' + + export default defineConfig({ + legacyExports: true, + tsconfig: 'tsconfig.${outDir}.json', + + // Remove this block to enable strict export validation + extract: { + rules: { + 'ae-forgotten-export': 'off', + 'ae-incompatible-release-tags': 'off', + 'ae-internal-missing-underscore': 'off', + 'ae-missing-release-tag': 'off', + }, + }, + }) + `, + } +} diff --git a/src/configs/tsconfig.ts b/src/configs/tsconfig.ts new file mode 100644 index 00000000..66f04b2a --- /dev/null +++ b/src/configs/tsconfig.ts @@ -0,0 +1,79 @@ +import {outdent} from 'outdent' +import {InjectTemplate} from '../actions/inject' +import {InitFlags} from '../actions/init' + +export function tsconfigTemplate(options: {flags: InitFlags}): InjectTemplate { + const {flags} = options + + return { + type: 'template', + force: flags.force, + to: 'tsconfig.json', + value: outdent` + { + "extends": "./tsconfig.settings", + "include": ["./src", "./package.config.ts"], + "compilerOptions": { + "rootDir": ".", + "jsx": "preserve", + "noEmit": true + } + } + `, + } +} + +export function tsconfigTemplateDist(options: {outDir: string; flags: InitFlags}): InjectTemplate { + const {flags, outDir} = options + + return { + type: 'template', + force: flags.force, + to: `tsconfig.${outDir}.json`, + value: outdent` + { + "extends": "./tsconfig.settings", + "include": ["./src"], + "exclude": [ + "./src/**/__fixtures__", + "./src/**/__mocks__", + "./src/**/*.test.ts", + "./src/**/*.test.tsx" + ], + "compilerOptions": { + "rootDir": ".", + "outDir": "./${outDir}", + "jsx": "preserve", + "emitDeclarationOnly": true + } + } + `, + } +} + +export function tsconfigTemplateSettings(options: {flags: InitFlags}): InjectTemplate { + const {flags} = options + + return { + type: 'template', + force: flags.force, + to: `tsconfig.settings.json`, + value: outdent` + { + "compilerOptions": { + "moduleResolution": "node", + "target": "esnext", + "module": "esnext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "esModuleInterop": true, + "strict": true, + "downlevelIteration": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "isolatedModules": true + } + } + `, + } +} diff --git a/src/constants.ts b/src/constants.ts index fcc2cb10..66be17b4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -11,3 +11,5 @@ export const urls = { } export const incompatiblePluginPackage = '@sanity/incompatible-plugin' + +export const defaultOutDir = 'lib' diff --git a/src/npm/package.ts b/src/npm/package.ts index 6618aaaf..7ce6bd50 100644 --- a/src/npm/package.ts +++ b/src/npm/package.ts @@ -205,7 +205,12 @@ function validateLockFiles(options: {basePath: string}) { 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 { + outDir, + peerDependencies: addPeers, + dependencies: addDeps, + devDependencies: addDevDeps, + } = options const {flags} = options const prev = prevPkg || {} @@ -272,6 +277,11 @@ export async function writePackageJson(data: PackageData, options: InjectOptions const source = flags.typescript ? './src/index.ts' : './src/index.js' + const files = [outDir, 'sanity.json', 'src', 'v2-incompatible.js'] + + // sort alphabetically for scanability + files.sort() + // order should be compatible with prettier-plugin-packagejson const forcedOrder = { name: pluginName, @@ -284,19 +294,19 @@ export async function writePackageJson(data: PackageData, options: InjectOptions author: user?.email ? `${user.name} <${user.email}>` : user?.name, exports: { '.': { - ...(flags.typescript ? {types: './lib/index.d.ts'} : {}), + ...(flags.typescript ? {types: `./${outDir}/index.d.ts`} : {}), source, - import: './lib/index.esm.js', - require: './lib/index.js', - default: './lib/index.esm.js', + import: `./${outDir}/index.esm.js`, + require: `./${outDir}/index.js`, + default: `./${outDir}/index.esm.js`, }, './package.json': './package.json', }, - main: './lib/index.js', - module: './lib/index.esm.js', + main: `./${outDir}/index.js`, + module: `./${outDir}/index.esm.js`, source, - ...(flags.typescript ? {types: './lib/index.d.ts'} : {}), - files: ['src', 'lib', 'v2-incompatible.js', 'sanity.json'], + ...(flags.typescript ? {types: `./${outDir}/index.d.ts`} : {}), + files, scripts: {...prev.scripts}, dependencies: sortKeys(dependencies), devDependencies: sortKeys(devDependencies), @@ -384,12 +394,14 @@ export async function writePackageJsonDirect(manifest: PackageJson, {basePath}: } export async function addBuildScripts(manifest: PackageJson, options: InjectOptions) { + const {outDir} = options + if (!options.flags.scripts) { return false } return addPackageJsonScripts(manifest, options, (scripts) => { scripts.build = addScript(expectedScripts.build, scripts.build) - scripts.clean = addScript(`rimraf lib`, scripts.clean) + scripts.clean = addScript(`rimraf ${outDir}`, scripts.clean) scripts['link-watch'] = addScript(expectedScripts['link-watch'], scripts['link-watch']) scripts.lint = addScript(`eslint .`, scripts.lint) scripts.prepublishOnly = addScript(expectedScripts.prepublishOnly, scripts.prepublishOnly) diff --git a/src/presets/renovatebot.ts b/src/presets/renovatebot.ts index 5e149870..38344425 100644 --- a/src/presets/renovatebot.ts +++ b/src/presets/renovatebot.ts @@ -8,5 +8,14 @@ export const renovatePreset: Preset = { } async function applyPreset(options: InjectOptions) { - await writeAssets([{from: ['renovatebot', 'renovate.json'], to: 'renovate.json'}], options) + await writeAssets( + [ + { + type: 'copy', + from: ['renovatebot', 'renovate.json'], + to: 'renovate.json', + }, + ], + options + ) } diff --git a/src/presets/semver-workflow.ts b/src/presets/semver-workflow.ts index c5fc15e1..5fda0375 100644 --- a/src/presets/semver-workflow.ts +++ b/src/presets/semver-workflow.ts @@ -1,4 +1,4 @@ -import {FromTo, InjectOptions, writeAssets} from '../actions/inject' +import {Injectable, InjectOptions, writeAssets} from '../actions/inject' import {resolveLatestVersions} from '../npm/resolveLatestVersions' import {Preset} from './presets' import { @@ -148,19 +148,31 @@ function closeEnough(a: string, b: string) { return isCloseEnough } -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: ['npmrc'], to: '.npmrc'}, - {from: ['commitlint.template.js'], to: 'commitlint.config.js'}, - {from: ['lint-staged.template.js'], to: 'lint-staged.config.js'}, - ].map((fromTo) => ({ - ...fromTo, - from: ['semver-workflow', ...fromTo.from], - })) +function semverWorkflowFiles(): Injectable[] { + const base: Injectable[] = [ + { + type: 'copy', + from: ['.github', 'workflows', 'main.yml'], + to: ['.github', 'workflows', 'main.yml'], + }, + {type: 'copy', from: ['.husky', 'commit-msg'], to: ['.husky', 'commit-msg']}, + {type: 'copy', from: ['.husky', 'pre-commit'], to: ['.husky', 'pre-commit']}, + {type: 'copy', from: ['.releaserc.json'], to: '.releaserc.json'}, + {type: 'copy', from: ['npmrc'], to: '.npmrc'}, + {type: 'copy', from: ['commitlint.template.js'], to: 'commitlint.config.js'}, + {type: 'copy', from: ['lint-staged.template.js'], to: 'lint-staged.config.js'}, + ] + + return base.map((fromTo) => { + if (fromTo.type === 'copy') { + return { + ...fromTo, + from: ['semver-workflow', ...fromTo.from], + } + } + + return fromTo + }) } async function semverWorkflowDependencies(): Promise> { diff --git a/src/presets/ui-workshop.ts b/src/presets/ui-workshop.ts index 644c5e1b..85e6c892 100644 --- a/src/presets/ui-workshop.ts +++ b/src/presets/ui-workshop.ts @@ -1,5 +1,5 @@ import {Preset} from './presets' -import {FromTo, InjectOptions, writeAssets} from '../actions/inject' +import {Injectable, InjectOptions, writeAssets} from '../actions/inject' import {getPackage, sortKeys, writePackageJsonDirect} from '../npm/package' import log from '../util/log' import chalk from 'chalk' @@ -32,16 +32,32 @@ async function applyPreset(options: InjectOptions) { ) } -function files(): FromTo[] { - return [ - {from: ['workshop.config.ts'], to: ['workshop.config.ts']}, - {from: ['src', 'CustomField.tsx'], to: ['src', 'CustomField.tsx']}, - {from: ['src', '__workshop__', 'index.tsx'], to: ['src', '__workshop__', 'index.tsx']}, - {from: ['src', '__workshop__', 'props.tsx'], to: ['src', '__workshop__', 'props.tsx']}, - ].map((fromTo) => ({ - ...fromTo, - from: ['ui-workshop', ...fromTo.from], - })) +function files(): Injectable[] { + const base: Injectable[] = [ + {type: 'copy', from: ['workshop.config.ts'], to: ['workshop.config.ts']}, + {type: 'copy', from: ['src', 'CustomField.tsx'], to: ['src', 'CustomField.tsx']}, + { + type: 'copy', + from: ['src', '__workshop__', 'index.tsx'], + to: ['src', '__workshop__', 'index.tsx'], + }, + { + type: 'copy', + from: ['src', '__workshop__', 'props.tsx'], + to: ['src', '__workshop__', 'props.tsx'], + }, + ] + + return base.map((fromTo) => { + if (fromTo.type === 'copy') { + return { + ...fromTo, + from: ['ui-workshop', ...fromTo.from], + } + } + + return fromTo + }) } async function updateGitIgnore(options: InjectOptions) { diff --git a/src/sanity/manifest.ts b/src/sanity/manifest.ts index d7145123..6b72bb82 100644 --- a/src/sanity/manifest.ts +++ b/src/sanity/manifest.ts @@ -233,7 +233,7 @@ async function validatePartFiles( return } - const [srcExists, libExists] = await Promise.all([ + const [srcExists, outDirExists] = await Promise.all([ hasSourceFile(part.path, paths), verifyCompiledParts && hasCompiledFile(part.path, paths), ]) @@ -246,7 +246,7 @@ async function validatePartFiles( ) } - if (verifyCompiledParts && !libExists) { + if (verifyCompiledParts && !outDirExists) { throw new Error( `Invalid sanity.json: Part path references file ("${part.path}") that does not exist in compiled directory (${paths?.compiled}) (parts[${index}])` ) diff --git a/tap-snapshots/test/verify-package.test.ts.test.cjs b/tap-snapshots/test/verify-package.test.ts.test.cjs index b539bdbc..91b497ce 100644 --- a/tap-snapshots/test/verify-package.test.ts.test.cjs +++ b/tap-snapshots/test/verify-package.test.ts.test.cjs @@ -92,8 +92,8 @@ package.json should contain the following entries to ensure that commonjs and es "module": "./lib/index.esm.js", "types": "./lib/index.d.ts", "files": [ - "src", - "lib" + "lib", + "src" ], Refer to @sanity/pkg-utils for more: https://github.com/sanity-io/pkg-utils#sanitypkg-utils diff --git a/test/fixtures/inject/valid/package.json b/test/fixtures/inject/valid/package.json index 9a36f927..384a8cdd 100644 --- a/test/fixtures/inject/valid/package.json +++ b/test/fixtures/inject/valid/package.json @@ -27,10 +27,10 @@ "source": "./src/index.ts", "types": "./lib/index.d.ts", "files": [ - "src", "lib", - "v2-incompatible.js", - "sanity.json" + "sanity.json", + "src", + "v2-incompatible.js" ], "scripts": { "build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict", diff --git a/test/fixtures/verify-package/every-failure-possible/.gitignore b/test/fixtures/verify-package/every-failure-possible/.gitignore index b9554332..9fd81937 100644 --- a/test/fixtures/verify-package/every-failure-possible/.gitignore +++ b/test/fixtures/verify-package/every-failure-possible/.gitignore @@ -51,4 +51,3 @@ jspm_packages # Compiled plugin lib - diff --git a/test/fixtures/verify-package/every-failure-possible/package.json b/test/fixtures/verify-package/every-failure-possible/package.json index a7253f86..4dc27f6d 100644 --- a/test/fixtures/verify-package/every-failure-possible/package.json +++ b/test/fixtures/verify-package/every-failure-possible/package.json @@ -4,9 +4,9 @@ "description": "", "license": "MIT", "files": [ - "src", "lib", - "sanity.json" + "sanity.json", + "src" ], "dependencies": {}, "devDependencies": { diff --git a/test/fixtures/verify-package/invalid-eslint/.gitignore b/test/fixtures/verify-package/invalid-eslint/.gitignore index b9554332..9fd81937 100644 --- a/test/fixtures/verify-package/invalid-eslint/.gitignore +++ b/test/fixtures/verify-package/invalid-eslint/.gitignore @@ -51,4 +51,3 @@ jspm_packages # Compiled plugin lib - diff --git a/test/fixtures/verify-package/invalid-eslint/package.json b/test/fixtures/verify-package/invalid-eslint/package.json index 7a03f2d6..2c49e2b2 100644 --- a/test/fixtures/verify-package/invalid-eslint/package.json +++ b/test/fixtures/verify-package/invalid-eslint/package.json @@ -23,10 +23,10 @@ "source": "./src/index.ts", "types": "./lib/types/index.d.ts", "files": [ - "src", "lib", - "v2-incompatible.js", - "sanity.json" + "sanity.json", + "src", + "v2-incompatible.js" ], "scripts": { "build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict", diff --git a/test/fixtures/verify-package/valid/.gitignore b/test/fixtures/verify-package/valid/.gitignore index b9554332..9fd81937 100644 --- a/test/fixtures/verify-package/valid/.gitignore +++ b/test/fixtures/verify-package/valid/.gitignore @@ -51,4 +51,3 @@ jspm_packages # Compiled plugin lib - diff --git a/test/fixtures/verify-package/valid/package.config.ts b/test/fixtures/verify-package/valid/package.config.ts new file mode 100644 index 00000000..9f84f013 --- /dev/null +++ b/test/fixtures/verify-package/valid/package.config.ts @@ -0,0 +1,7 @@ +import {defineConfig} from '@sanity/pkg-utils' + +export default defineConfig({ + dist: 'lib', + minify: false, + legacyExports: true +}) diff --git a/test/init.test.ts b/test/init.test.ts index 369b9b93..a813dd11 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -99,7 +99,7 @@ tap.test('plugin-kit init --force in empty directory', async (t) => { main: './lib/index.js', module: './lib/index.esm.js', types: './lib/index.d.ts', - files: ['src', 'lib', 'v2-incompatible.js', 'sanity.json'], + files: ['lib', 'sanity.json', 'src', 'v2-incompatible.js'], scripts: { clean: 'rimraf lib', lint: 'eslint .',