@sanity/plugin-kit 4.0.19 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/inject/semver-workflow/.husky/commit-msg +0 -0
- package/assets/inject/semver-workflow/.husky/pre-commit +0 -0
- package/assets/inject/ui-workshop/src/__workshop__/props.tsx +2 -1
- package/bin/plugin-kit.js +3 -1
- package/dist/{_chunks-cjs/cli.js → _chunks-es/index.js} +53 -63
- package/dist/_chunks-es/index.js.map +1 -0
- package/dist/{_chunks-cjs/init2.js → _chunks-es/init.js} +25 -23
- package/dist/_chunks-es/init.js.map +1 -0
- package/dist/_chunks-es/init2.js +140 -0
- package/dist/_chunks-es/init2.js.map +1 -0
- package/{src/cmds/inject.ts → dist/_chunks-es/inject.js} +18 -32
- package/dist/{_chunks-cjs → _chunks-es}/inject.js.map +1 -1
- package/dist/_chunks-es/link-watch.js +91 -0
- package/dist/_chunks-es/link-watch.js.map +1 -0
- package/dist/_chunks-es/load-package-config.js +22 -0
- package/dist/_chunks-es/load-package-config.js.map +1 -0
- package/dist/_chunks-es/package.js +1759 -0
- package/dist/_chunks-es/package.js.map +1 -0
- package/dist/_chunks-es/package2.js +9 -0
- package/dist/_chunks-es/package2.js.map +1 -0
- package/dist/{_chunks-cjs → _chunks-es}/ts.js +101 -92
- package/dist/_chunks-es/ts.js.map +1 -0
- package/dist/_chunks-es/verify-package.js +92 -0
- package/dist/_chunks-es/verify-package.js.map +1 -0
- package/dist/_chunks-es/verify-studio.js +61 -0
- package/dist/_chunks-es/verify-studio.js.map +1 -0
- package/dist/_chunks-es/version.js +50 -0
- package/dist/_chunks-es/version.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +41 -88
- package/LICENSE +0 -21
- package/dist/_chunks-cjs/cli.js.map +0 -1
- package/dist/_chunks-cjs/init.js +0 -894
- package/dist/_chunks-cjs/init.js.map +0 -1
- package/dist/_chunks-cjs/init2.js.map +0 -1
- package/dist/_chunks-cjs/inject.js +0 -54
- package/dist/_chunks-cjs/link-watch.js +0 -84
- package/dist/_chunks-cjs/link-watch.js.map +0 -1
- package/dist/_chunks-cjs/package.js +0 -1809
- package/dist/_chunks-cjs/package.js.map +0 -1
- package/dist/_chunks-cjs/package2.js +0 -146
- package/dist/_chunks-cjs/package2.js.map +0 -1
- package/dist/_chunks-cjs/ts.js.map +0 -1
- package/dist/_chunks-cjs/verify-package.js +0 -75
- package/dist/_chunks-cjs/verify-package.js.map +0 -1
- package/dist/_chunks-cjs/verify-studio.js +0 -57
- package/dist/_chunks-cjs/verify-studio.js.map +0 -1
- package/dist/_chunks-cjs/version.js +0 -51
- package/dist/_chunks-cjs/version.js.map +0 -1
- package/dist/cli.d.ts +0 -4
- package/dist/cli.js +0 -6
- package/dist/cli.js.map +0 -1
- package/src/actions/init.ts +0 -95
- package/src/actions/inject.ts +0 -399
- package/src/actions/link-watch.ts +0 -98
- package/src/actions/verify/types.ts +0 -56
- package/src/actions/verify/validations.ts +0 -505
- package/src/actions/verify/verify-common.ts +0 -93
- package/src/actions/verify-package.ts +0 -103
- package/src/actions/verify-studio.ts +0 -58
- package/src/cli.ts +0 -77
- package/src/cmds/index.ts +0 -20
- package/src/cmds/init.ts +0 -90
- package/src/cmds/link-watch.ts +0 -50
- package/src/cmds/verify-package.ts +0 -36
- package/src/cmds/verify-studio.ts +0 -36
- package/src/cmds/version.ts +0 -67
- package/src/configs/banned-packages.ts +0 -27
- package/src/configs/buildExtensions.ts +0 -1
- package/src/configs/default-source.ts +0 -64
- package/src/configs/eslint.ts +0 -51
- package/src/configs/forced-package-versions.ts +0 -12
- package/src/configs/git.ts +0 -68
- package/src/configs/pkg-config.ts +0 -31
- package/src/configs/prettier.ts +0 -11
- package/src/configs/tsconfig.ts +0 -78
- package/src/configs/uselessFiles.ts +0 -29
- package/src/constants.ts +0 -15
- package/src/dependencies/find.ts +0 -193
- package/src/dependencies/import-linter.ts +0 -95
- package/src/index.ts +0 -1
- package/src/npm/manager.ts +0 -44
- package/src/npm/package.ts +0 -427
- package/src/npm/publish.ts +0 -9
- package/src/npm/resolveLatestVersions.ts +0 -31
- package/src/presets/presets.ts +0 -54
- package/src/presets/renovatebot.ts +0 -21
- package/src/presets/semver-workflow.ts +0 -193
- package/src/presets/ui-workshop.ts +0 -97
- package/src/presets/ui.ts +0 -67
- package/src/sanity/manifest.ts +0 -340
- package/src/sharedFlags.ts +0 -14
- package/src/util/command-parser.ts +0 -36
- package/src/util/errorToUndefined.ts +0 -7
- package/src/util/files.ts +0 -260
- package/src/util/log.ts +0 -44
- package/src/util/prompt.ts +0 -70
- package/src/util/readme.ts +0 -93
- package/src/util/request.ts +0 -11
- package/src/util/ts.ts +0 -13
- package/src/util/user.ts +0 -119
|
@@ -0,0 +1,1759 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import util from "util";
|
|
4
|
+
import githubUrlToObject from "github-url-to-object";
|
|
5
|
+
import validateNpmPackageName from "validate-npm-package-name";
|
|
6
|
+
import { URL, fileURLToPath } from "url";
|
|
7
|
+
import licenses from "@rexxars/choosealicense-list";
|
|
8
|
+
import gitRemoteOriginUrl from "git-remote-origin-url";
|
|
9
|
+
import { log, urls, minPkgUtilsMajor, requiredNodeEngine, incompatiblePluginPackage, cliName } from "./index.js";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import outdent$1, { outdent } from "outdent";
|
|
13
|
+
import crypto from "crypto";
|
|
14
|
+
import json5 from "json5";
|
|
15
|
+
import pAny from "p-any";
|
|
16
|
+
import { pkg } from "./package2.js";
|
|
17
|
+
import inquirer from "inquirer";
|
|
18
|
+
import getLatestVersion from "get-latest-version";
|
|
19
|
+
import pProps from "p-props";
|
|
20
|
+
import { getIt } from "get-it";
|
|
21
|
+
import { promise, jsonRequest, jsonResponse, httpErrors, headers } from "get-it/middleware";
|
|
22
|
+
import { execSync } from "child_process";
|
|
23
|
+
import { validate } from "email-validator";
|
|
24
|
+
import xdgBasedir from "xdg-basedir";
|
|
25
|
+
function eslintrcTemplate(options) {
|
|
26
|
+
const { flags } = options, eslintConfig = {
|
|
27
|
+
root: !0,
|
|
28
|
+
env: {
|
|
29
|
+
node: !0,
|
|
30
|
+
browser: !0
|
|
31
|
+
},
|
|
32
|
+
extends: [
|
|
33
|
+
"sanity",
|
|
34
|
+
flags.typescript && "sanity/typescript",
|
|
35
|
+
"sanity/react",
|
|
36
|
+
"plugin:react-hooks/recommended",
|
|
37
|
+
flags.prettier && "plugin:prettier/recommended",
|
|
38
|
+
"plugin:react/jsx-runtime"
|
|
39
|
+
].filter(Boolean)
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
type: "template",
|
|
43
|
+
force: flags.force,
|
|
44
|
+
to: ".eslintrc",
|
|
45
|
+
value: JSON.stringify(eslintConfig, null, 2)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function eslintignoreTemplate(options) {
|
|
49
|
+
const { flags, outDir } = options, patterns = [
|
|
50
|
+
".eslintrc.js",
|
|
51
|
+
"commitlint.config.js",
|
|
52
|
+
outDir,
|
|
53
|
+
"lint-staged.config.js",
|
|
54
|
+
"package.config.ts",
|
|
55
|
+
flags.typescript ? "*.js" : ""
|
|
56
|
+
].filter(Boolean);
|
|
57
|
+
return patterns.sort(), {
|
|
58
|
+
type: "template",
|
|
59
|
+
force: flags.force,
|
|
60
|
+
to: ".eslintignore",
|
|
61
|
+
value: patterns.join(`
|
|
62
|
+
`)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function gitignoreTemplate() {
|
|
66
|
+
return {
|
|
67
|
+
type: "template",
|
|
68
|
+
to: ".gitignore",
|
|
69
|
+
value: outdent`
|
|
70
|
+
# Logs
|
|
71
|
+
logs
|
|
72
|
+
*.log
|
|
73
|
+
npm-debug.log*
|
|
74
|
+
|
|
75
|
+
# Runtime data
|
|
76
|
+
pids
|
|
77
|
+
*.pid
|
|
78
|
+
*.seed
|
|
79
|
+
|
|
80
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
81
|
+
lib-cov
|
|
82
|
+
|
|
83
|
+
# Coverage directory used by tools like istanbul
|
|
84
|
+
coverage
|
|
85
|
+
|
|
86
|
+
# nyc test coverage
|
|
87
|
+
.nyc_output
|
|
88
|
+
|
|
89
|
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
90
|
+
.grunt
|
|
91
|
+
|
|
92
|
+
# node-waf configuration
|
|
93
|
+
.lock-wscript
|
|
94
|
+
|
|
95
|
+
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
96
|
+
build/Release
|
|
97
|
+
|
|
98
|
+
# Dependency directories
|
|
99
|
+
node_modules
|
|
100
|
+
jspm_packages
|
|
101
|
+
|
|
102
|
+
# Optional npm cache directory
|
|
103
|
+
.npm
|
|
104
|
+
|
|
105
|
+
# Optional REPL history
|
|
106
|
+
.node_repl_history
|
|
107
|
+
|
|
108
|
+
# macOS finder cache file
|
|
109
|
+
.DS_Store
|
|
110
|
+
|
|
111
|
+
# VS Code settings
|
|
112
|
+
.vscode
|
|
113
|
+
|
|
114
|
+
# IntelliJ
|
|
115
|
+
.idea
|
|
116
|
+
*.iml
|
|
117
|
+
|
|
118
|
+
# Cache
|
|
119
|
+
.cache
|
|
120
|
+
|
|
121
|
+
# Yalc
|
|
122
|
+
.yalc
|
|
123
|
+
yalc.lock
|
|
124
|
+
|
|
125
|
+
# npm package zips
|
|
126
|
+
*.tgz
|
|
127
|
+
`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function pkgConfigTemplate(options) {
|
|
131
|
+
const { flags, outDir } = options;
|
|
132
|
+
return {
|
|
133
|
+
type: "template",
|
|
134
|
+
force: flags.force,
|
|
135
|
+
// Always a `.ts` config: plugins are ESM (`"type": "module"`), so `@sanity/pkg-utils`
|
|
136
|
+
// loads it without needing a `.mts`/`.mjs` extension to force ESM interpretation.
|
|
137
|
+
to: "package.config.ts",
|
|
138
|
+
value: outdent`
|
|
139
|
+
import {defineConfig} from '@sanity/pkg-utils'
|
|
140
|
+
|
|
141
|
+
export default defineConfig({
|
|
142
|
+
dist: '${outDir}',
|
|
143
|
+
tsconfig: 'tsconfig.${outDir}.json',
|
|
144
|
+
|
|
145
|
+
// Remove this block to enable strict export validation
|
|
146
|
+
extract: {
|
|
147
|
+
rules: {
|
|
148
|
+
'ae-incompatible-release-tags': 'off',
|
|
149
|
+
'ae-internal-missing-underscore': 'off',
|
|
150
|
+
'ae-missing-release-tag': 'off',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function prettierignoreTemplate(options) {
|
|
158
|
+
const { outDir } = options;
|
|
159
|
+
return {
|
|
160
|
+
type: "template",
|
|
161
|
+
to: ".prettierignore",
|
|
162
|
+
value: [outDir, "pnpm-lock.yaml", "yarn.lock", "package-lock.json"].join(`
|
|
163
|
+
`)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function tsconfigTemplate(options) {
|
|
167
|
+
const { flags } = options;
|
|
168
|
+
return {
|
|
169
|
+
type: "template",
|
|
170
|
+
force: flags.force,
|
|
171
|
+
to: "tsconfig.json",
|
|
172
|
+
value: outdent`
|
|
173
|
+
{
|
|
174
|
+
"extends": "./tsconfig.settings",
|
|
175
|
+
"include": ["./src", "./package.config.ts"]
|
|
176
|
+
}
|
|
177
|
+
`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function tsconfigTemplateDist(options) {
|
|
181
|
+
const { flags, outDir } = options;
|
|
182
|
+
return {
|
|
183
|
+
type: "template",
|
|
184
|
+
force: flags.force,
|
|
185
|
+
to: `tsconfig.${outDir}.json`,
|
|
186
|
+
value: outdent`
|
|
187
|
+
{
|
|
188
|
+
"extends": "./tsconfig.settings",
|
|
189
|
+
"include": ["./src"],
|
|
190
|
+
"exclude": [
|
|
191
|
+
"./src/**/__fixtures__",
|
|
192
|
+
"./src/**/__mocks__",
|
|
193
|
+
"./src/**/*.test.ts",
|
|
194
|
+
"./src/**/*.test.tsx"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function tsconfigTemplateSettings(options) {
|
|
201
|
+
const { flags, outDir } = options;
|
|
202
|
+
return {
|
|
203
|
+
type: "template",
|
|
204
|
+
force: flags.force,
|
|
205
|
+
to: "tsconfig.settings.json",
|
|
206
|
+
value: outdent`
|
|
207
|
+
{
|
|
208
|
+
"compilerOptions": {
|
|
209
|
+
"rootDir": ".",
|
|
210
|
+
"outDir": "./${outDir}",
|
|
211
|
+
|
|
212
|
+
"target": "esnext",
|
|
213
|
+
"jsx": "preserve",
|
|
214
|
+
"module": "preserve",
|
|
215
|
+
"moduleResolution": "bundler",
|
|
216
|
+
"esModuleInterop": true,
|
|
217
|
+
"resolveJsonModule": true,
|
|
218
|
+
"moduleDetection": "force",
|
|
219
|
+
"strict": true,
|
|
220
|
+
"allowSyntheticDefaultImports": true,
|
|
221
|
+
"skipLibCheck": true,
|
|
222
|
+
"forceConsistentCasingInFileNames": true,
|
|
223
|
+
"isolatedModules": true,
|
|
224
|
+
|
|
225
|
+
// Don't emit by default, pkg-utils will ignore this when generating .d.ts files
|
|
226
|
+
"noEmit": true
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const renovatePreset = {
|
|
233
|
+
name: "renovatebot",
|
|
234
|
+
description: "Files to enable renovatebot.",
|
|
235
|
+
apply: applyPreset$3
|
|
236
|
+
};
|
|
237
|
+
async function applyPreset$3(options) {
|
|
238
|
+
await writeAssets(
|
|
239
|
+
[
|
|
240
|
+
{
|
|
241
|
+
type: "copy",
|
|
242
|
+
from: ["renovatebot", "renovate.json"],
|
|
243
|
+
to: "renovate.json"
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
options
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const lockedDependencies = {
|
|
250
|
+
"styled-components": "^6.1",
|
|
251
|
+
eslint: "^8.57.0"
|
|
252
|
+
};
|
|
253
|
+
function resolveLatestVersions(packages) {
|
|
254
|
+
const versions = {};
|
|
255
|
+
for (const pkgName of packages)
|
|
256
|
+
versions[pkgName] = pkgName in lockedDependencies ? lockedDependencies[pkgName] : "latest";
|
|
257
|
+
return pProps(
|
|
258
|
+
versions,
|
|
259
|
+
async (range, pkgName) => {
|
|
260
|
+
const version = await getLatestVersion(pkgName, { range });
|
|
261
|
+
if (!version)
|
|
262
|
+
throw new Error(`Found no version for ${pkgName}`);
|
|
263
|
+
return rangeify(version);
|
|
264
|
+
},
|
|
265
|
+
{ concurrency: 8 }
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
function rangeify(version) {
|
|
269
|
+
return `^${version}`;
|
|
270
|
+
}
|
|
271
|
+
function errorToUndefined(err) {
|
|
272
|
+
if (err instanceof TypeError)
|
|
273
|
+
throw err;
|
|
274
|
+
}
|
|
275
|
+
const buildExtensions = [".js", ".jsx", ".es6", ".es", ".mjs", ".ts", ".tsx"], stat$1 = util.promisify(fs.stat), readFile$2 = util.promisify(fs.readFile), allowedPartProps = ["name", "implements", "path", "description"], disallowedPluginProps = ["api", "project", "plugins", "env"];
|
|
276
|
+
async function getPaths(options) {
|
|
277
|
+
const { basePath } = options, manifest = await readManifest(options);
|
|
278
|
+
return manifest.paths ? absolutifyPaths(manifest.paths, basePath) : null;
|
|
279
|
+
}
|
|
280
|
+
function absolutifyPaths(paths, basePath) {
|
|
281
|
+
const getPath = (relative) => relative ? path.resolve(path.join(basePath, relative)) : void 0;
|
|
282
|
+
return paths ? {
|
|
283
|
+
basePath,
|
|
284
|
+
compiled: getPath(paths.compiled),
|
|
285
|
+
source: getPath(paths.source)
|
|
286
|
+
} : { basePath };
|
|
287
|
+
}
|
|
288
|
+
async function readManifest(options) {
|
|
289
|
+
const { basePath, validate: validate2 = !0 } = options, manifestPath = path.normalize(path.join(basePath, "sanity.json"));
|
|
290
|
+
let content;
|
|
291
|
+
try {
|
|
292
|
+
content = await readFile$2(manifestPath, "utf8");
|
|
293
|
+
} catch (err) {
|
|
294
|
+
throw err.code === "ENOENT" ? new Error(
|
|
295
|
+
`No sanity.json found. sanity.json is required for plugins to function. Use \`${pkg.binname} init\` for a new plugin, or create an empty \`sanity.json\` with an empty object (\`{}\`) for existing ones.`
|
|
296
|
+
) : new Error(`Failed to read "${manifestPath}": ${err.message}`);
|
|
297
|
+
}
|
|
298
|
+
let parsed;
|
|
299
|
+
try {
|
|
300
|
+
parsed = JSON.parse(content);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
throw new Error(`Error parsing "${manifestPath}": ${err.message}`);
|
|
303
|
+
}
|
|
304
|
+
return validate2 && await validateManifest(parsed, options), parsed;
|
|
305
|
+
}
|
|
306
|
+
async function validateManifest(manifest, opts) {
|
|
307
|
+
const options = { isPlugin: !0, ...opts };
|
|
308
|
+
if (!isObject$1(manifest))
|
|
309
|
+
throw new Error("Invalid sanity.json: Root must be an object");
|
|
310
|
+
if (options.isPlugin ? await validatePluginManifest(manifest, options) : validateProjectManifest(manifest), "root" in manifest && typeof manifest.root != "boolean")
|
|
311
|
+
throw new Error('Invalid sanity.json: "root" property must be a boolean if declared');
|
|
312
|
+
await validateParts(manifest, {
|
|
313
|
+
...options,
|
|
314
|
+
paths: absolutifyPaths(manifest.paths, options.basePath)
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function validateProjectManifest(manifest) {
|
|
318
|
+
if ("paths" in manifest)
|
|
319
|
+
throw new Error('Invalid sanity.json: "paths" property has no meaning in a project manifest');
|
|
320
|
+
}
|
|
321
|
+
async function validatePluginManifest(manifest, options) {
|
|
322
|
+
const disallowed = Object.keys(manifest).filter((key) => disallowedPluginProps.includes(key)).map((key) => `"${key}"`);
|
|
323
|
+
if (disallowed.length > 0) {
|
|
324
|
+
const plural = disallowed.length > 1 ? "s" : "", joined = disallowed.join(", ");
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Invalid sanity.json: Key${plural} ${joined} ${plural ? "are" : "is"} not allowed in a plugin manifest`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (manifest.root)
|
|
330
|
+
throw new Error('Invalid sanity.json: "root" cannot be truthy in a plugin manifest');
|
|
331
|
+
await validatePaths$1(manifest, options);
|
|
332
|
+
}
|
|
333
|
+
async function validatePaths$1(manifest, options) {
|
|
334
|
+
if (!("paths" in manifest))
|
|
335
|
+
return;
|
|
336
|
+
if (!isObject$1(manifest.paths))
|
|
337
|
+
throw new Error('Invalid sanity.json: "paths" must be an object if declared');
|
|
338
|
+
if (typeof manifest.paths.compiled != "string")
|
|
339
|
+
throw new Error(
|
|
340
|
+
'Invalid sanity.json: "paths" must have a (string) "compiled" property if declared'
|
|
341
|
+
);
|
|
342
|
+
if (typeof manifest.paths.source != "string")
|
|
343
|
+
throw new Error(
|
|
344
|
+
'Invalid sanity.json: "paths" must have a (string) "source" property if declared'
|
|
345
|
+
);
|
|
346
|
+
const sourcePath = path.resolve(options.basePath, manifest.paths.source);
|
|
347
|
+
let srcStats;
|
|
348
|
+
try {
|
|
349
|
+
srcStats = await stat$1(sourcePath);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
if (err.code === "ENOENT")
|
|
352
|
+
throw new Error(`sanity.json references "source" path which does not exist: "${sourcePath}"`);
|
|
353
|
+
}
|
|
354
|
+
if (!srcStats?.isDirectory())
|
|
355
|
+
throw new Error(
|
|
356
|
+
`sanity.json references "source" path which is not a directory: "${sourcePath}"`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
async function validateParts(manifest, options) {
|
|
360
|
+
if (!("parts" in manifest))
|
|
361
|
+
return;
|
|
362
|
+
if (!Array.isArray(manifest.parts))
|
|
363
|
+
throw new Error('Invalid sanity.json: "parts" must be an array if declared');
|
|
364
|
+
let i = 0;
|
|
365
|
+
for (const part of manifest.parts)
|
|
366
|
+
await validatePart(part, i, options), i++;
|
|
367
|
+
}
|
|
368
|
+
async function validatePart(part, index, options) {
|
|
369
|
+
if (!isObject$1(part))
|
|
370
|
+
throw new Error(`Invalid sanity.json: "parts[${index}]" must be an object`);
|
|
371
|
+
validateAllowedPartKeys(part, index), validatePartStringValues(part, index), validatePartNames(part, index, options), await validatePartFiles(part, index, options);
|
|
372
|
+
}
|
|
373
|
+
async function validatePartFiles(part, index, options) {
|
|
374
|
+
const { verifyCompiledParts, verifySourceParts, paths } = options;
|
|
375
|
+
if (!part?.path)
|
|
376
|
+
return;
|
|
377
|
+
const ext = path.extname(part.path);
|
|
378
|
+
if (paths?.source && ext && ext !== ".js" && buildExtensions.includes(ext))
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Invalid sanity.json: Part path has extension which is not applicable after compiling. ${ext} becomes .js after compiling. Specify filename without extension (${path.basename(
|
|
381
|
+
part.path
|
|
382
|
+
)}) (parts[${index}])`
|
|
383
|
+
);
|
|
384
|
+
if (!verifySourceParts && !verifyCompiledParts)
|
|
385
|
+
return;
|
|
386
|
+
const [srcExists, outDirExists] = await Promise.all([
|
|
387
|
+
hasSourceFile(part.path, paths),
|
|
388
|
+
verifyCompiledParts && hasCompiledFile(part.path, paths)
|
|
389
|
+
]);
|
|
390
|
+
if (!srcExists)
|
|
391
|
+
throw new Error(
|
|
392
|
+
`Invalid sanity.json: Part path references file that does not exist in source directory (${paths?.source || paths?.basePath}) (parts[${index}])`
|
|
393
|
+
);
|
|
394
|
+
if (verifyCompiledParts && !outDirExists)
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Invalid sanity.json: Part path references file ("${part.path}") that does not exist in compiled directory (${paths?.compiled}) (parts[${index}])`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
function validatePartNames(part, index, options) {
|
|
400
|
+
const pluginName = options.pluginName ? options.pluginName.replace(/^sanity-plugin-/, "") : "";
|
|
401
|
+
if (!part?.name || !part?.name?.startsWith(`part:${pluginName}/`))
|
|
402
|
+
throw new Error(
|
|
403
|
+
`Invalid sanity.json: "name" must be prefixed with "part:${pluginName}/" - got "${part?.name}" (parts[${index}])`
|
|
404
|
+
);
|
|
405
|
+
if (!part?.implements?.startsWith("part:"))
|
|
406
|
+
throw new Error(
|
|
407
|
+
`Invalid sanity.json: "implements" must be prefixed with "part:" - got "${part?.implements}" (parts[${index}])`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
function validateAllowedPartKeys(part, index) {
|
|
411
|
+
const disallowed = Object.keys(part).filter((key) => !allowedPartProps.includes(key)).map((key) => `"${key}"`);
|
|
412
|
+
if (disallowed.length > 0) {
|
|
413
|
+
const plural = disallowed.length > 1 ? "s" : "", joined = disallowed.join(", ");
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Invalid sanity.json: Key${plural} ${joined} ${plural ? "are" : "is"} not allowed in a part declaration (parts[${index}])`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function validatePartStringValues(part, index) {
|
|
420
|
+
const nonStrings = Object.keys(part).filter((key) => typeof part[key] != "string").map((key) => `"${key}"`);
|
|
421
|
+
if (nonStrings.length > 0) {
|
|
422
|
+
const plural = nonStrings.length > 1 ? "s" : "", joined = nonStrings.join(", ");
|
|
423
|
+
throw new Error(
|
|
424
|
+
`Invalid sanity.json: Key${plural} ${joined} should be of type string (parts[${index}])`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function isObject$1(obj) {
|
|
429
|
+
return !Array.isArray(obj) && obj !== null && typeof obj == "object";
|
|
430
|
+
}
|
|
431
|
+
async function hasSanityJson(basePath) {
|
|
432
|
+
const file = await readJsonFile(path.join(basePath, "sanity.json")).catch(
|
|
433
|
+
errorToUndefined
|
|
434
|
+
);
|
|
435
|
+
return { exists: !!file, isRoot: !!(file && file.root) };
|
|
436
|
+
}
|
|
437
|
+
async function findStudioV3Config(basePath) {
|
|
438
|
+
const jsFile = "sanity.config.js";
|
|
439
|
+
if (await fileExists(path.join(basePath, jsFile)))
|
|
440
|
+
return { v3ConfigFile: jsFile };
|
|
441
|
+
const tsFile = "sanity.config.ts";
|
|
442
|
+
return { v3ConfigFile: await fileExists(path.join(basePath, tsFile)) ? tsFile : void 0 };
|
|
443
|
+
}
|
|
444
|
+
async function prompt(message, options) {
|
|
445
|
+
const type = options.choices ? "list" : options.type, result = await inquirer.prompt([{ ...options, type, message, name: "single" }]);
|
|
446
|
+
return result && result.single;
|
|
447
|
+
}
|
|
448
|
+
prompt.separator = () => new inquirer.Separator();
|
|
449
|
+
function promptForPackageName({ basePath }, defaultVal) {
|
|
450
|
+
return prompt("Plugin name (sanity-plugin-...)", {
|
|
451
|
+
default: defaultVal || path.basename(basePath),
|
|
452
|
+
filter: (name) => {
|
|
453
|
+
const prefixless = name.trim().replace(/^sanity-plugin-/, "");
|
|
454
|
+
return name[0] === "@" ? name : `sanity-plugin-${prefixless}`;
|
|
455
|
+
},
|
|
456
|
+
validate: (name) => {
|
|
457
|
+
const valid = validateNpmPackageName(name);
|
|
458
|
+
return valid.errors ? valid.errors[0] : name[0] !== "@" && name.endsWith("plugin") ? `Name shouldn't include "plugin" multiple times (${name})` : !0;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function promptForRepoOrigin(_options, defaultVal) {
|
|
463
|
+
return prompt("Git repository URL", {
|
|
464
|
+
default: defaultVal,
|
|
465
|
+
filter: (raw) => {
|
|
466
|
+
const url = (raw || "").trim(), gh = githubUrlToObject(url);
|
|
467
|
+
return gh ? `git+ssh://git@github.com/${gh.user}/${gh.repo}.git` : url;
|
|
468
|
+
},
|
|
469
|
+
validate: (url) => {
|
|
470
|
+
if (!url)
|
|
471
|
+
return !0;
|
|
472
|
+
try {
|
|
473
|
+
return new URL(url) ? !0 : "Invalid URL";
|
|
474
|
+
} catch {
|
|
475
|
+
return "Invalid URL";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const stat = util.promisify(fs.stat), mkdir = util.promisify(fs.mkdir), readdir = util.promisify(fs.readdir), copyFile = util.promisify(fs.copyFile), readFile$1 = util.promisify(fs.readFile), writeFile = util.promisify(fs.writeFile);
|
|
481
|
+
function hasSourceEquivalent(compiledFile, paths) {
|
|
482
|
+
if (!paths.source)
|
|
483
|
+
return fileExists(
|
|
484
|
+
path.isAbsolute(compiledFile) ? compiledFile : path.resolve(paths.basePath, compiledFile)
|
|
485
|
+
);
|
|
486
|
+
const baseDir = path.dirname(compiledFile.replace(paths.compiled, paths.source)), baseName = path.basename(compiledFile, path.extname(compiledFile)), pathStub = path.join(baseDir, baseName);
|
|
487
|
+
return buildCandidateExists(pathStub);
|
|
488
|
+
}
|
|
489
|
+
async function hasSourceFile(filePath, paths) {
|
|
490
|
+
if (!paths?.source)
|
|
491
|
+
return fileExists(
|
|
492
|
+
path.isAbsolute(filePath) ? filePath : path.resolve(paths?.basePath ?? "", filePath)
|
|
493
|
+
);
|
|
494
|
+
const pathStub = path.isAbsolute(filePath) ? filePath : path.resolve(paths.source, filePath);
|
|
495
|
+
return await fileExists(pathStub) ? !0 : buildCandidateExists(pathStub);
|
|
496
|
+
}
|
|
497
|
+
function hasCompiledFile(filePath, paths) {
|
|
498
|
+
if (!paths?.compiled)
|
|
499
|
+
return fileExists(
|
|
500
|
+
path.isAbsolute(filePath) ? filePath : path.resolve(paths?.basePath ?? "", filePath)
|
|
501
|
+
);
|
|
502
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(paths.compiled, filePath), withExt = path.extname(absPath) === "" ? `${absPath}.js` : absPath;
|
|
503
|
+
return fileExists(withExt);
|
|
504
|
+
}
|
|
505
|
+
function buildCandidateExists(pathStub) {
|
|
506
|
+
const candidates = buildExtensions.map((extCandidate) => `${pathStub}${extCandidate}`);
|
|
507
|
+
return pAny(candidates.map((candidate) => stat(candidate))).then(() => !0).catch(() => !1);
|
|
508
|
+
}
|
|
509
|
+
function fileExists(filePath) {
|
|
510
|
+
return stat(filePath).then(() => !0).catch(() => !1);
|
|
511
|
+
}
|
|
512
|
+
async function readJsonFile(filePath) {
|
|
513
|
+
const content = await readFile$1(filePath, "utf8");
|
|
514
|
+
return JSON.parse(content);
|
|
515
|
+
}
|
|
516
|
+
function writeJsonFile(filePath, content) {
|
|
517
|
+
const data = JSON.stringify(content, null, 2) + `
|
|
518
|
+
`;
|
|
519
|
+
return writeFile(filePath, data, { encoding: "utf8" });
|
|
520
|
+
}
|
|
521
|
+
async function writeFileWithOverwritePrompt(filePath, content, options) {
|
|
522
|
+
const { default: defaultVal, force = !1, ...writeOptions } = options, printablePath = filePath.startsWith(process.cwd()) ? path.relative(process.cwd(), filePath) : filePath;
|
|
523
|
+
return await fileEqualsData(filePath, content) || !force && await fileExists(filePath) && !await prompt(`File "${printablePath}" already exists. Overwrite?`, {
|
|
524
|
+
type: "confirm",
|
|
525
|
+
default: defaultVal
|
|
526
|
+
}) ? !1 : (await writeFile(filePath, content, writeOptions), !0);
|
|
527
|
+
}
|
|
528
|
+
async function copyFileWithOverwritePrompt(from, to, flags) {
|
|
529
|
+
const printablePath = to.startsWith(process.cwd()) ? path.relative(process.cwd(), to) : to;
|
|
530
|
+
return await filesAreEqual(from, to) || (await ensureDirectoryExists(to), !flags.force && await fileExists(to) && !await prompt(`File "${printablePath}" already exists. Overwrite?`, {
|
|
531
|
+
type: "confirm",
|
|
532
|
+
default: !1
|
|
533
|
+
})) ? !1 : (await copyFile(from, to), !0);
|
|
534
|
+
}
|
|
535
|
+
async function ensureDirectoryExists(filePath) {
|
|
536
|
+
const dirname = path.dirname(filePath);
|
|
537
|
+
await fileExists(dirname) || (await ensureDirectoryExists(dirname), await mkdir(dirname));
|
|
538
|
+
}
|
|
539
|
+
async function fileEqualsData(filePath, content) {
|
|
540
|
+
const contentHash = crypto.createHash("sha1").update(content).digest("hex"), remoteHash = await getFileHash(filePath);
|
|
541
|
+
return contentHash === remoteHash;
|
|
542
|
+
}
|
|
543
|
+
async function filesAreEqual(file1, file2) {
|
|
544
|
+
const [hash1, hash2] = await Promise.all([getFileHash(file1, !1), getFileHash(file2)]);
|
|
545
|
+
return hash1 === hash2;
|
|
546
|
+
}
|
|
547
|
+
function getFileHash(filePath, allowMissing = !0) {
|
|
548
|
+
return new Promise((resolve, reject) => {
|
|
549
|
+
const hash = crypto.createHash("sha1"), stream = fs.createReadStream(filePath);
|
|
550
|
+
stream.on("error", (err) => {
|
|
551
|
+
err.code === "ENOENT" && allowMissing ? resolve(null) : reject(err);
|
|
552
|
+
}), stream.on("end", () => resolve(hash.digest("hex"))), stream.on("data", (chunk) => hash.update(chunk));
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
async function ensureDir(dirPath) {
|
|
556
|
+
try {
|
|
557
|
+
await mkdir(dirPath);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
if (err.code !== "EEXIST")
|
|
560
|
+
throw err;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function isEmptyish(dirPath) {
|
|
564
|
+
const ignoredFiles = [".git", ".gitignore", "license", "readme.md"];
|
|
565
|
+
return (await readdir(dirPath).catch(() => [])).filter((file) => !ignoredFiles.includes(file.toLowerCase())).length === 0;
|
|
566
|
+
}
|
|
567
|
+
async function readFileContent({
|
|
568
|
+
filename,
|
|
569
|
+
basePath
|
|
570
|
+
}) {
|
|
571
|
+
const filepath = path.normalize(path.join(basePath, filename));
|
|
572
|
+
try {
|
|
573
|
+
return await readFile$1(filepath, "utf8");
|
|
574
|
+
} catch (err) {
|
|
575
|
+
if (err.code === "ENOENT") {
|
|
576
|
+
log.debug(`No ${filename} file found.`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
throw new Error(`Failed to read "${filepath}": ${err.message}`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function readJson5File({
|
|
583
|
+
filename,
|
|
584
|
+
basePath
|
|
585
|
+
}) {
|
|
586
|
+
const content = await readFileContent({ filename, basePath });
|
|
587
|
+
if (content)
|
|
588
|
+
return parseJson5(content, filename);
|
|
589
|
+
}
|
|
590
|
+
function parseJson5(content, errorKey) {
|
|
591
|
+
try {
|
|
592
|
+
return json5.parse(content);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
throw new Error(`Error parsing "${errorKey}": ${err.message}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const request = getIt([
|
|
598
|
+
promise({ onlyBody: !0 }),
|
|
599
|
+
jsonRequest(),
|
|
600
|
+
jsonResponse(),
|
|
601
|
+
httpErrors(),
|
|
602
|
+
headers({ "User-Agent": `${pkg.name}@${pkg.version}` })
|
|
603
|
+
]);
|
|
604
|
+
async function getUserInfo({ requireUserConfirmation, flags }, pkg2) {
|
|
605
|
+
const userInfo = getPackageUserInfo({ author: flags.author ?? pkg2?.author }) || await getSanityUserInfo() || await getGitUserInfo();
|
|
606
|
+
return requireUserConfirmation ? promptForInfo(userInfo) : userInfo;
|
|
607
|
+
}
|
|
608
|
+
function getPackageUserInfo(pkg2) {
|
|
609
|
+
let author = pkg2?.author;
|
|
610
|
+
if (!author)
|
|
611
|
+
return;
|
|
612
|
+
if (author && typeof author != "string")
|
|
613
|
+
return author;
|
|
614
|
+
if (!author.includes("@"))
|
|
615
|
+
return { name: author };
|
|
616
|
+
const [pre, ...post] = author.replace(/[<>[\]]/g, "").split(/@/), nameParts = pre.split(/\s+/), email = [nameParts[nameParts.length - 1], ...post].join("@");
|
|
617
|
+
return { name: nameParts.slice(0, -1).join(" "), email };
|
|
618
|
+
}
|
|
619
|
+
async function promptForInfo(defValue) {
|
|
620
|
+
const name = await prompt("Author name", {
|
|
621
|
+
filter: filterString,
|
|
622
|
+
default: defValue && defValue.name,
|
|
623
|
+
validate: requiredString
|
|
624
|
+
}), email = await prompt("Author email", {
|
|
625
|
+
filter: filterString,
|
|
626
|
+
default: defValue && defValue.email,
|
|
627
|
+
validate: validOrEmptyEmail
|
|
628
|
+
});
|
|
629
|
+
return { name, email };
|
|
630
|
+
}
|
|
631
|
+
async function getSanityUserInfo() {
|
|
632
|
+
try {
|
|
633
|
+
const token = (await readJsonFile(
|
|
634
|
+
path.join(xdgBasedir.config ?? "", "sanity", "config.json")
|
|
635
|
+
))?.authToken;
|
|
636
|
+
if (!token)
|
|
637
|
+
return;
|
|
638
|
+
const user = await request({
|
|
639
|
+
url: "https://api.sanity.io/v1/users/me",
|
|
640
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
641
|
+
});
|
|
642
|
+
if (!user)
|
|
643
|
+
return;
|
|
644
|
+
const { name, email } = user;
|
|
645
|
+
return { name, email };
|
|
646
|
+
} catch {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
async function getGitUserInfo() {
|
|
651
|
+
try {
|
|
652
|
+
const name = execSync("git config user.name", { encoding: "utf8" }).trim(), email = execSync("git config user.email", { encoding: "utf8" }).trim();
|
|
653
|
+
return name ? { name, email: email || void 0 } : void 0;
|
|
654
|
+
} catch {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function filterString(val) {
|
|
659
|
+
return (val || "").trim();
|
|
660
|
+
}
|
|
661
|
+
function requiredString(value) {
|
|
662
|
+
return value.length > 1 ? !0 : "Required";
|
|
663
|
+
}
|
|
664
|
+
function validOrEmptyEmail(value) {
|
|
665
|
+
return value ? validate(value) ? !0 : "Must either be a valid email or empty" : !0;
|
|
666
|
+
}
|
|
667
|
+
function generateReadme(data) {
|
|
668
|
+
const { user, pluginName, license } = data;
|
|
669
|
+
return outdent$1`
|
|
670
|
+
# ${pluginName}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
${installationSnippet(pluginName ?? "unknown")}
|
|
674
|
+
|
|
675
|
+
## Usage
|
|
676
|
+
|
|
677
|
+
Add it as a plugin in \`sanity.config.ts\` (or .js):
|
|
678
|
+
|
|
679
|
+
\`\`\`ts
|
|
680
|
+
import {defineConfig} from 'sanity'
|
|
681
|
+
import {myPlugin} from '${pluginName}'
|
|
682
|
+
|
|
683
|
+
export default defineConfig({
|
|
684
|
+
//...
|
|
685
|
+
plugins: [myPlugin({})],
|
|
686
|
+
})
|
|
687
|
+
\`\`\`
|
|
688
|
+
|
|
689
|
+
${getLicenseText(license?.id, user?.name ? user : void 0)}
|
|
690
|
+
${developTestSnippet()}
|
|
691
|
+
` + `
|
|
692
|
+
`;
|
|
693
|
+
}
|
|
694
|
+
function installationSnippet(packageName) {
|
|
695
|
+
return outdent$1`
|
|
696
|
+
## Installation
|
|
697
|
+
|
|
698
|
+
\`\`\`sh
|
|
699
|
+
npm install ${packageName}
|
|
700
|
+
\`\`\`
|
|
701
|
+
`;
|
|
702
|
+
}
|
|
703
|
+
function developTestSnippet() {
|
|
704
|
+
return outdent$1`
|
|
705
|
+
## Develop & test
|
|
706
|
+
|
|
707
|
+
This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit)
|
|
708
|
+
with default configuration for build & watch scripts.
|
|
709
|
+
|
|
710
|
+
See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio)
|
|
711
|
+
on how to run this plugin with hotreload in the studio.
|
|
712
|
+
`;
|
|
713
|
+
}
|
|
714
|
+
function getLicenseText(licenseId, user) {
|
|
715
|
+
if (!licenseId)
|
|
716
|
+
return "";
|
|
717
|
+
let licenseName = licenses.find(licenseId).title;
|
|
718
|
+
licenseName = licenseName?.replace(/\s+license$/i, "");
|
|
719
|
+
let licenseText = `## License
|
|
720
|
+
`;
|
|
721
|
+
return licenseName && user?.name ? licenseText = `${licenseText}
|
|
722
|
+
[${licenseName}](LICENSE) \xA9 ${user?.name}
|
|
723
|
+
` : licenseName ? licenseText = `${licenseText}
|
|
724
|
+
[${licenseName}](LICENSE)
|
|
725
|
+
` : licenseText = `${licenseText}
|
|
726
|
+
See [LICENSE](LICENSE)`, licenseText;
|
|
727
|
+
}
|
|
728
|
+
function isDefaultGitHubReadme(readme) {
|
|
729
|
+
if (!readme)
|
|
730
|
+
return !1;
|
|
731
|
+
const lines = readme.split(`
|
|
732
|
+
`, 20).filter(Boolean);
|
|
733
|
+
return lines.length <= 2 && lines[0].startsWith("#");
|
|
734
|
+
}
|
|
735
|
+
const semverWorkflowPreset = {
|
|
736
|
+
name: "semver-workflow",
|
|
737
|
+
description: "Files and dependencies for conventional-commits, github workflow and semantic-release.",
|
|
738
|
+
apply: applyPreset$2
|
|
739
|
+
}, info = (write, msg, ...args) => write && log.info(msg, ...args);
|
|
740
|
+
async function applyPreset$2(options) {
|
|
741
|
+
await writeAssets(semverWorkflowFiles(), options), await addPrepareScript(options), await addDevDependencies$2(options), await updateReadme(options);
|
|
742
|
+
}
|
|
743
|
+
async function addPrepareScript(options) {
|
|
744
|
+
const pkg2 = await getPackage(options), didWrite = await addPackageJsonScripts(pkg2, options, (scripts) => (scripts.prepare = addScript("husky", scripts.prepare), scripts));
|
|
745
|
+
info(didWrite, "Added prepare script to package.json");
|
|
746
|
+
}
|
|
747
|
+
async function addDevDependencies$2(options) {
|
|
748
|
+
const pkg2 = await getPackage(options), devDeps = sortKeys({
|
|
749
|
+
...pkg2.devDependencies,
|
|
750
|
+
...await semverWorkflowDependencies()
|
|
751
|
+
}), newPkg = { ...pkg2 };
|
|
752
|
+
newPkg.devDependencies = devDeps, await writePackageJsonDirect(newPkg, options), log.info("Updated devDependencies."), log.info(
|
|
753
|
+
chalk.green(
|
|
754
|
+
outdent$1`
|
|
755
|
+
semantic-release preset injected.
|
|
756
|
+
|
|
757
|
+
Please confer
|
|
758
|
+
https://github.com/sanity-io/plugin-kit/blob/main/docs/semver-workflow.md#manual-steps-after-inject
|
|
759
|
+
to finalize configuration for this preset.
|
|
760
|
+
`.trim()
|
|
761
|
+
)
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
async function updateReadme(options) {
|
|
765
|
+
const { basePath } = options, readmePath = path.join(basePath, "README.md"), readme = await readFile$1(readmePath, "utf8").catch(errorToUndefined) ?? "", { install, usage, developTest, license, releaseSnippet } = await readmeSnippets(options), prependSections = missingSections(readme, [install, usage]), appendSections = missingSections(readme, [license, developTest, releaseSnippet]);
|
|
766
|
+
if (prependSections.length || appendSections.length) {
|
|
767
|
+
const updatedReadme = [...prependSections, readme, ...appendSections].filter(Boolean).join(`
|
|
768
|
+
|
|
769
|
+
`);
|
|
770
|
+
await writeFile(readmePath, updatedReadme, { encoding: "utf8" }), log.info("Updated README. Please review the changes.");
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
async function readmeSnippets(options) {
|
|
774
|
+
const pkg2 = await getPackage(options), user = await getUserInfo(options, pkg2), bestEffortUrl = readmeBaseurl(pkg2), install = installationSnippet(pkg2.name ?? "unknown"), usage = outdent$1`
|
|
775
|
+
## Usage
|
|
776
|
+
`, license = getLicenseText(typeof pkg2.license == "string" ? pkg2.license : void 0, user), releaseSnippet = outdent$1`
|
|
777
|
+
### Release new version
|
|
778
|
+
|
|
779
|
+
Run ["CI & Release" workflow](${bestEffortUrl}/actions/workflows/main.yml).
|
|
780
|
+
Make sure to select the main branch and check "Release new version".
|
|
781
|
+
|
|
782
|
+
Semantic release will only release on configured branches, so it is safe to run release on any branch.
|
|
783
|
+
`;
|
|
784
|
+
return {
|
|
785
|
+
install,
|
|
786
|
+
usage,
|
|
787
|
+
license,
|
|
788
|
+
developTest: developTestSnippet(),
|
|
789
|
+
releaseSnippet
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function missingSections(readme, sections) {
|
|
793
|
+
return sections.filter((section) => !closeEnough(section, readme));
|
|
794
|
+
}
|
|
795
|
+
function closeEnough(a, b) {
|
|
796
|
+
const aLines = a.split(`
|
|
797
|
+
`), bLines = b.split(`
|
|
798
|
+
`);
|
|
799
|
+
return aLines.filter((line) => bLines.find((bLine) => bLine === line)).length >= aLines.length * 0.5;
|
|
800
|
+
}
|
|
801
|
+
function semverWorkflowFiles() {
|
|
802
|
+
return [
|
|
803
|
+
{
|
|
804
|
+
type: "copy",
|
|
805
|
+
from: [".github", "workflows", "main.yml"],
|
|
806
|
+
to: [".github", "workflows", "main.yml"]
|
|
807
|
+
},
|
|
808
|
+
{ type: "copy", from: [".husky", "commit-msg"], to: [".husky", "commit-msg"] },
|
|
809
|
+
{ type: "copy", from: [".husky", "pre-commit"], to: [".husky", "pre-commit"] },
|
|
810
|
+
{ type: "copy", from: [".releaserc.json"], to: ".releaserc.json" },
|
|
811
|
+
{ type: "copy", from: ["commitlint.template.js"], to: "commitlint.config.js" },
|
|
812
|
+
{ type: "copy", from: ["lint-staged.template.js"], to: "lint-staged.config.js" }
|
|
813
|
+
].map((fromTo) => fromTo.type === "copy" ? {
|
|
814
|
+
...fromTo,
|
|
815
|
+
from: ["semver-workflow", ...fromTo.from]
|
|
816
|
+
} : fromTo);
|
|
817
|
+
}
|
|
818
|
+
async function semverWorkflowDependencies() {
|
|
819
|
+
return resolveLatestVersions([
|
|
820
|
+
"@commitlint/cli",
|
|
821
|
+
"@commitlint/config-conventional",
|
|
822
|
+
"@sanity/semantic-release-preset",
|
|
823
|
+
"husky",
|
|
824
|
+
"lint-staged"
|
|
825
|
+
]);
|
|
826
|
+
}
|
|
827
|
+
function readmeBaseurl(pkg2) {
|
|
828
|
+
return (pkg2.repository?.url ?? pkg2.homepage ?? "TODO").replace(/.+:\/\//g, "https://").replace(/\.git/g, "").replace(/git@github.com\//g, "github.com/").replace(/git@github.com:/g, "https://github.com/").replace(/#.+/g, "");
|
|
829
|
+
}
|
|
830
|
+
const forcedPackageVersions = {}, forcedDevPackageVersions = {}, forcedPeerPackageVersions = {
|
|
831
|
+
react: "^18",
|
|
832
|
+
"react-dom": "^18",
|
|
833
|
+
"@types/react": "^18",
|
|
834
|
+
"@types/react-dom": "^18",
|
|
835
|
+
sanity: "^3",
|
|
836
|
+
"styled-components": "^5.2"
|
|
837
|
+
}, ui = {
|
|
838
|
+
name: "ui",
|
|
839
|
+
description: "`@sanity/ui` and dependencies",
|
|
840
|
+
apply: applyPreset$1
|
|
841
|
+
};
|
|
842
|
+
async function applyPreset$1(options) {
|
|
843
|
+
await addDependencies(options), await addDevDependencies$1(options), log.info(chalk.green("ui preset injected"));
|
|
844
|
+
}
|
|
845
|
+
async function addDependencies(options) {
|
|
846
|
+
const pkg2 = await getPackage(options), newDeps = sortKeys(
|
|
847
|
+
forceDependencyVersions(
|
|
848
|
+
{
|
|
849
|
+
...pkg2.dependencies,
|
|
850
|
+
...await resolveDependencyList()
|
|
851
|
+
},
|
|
852
|
+
forcedPackageVersions
|
|
853
|
+
)
|
|
854
|
+
), newPkg = { ...pkg2 };
|
|
855
|
+
newPkg.dependencies = newDeps, await writePackageJsonDirect(newPkg, options), log.info("Updated dependencies.");
|
|
856
|
+
}
|
|
857
|
+
async function addDevDependencies$1(options) {
|
|
858
|
+
const pkg2 = await getPackage(options), newDeps = sortKeys(
|
|
859
|
+
forceDependencyVersions(
|
|
860
|
+
{
|
|
861
|
+
...pkg2.devDependencies,
|
|
862
|
+
...await resolveDevDependencyList()
|
|
863
|
+
},
|
|
864
|
+
forcedDevPackageVersions
|
|
865
|
+
)
|
|
866
|
+
), newPkg = { ...pkg2 };
|
|
867
|
+
newPkg.devDependencies = newDeps, await writePackageJsonDirect(newPkg, options), log.info("Updated devDependencies.");
|
|
868
|
+
}
|
|
869
|
+
async function resolveDependencyList() {
|
|
870
|
+
return resolveLatestVersions(["@sanity/icons", "@sanity/ui"]);
|
|
871
|
+
}
|
|
872
|
+
async function resolveDevDependencyList() {
|
|
873
|
+
return resolveLatestVersions([
|
|
874
|
+
// install the peer dependencies of `@sanity/ui` as dev dependencies
|
|
875
|
+
"react",
|
|
876
|
+
"react-dom",
|
|
877
|
+
"styled-components"
|
|
878
|
+
]);
|
|
879
|
+
}
|
|
880
|
+
const uiWorkshop = {
|
|
881
|
+
name: "ui-workshop",
|
|
882
|
+
description: "Files for testing custom components with @sanity/ui-workshop",
|
|
883
|
+
apply: applyPreset
|
|
884
|
+
};
|
|
885
|
+
async function applyPreset(options) {
|
|
886
|
+
await writeAssets(files(), options), await addDevDependencies(options), await updateGitIgnore(options), log.info(
|
|
887
|
+
chalk.green(
|
|
888
|
+
outdent$1`
|
|
889
|
+
ui-workshop preset injected.
|
|
890
|
+
|
|
891
|
+
Please confer
|
|
892
|
+
https://github.com/sanity-io/plugin-kit/blob/main/docs/ui-workshop.md#manual-steps-after-inject
|
|
893
|
+
to finalize configuration for this preset.
|
|
894
|
+
`.trim()
|
|
895
|
+
)
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
function files() {
|
|
899
|
+
return [
|
|
900
|
+
{ type: "copy", from: ["workshop.config.ts"], to: ["workshop.config.ts"] },
|
|
901
|
+
{ type: "copy", from: ["src", "CustomField.tsx"], to: ["src", "CustomField.tsx"] },
|
|
902
|
+
{
|
|
903
|
+
type: "copy",
|
|
904
|
+
from: ["src", "__workshop__", "index.tsx"],
|
|
905
|
+
to: ["src", "__workshop__", "index.tsx"]
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
type: "copy",
|
|
909
|
+
from: ["src", "__workshop__", "props.tsx"],
|
|
910
|
+
to: ["src", "__workshop__", "props.tsx"]
|
|
911
|
+
}
|
|
912
|
+
].map((fromTo) => fromTo.type === "copy" ? {
|
|
913
|
+
...fromTo,
|
|
914
|
+
from: ["ui-workshop", ...fromTo.from]
|
|
915
|
+
} : fromTo);
|
|
916
|
+
}
|
|
917
|
+
async function updateGitIgnore(options) {
|
|
918
|
+
const { basePath } = options, gitignorePath = path.join(basePath, ".gitignore");
|
|
919
|
+
let gitignore = await readFile$1(gitignorePath, "utf8").catch(errorToUndefined) ?? "";
|
|
920
|
+
const value = ".workshop";
|
|
921
|
+
gitignore.includes(value) || (gitignore += `
|
|
922
|
+
|
|
923
|
+
${value}`, await writeFile(gitignorePath, gitignore, { encoding: "utf8" }));
|
|
924
|
+
}
|
|
925
|
+
async function addDevDependencies(options) {
|
|
926
|
+
const pkg2 = await getPackage(options), devDeps = sortKeys({
|
|
927
|
+
...pkg2.devDependencies,
|
|
928
|
+
...await devDependencies()
|
|
929
|
+
}), newPkg = { ...pkg2 };
|
|
930
|
+
newPkg.devDependencies = devDeps, await writePackageJsonDirect(newPkg, options), log.info("Updated devDependencies.");
|
|
931
|
+
}
|
|
932
|
+
async function devDependencies() {
|
|
933
|
+
return resolveLatestVersions([
|
|
934
|
+
"@sanity/ui-workshop",
|
|
935
|
+
"@sanity/icons",
|
|
936
|
+
"@sanity/ui",
|
|
937
|
+
"react",
|
|
938
|
+
"react-dom",
|
|
939
|
+
"styled-components"
|
|
940
|
+
]);
|
|
941
|
+
}
|
|
942
|
+
const presets = [semverWorkflowPreset, renovatePreset, ui, uiWorkshop], presetNames = presets.map((p) => p?.name);
|
|
943
|
+
function presetHelpList(padStart) {
|
|
944
|
+
return presets.map((p) => `${"".padStart(padStart)}${p.name.padEnd(20)}${p.description}`).join(`
|
|
945
|
+
`);
|
|
946
|
+
}
|
|
947
|
+
async function injectPresets(options) {
|
|
948
|
+
if (options.flags.presetOnly && !options.flags.preset?.length)
|
|
949
|
+
throw new Error("--preset-only, but no --preset [preset-name] was provided.");
|
|
950
|
+
const applyPresets = presetsFromInput(options.flags.preset);
|
|
951
|
+
for (const preset of applyPresets)
|
|
952
|
+
await preset.apply(options);
|
|
953
|
+
}
|
|
954
|
+
function presetsFromInput(inputPresets) {
|
|
955
|
+
if (!inputPresets)
|
|
956
|
+
return [];
|
|
957
|
+
const unknownPresets = inputPresets.filter((p) => !presetNames.includes(p));
|
|
958
|
+
if (unknownPresets.length)
|
|
959
|
+
throw new Error(
|
|
960
|
+
`Unknown --preset(s): [${unknownPresets.join(", ")}]. Must be one of: [${presetNames.join(
|
|
961
|
+
", "
|
|
962
|
+
)}]`
|
|
963
|
+
);
|
|
964
|
+
return inputPresets.filter(onlyUnique).map((presetName) => presets.find((p) => p.name === presetName)).filter((p) => !!p);
|
|
965
|
+
}
|
|
966
|
+
function onlyUnique(value, index, arr) {
|
|
967
|
+
return arr.indexOf(value) === index;
|
|
968
|
+
}
|
|
969
|
+
const bannedFields = ["login", "description", "projecturl", "email"], preferredLicenses = ["MIT", "ISC", "BSD-3-Clause"], otherLicenses = Object.keys(licenses.list).filter((id) => {
|
|
970
|
+
const license = licenses.list[id];
|
|
971
|
+
return !preferredLicenses.includes(id) && !bannedFields.some((field) => license.body.includes(`[${field}]`));
|
|
972
|
+
});
|
|
973
|
+
async function inject(options) {
|
|
974
|
+
options.flags.presetOnly ? log.info("Only apply presets, skipping default inject.") : await injectBase(options), await injectPresets(options);
|
|
975
|
+
}
|
|
976
|
+
async function injectBase(options) {
|
|
977
|
+
const { basePath, flags, requireUserConfirmation } = options, info2 = (write, msg, ...args) => write && log.info(msg, ...args), pkg2 = await getPackage(options).catch(errorToUndefined);
|
|
978
|
+
log.debug("Plugin has package.json: %s", pkg2 ? "yes" : "no");
|
|
979
|
+
const user = await getUserInfo(options, pkg2);
|
|
980
|
+
log.debug("User information: %o", user);
|
|
981
|
+
const pkgName = flags.name ?? pkg2?.name, pluginName = requireUserConfirmation || !pkgName ? await promptForPackageName(options, pkgName) : pkgName;
|
|
982
|
+
log.debug("Plugin name: %s", pluginName);
|
|
983
|
+
const license = await getLicense(flags, { user, pluginName, pkg: pkg2, requireUserConfirmation }), licenseChanged = (pkg2 && pkg2.license) !== (license && license.id);
|
|
984
|
+
log.debug("License: %s", license ? license.id : "<none>");
|
|
985
|
+
const description = await getProjectDescription(basePath, pkg2, requireUserConfirmation);
|
|
986
|
+
log.debug("Description: %s", description || "<none>");
|
|
987
|
+
const repoUrl = flags.repo ?? (await gitRemoteOriginUrl(basePath).catch(errorToUndefined) || pkg2?.repository?.url), gitOrigin = requireUserConfirmation ? await promptForRepoOrigin(options, repoUrl) : repoUrl;
|
|
988
|
+
log.debug("Remote origin: %s", gitOrigin || "<none>");
|
|
989
|
+
const data = { user, pluginName, license, description, pkg: pkg2, gitOrigin };
|
|
990
|
+
let didWrite;
|
|
991
|
+
const newPkg = await writePackageJson(data, options);
|
|
992
|
+
info2(newPkg !== pkg2, "Wrote package.json"), data.pkg = newPkg, didWrite = await writeLicense(data, options, licenseChanged), info2(didWrite, "Wrote license file (LICENSE)"), didWrite = await writeReadme(data, options), info2(didWrite, "Wrote readme file (README.md)"), didWrite = await writeStaticAssets(options), info2(didWrite.length > 0, "Wrote static asset files: %s", didWrite.join(", ")), didWrite = await addBuildScripts(newPkg, options), info2(didWrite, "Added build scripts to package.json"), didWrite = await addCompileDirToGitIgnore(options), info2(didWrite, "Added compilation output directory to .gitignore");
|
|
993
|
+
}
|
|
994
|
+
async function writeReadme(data, options) {
|
|
995
|
+
const { basePath } = options, readmePath = path.join(basePath, "README.md"), readme = await readFile$1(readmePath, "utf8").catch(errorToUndefined);
|
|
996
|
+
return readme && !isDefaultGitHubReadme(readme) ? !1 : (await writeFileWithOverwritePrompt(readmePath, generateReadme(data), {
|
|
997
|
+
encoding: "utf8",
|
|
998
|
+
force: options.flags.force
|
|
999
|
+
}), !0);
|
|
1000
|
+
}
|
|
1001
|
+
async function writeLicense({ license }, options, licenseChanged) {
|
|
1002
|
+
const { basePath, flags } = options;
|
|
1003
|
+
if (flags.license === !1 || !license)
|
|
1004
|
+
return !1;
|
|
1005
|
+
const hasLicenseMdFile = await fileExists(path.join(basePath, "LICENSE.md")), licensePath = path.join(basePath, hasLicenseMdFile ? "LICENSE.md" : "LICENSE");
|
|
1006
|
+
return await writeFileWithOverwritePrompt(licensePath, license.text, {
|
|
1007
|
+
encoding: "utf8",
|
|
1008
|
+
default: licenseChanged,
|
|
1009
|
+
force: flags.force
|
|
1010
|
+
}), !0;
|
|
1011
|
+
}
|
|
1012
|
+
async function getLicense(flags, {
|
|
1013
|
+
user,
|
|
1014
|
+
pluginName,
|
|
1015
|
+
pkg: pkg2,
|
|
1016
|
+
requireUserConfirmation
|
|
1017
|
+
}) {
|
|
1018
|
+
const license = await getLicenseIdentifier(flags, pkg2, requireUserConfirmation);
|
|
1019
|
+
if (!license)
|
|
1020
|
+
return;
|
|
1021
|
+
const text = license.body.replace(/\[fullname\]/g, user?.name).replace(/\[project\]/g, pluginName).replace(/\[year\]/g, (/* @__PURE__ */ new Date()).getFullYear());
|
|
1022
|
+
return { id: license.id, text };
|
|
1023
|
+
}
|
|
1024
|
+
async function getLicenseIdentifier(flags, pkg2, requireUserConfirmation = !1) {
|
|
1025
|
+
if (flags.license === !1)
|
|
1026
|
+
return null;
|
|
1027
|
+
if (typeof flags.license == "string") {
|
|
1028
|
+
const license = licenses.find(`${flags.license}`);
|
|
1029
|
+
if (!license)
|
|
1030
|
+
throw new Error(`License "${flags.license}" not found`);
|
|
1031
|
+
return license;
|
|
1032
|
+
}
|
|
1033
|
+
if (pkg2 && pkg2.license && !requireUserConfirmation) {
|
|
1034
|
+
const license = licenses.find(`${pkg2.license}`);
|
|
1035
|
+
if (license)
|
|
1036
|
+
return license;
|
|
1037
|
+
log.warn(`package.json contains license "${pkg2.license}", which is not recognized`);
|
|
1038
|
+
}
|
|
1039
|
+
const licenseId = await prompt("Which license do you want to use?", {
|
|
1040
|
+
default: pkg2 && pkg2.license && licenses.find(pkg2.license) ? pkg2.license : preferredLicenses[0],
|
|
1041
|
+
choices: [
|
|
1042
|
+
prompt.separator(),
|
|
1043
|
+
...preferredLicenses.map((value) => ({ value, name: licenses.list[value].title })),
|
|
1044
|
+
prompt.separator(),
|
|
1045
|
+
...otherLicenses.map((value) => ({ value, name: licenses.list[value].title }))
|
|
1046
|
+
]
|
|
1047
|
+
});
|
|
1048
|
+
return licenses.find(licenseId);
|
|
1049
|
+
}
|
|
1050
|
+
async function getProjectDescription(basePath, pkg2, requireUserConfirmation = !1) {
|
|
1051
|
+
let description = await resolveProjectDescription(basePath, pkg2);
|
|
1052
|
+
return requireUserConfirmation && (description = await prompt("Plugin description", { default: description || "" })), description ?? "";
|
|
1053
|
+
}
|
|
1054
|
+
async function resolveProjectDescription(basePath, pkg2) {
|
|
1055
|
+
if (pkg2 && typeof pkg2.description == "string" && pkg2.description.length > 5)
|
|
1056
|
+
return pkg2.description;
|
|
1057
|
+
try {
|
|
1058
|
+
const readmePath = path.join(basePath, "README.md"), readme = await readFile$1(readmePath, "utf8"), [title, description] = readme.split(`
|
|
1059
|
+
`).filter(Boolean);
|
|
1060
|
+
if (!title || !description || !title.match(/^#\s+\w+/))
|
|
1061
|
+
return null;
|
|
1062
|
+
const unlinked = description.replace(/\[(.*?)\]\(.*?\)/g, "$1");
|
|
1063
|
+
return /^[^#]/.test(unlinked) ? unlinked : null;
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
return errorToUndefined(err);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async function writeAssets(injectables, { basePath, flags }) {
|
|
1069
|
+
const assetsDir = await findAssetsDir(), from = (...segments) => path.join(assetsDir, "inject", ...segments), to = (...segments) => path.join(basePath, ...segments), writes = [];
|
|
1070
|
+
for (const injectable of injectables) {
|
|
1071
|
+
if (injectable.type === "copy") {
|
|
1072
|
+
const fromPath = asArray(injectable.from), toPath = asArray(injectable.to);
|
|
1073
|
+
await copyFileWithOverwritePrompt(from(...fromPath), to(...toPath), flags) && writes.push(path.join(...toPath));
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
if (injectable.type === "template") {
|
|
1077
|
+
const toPath = asArray(injectable.to);
|
|
1078
|
+
await writeFileWithOverwritePrompt(to(...toPath), `${injectable.value.trim()}
|
|
1079
|
+
`, {
|
|
1080
|
+
default: "n",
|
|
1081
|
+
force: injectable.force || flags.force
|
|
1082
|
+
}), writes.push(path.join(...toPath));
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
throw new Error(`Unknown operation type "${injectable.type}"`);
|
|
1086
|
+
}
|
|
1087
|
+
return writes;
|
|
1088
|
+
}
|
|
1089
|
+
async function writeStaticAssets(options) {
|
|
1090
|
+
const { outDir, flags } = options, files2 = [
|
|
1091
|
+
flags.eslint && eslintrcTemplate({ flags: options.flags }),
|
|
1092
|
+
flags.eslint && eslintignoreTemplate({ outDir, flags: options.flags }),
|
|
1093
|
+
{ type: "copy", from: "editorconfig", to: ".editorconfig" },
|
|
1094
|
+
{ type: "copy", from: "sanity.json", to: "sanity.json" },
|
|
1095
|
+
{ type: "copy", from: "v2-incompatible.js.template", to: "v2-incompatible.js" },
|
|
1096
|
+
pkgConfigTemplate({ outDir, flags: options.flags }),
|
|
1097
|
+
flags.gitignore && gitignoreTemplate(),
|
|
1098
|
+
flags.typescript && tsconfigTemplate({ flags: options.flags }),
|
|
1099
|
+
flags.typescript && tsconfigTemplateDist({ outDir, flags: options.flags }),
|
|
1100
|
+
flags.typescript && tsconfigTemplateSettings({ outDir, flags: options.flags }),
|
|
1101
|
+
flags.prettier && prettierignoreTemplate({ outDir }),
|
|
1102
|
+
flags.prettier && { type: "copy", from: "prettierrc.json", to: ".prettierrc" }
|
|
1103
|
+
].map((f) => f || void 0).filter((f) => !!f);
|
|
1104
|
+
return writeAssets(files2, options);
|
|
1105
|
+
}
|
|
1106
|
+
function asArray(input) {
|
|
1107
|
+
return typeof input == "string" ? [input] : input;
|
|
1108
|
+
}
|
|
1109
|
+
async function findAssetsDir() {
|
|
1110
|
+
let maxBackpaddle = 3, currDir = path.dirname(fileURLToPath(import.meta.url)), assetsDir = "";
|
|
1111
|
+
for (; !assetsDir && maxBackpaddle; ) {
|
|
1112
|
+
currDir = path.join(currDir, "..");
|
|
1113
|
+
const assets = path.join(currDir, "assets");
|
|
1114
|
+
await fileExists(assets) ? assetsDir = assets : maxBackpaddle--;
|
|
1115
|
+
}
|
|
1116
|
+
if (!assetsDir)
|
|
1117
|
+
throw new Error("Could not find assets directory!");
|
|
1118
|
+
return assetsDir;
|
|
1119
|
+
}
|
|
1120
|
+
async function addCompileDirToGitIgnore(options) {
|
|
1121
|
+
const gitIgnorePath = path.join(options.basePath, ".gitignore"), gitignore = await readFile$1(gitIgnorePath, "utf8").catch(errorToUndefined);
|
|
1122
|
+
if (!gitignore)
|
|
1123
|
+
return !1;
|
|
1124
|
+
const ignore = options.outDir.replace(/^[./]+/, "").split("/")[0];
|
|
1125
|
+
if (!ignore)
|
|
1126
|
+
return !1;
|
|
1127
|
+
const lines = gitignore.trim().split(`
|
|
1128
|
+
`);
|
|
1129
|
+
return lines.includes(ignore) ? !1 : (lines.push("", "# Compiled plugin", ignore), await writeFile(gitIgnorePath, lines.join(`
|
|
1130
|
+
`) + `
|
|
1131
|
+
`, { encoding: "utf8" }), !0);
|
|
1132
|
+
}
|
|
1133
|
+
const mergedPackages = [
|
|
1134
|
+
"@sanity/base",
|
|
1135
|
+
"@sanity/core",
|
|
1136
|
+
"@sanity/types",
|
|
1137
|
+
"@sanity/data-aspects",
|
|
1138
|
+
"@sanity/default-layout",
|
|
1139
|
+
"@sanity/default-login",
|
|
1140
|
+
"@sanity/desk-tool",
|
|
1141
|
+
"@sanity/field",
|
|
1142
|
+
"@sanity/form-builder",
|
|
1143
|
+
"@sanity/initial-value-templates",
|
|
1144
|
+
"@sanity/language-filter",
|
|
1145
|
+
"@sanity/production-preview",
|
|
1146
|
+
"@sanity/react-hooks",
|
|
1147
|
+
"@sanity/resolver",
|
|
1148
|
+
"@sanity/state-router",
|
|
1149
|
+
"@sanity/structure",
|
|
1150
|
+
"@sanity/studio-hints"
|
|
1151
|
+
].sort(), deprecatedDevDeps = [
|
|
1152
|
+
"tsdx",
|
|
1153
|
+
"sanipack",
|
|
1154
|
+
"parcel",
|
|
1155
|
+
"@parcel/packager-ts",
|
|
1156
|
+
"@parcel/transformer-typescript-types"
|
|
1157
|
+
], expectedScripts = {
|
|
1158
|
+
build: "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
|
|
1159
|
+
watch: "pkg-utils watch --strict",
|
|
1160
|
+
"link-watch": "plugin-kit link-watch",
|
|
1161
|
+
prepublishOnly: "npm run build"
|
|
1162
|
+
};
|
|
1163
|
+
function filesWithSuffixes(fileBases, suffixes) {
|
|
1164
|
+
return fileBases.flatMap((file) => suffixes.map((suffix) => `${file}.${suffix}`));
|
|
1165
|
+
}
|
|
1166
|
+
function validateNodeEngine(packageJson) {
|
|
1167
|
+
return packageJson.engines?.node !== requiredNodeEngine ? [
|
|
1168
|
+
outdent$1`
|
|
1169
|
+
Expected package.json to contain engines.node: "${requiredNodeEngine}" to match @sanity/pkg-utils,
|
|
1170
|
+
but it was: ${packageJson.engines?.node}
|
|
1171
|
+
|
|
1172
|
+
Please add the following to package.json:
|
|
1173
|
+
|
|
1174
|
+
"engines": {
|
|
1175
|
+
"node": "${requiredNodeEngine}"
|
|
1176
|
+
}`.trimStart()
|
|
1177
|
+
] : [];
|
|
1178
|
+
}
|
|
1179
|
+
function validateScripts(packageJson) {
|
|
1180
|
+
const errors = [], divergentScripts = Object.entries(expectedScripts).filter(([key, expectedCommand]) => {
|
|
1181
|
+
const command = packageJson.scripts?.[key];
|
|
1182
|
+
return !command || !command.includes(expectedCommand);
|
|
1183
|
+
});
|
|
1184
|
+
return divergentScripts.length && errors.push(
|
|
1185
|
+
outdent$1`
|
|
1186
|
+
The following script commands did not contain expected defaults: ${divergentScripts.map(([key]) => key).join(", ")}
|
|
1187
|
+
|
|
1188
|
+
This checks for that the commands-strings includes these terms.
|
|
1189
|
+
|
|
1190
|
+
Please add the following to your package.json "scripts":
|
|
1191
|
+
|
|
1192
|
+
${divergentScripts.map(([key, value]) => `"${key}": "${value}"`).join(`,
|
|
1193
|
+
`)}
|
|
1194
|
+
`.trimStart()
|
|
1195
|
+
), errors;
|
|
1196
|
+
}
|
|
1197
|
+
async function validateTsConfig(ts, options) {
|
|
1198
|
+
const { basePath, outDir, tsconfig } = options, errors = [], wrongEntries = Object.entries({
|
|
1199
|
+
target: "esnext",
|
|
1200
|
+
jsx: "preserve",
|
|
1201
|
+
module: "preserve",
|
|
1202
|
+
rootDir: ".",
|
|
1203
|
+
outDir,
|
|
1204
|
+
noEmit: !0
|
|
1205
|
+
}).filter(([key, value]) => {
|
|
1206
|
+
let option = ts.options[key];
|
|
1207
|
+
return key === "rootDir" && typeof option == "string" && (option = path.relative(basePath, option) || "."), key === "outDir" && typeof option == "string" && (option = path.relative(basePath, option) || "."), key === "target" && option === 99 && (option = "esnext"), key === "module" && option === 200 && (option = "preserve"), key === "jsx" && option === 1 && (option = "preserve"), typeof value == "string" && typeof option == "string" ? value.toLowerCase() !== option?.toLowerCase() : value !== option;
|
|
1208
|
+
});
|
|
1209
|
+
if (wrongEntries.length) {
|
|
1210
|
+
const expectedOutput = wrongEntries.map(([key, value]) => `"${key}": ${typeof value == "string" ? `"${value}"` : value},`).join(`
|
|
1211
|
+
`);
|
|
1212
|
+
errors.push(
|
|
1213
|
+
outdent$1`
|
|
1214
|
+
Recommended ${tsconfig} compilerOptions missing:
|
|
1215
|
+
|
|
1216
|
+
The following fields had unexpected values: [${wrongEntries.map(([key]) => key).join(", ")}]
|
|
1217
|
+
Expected to find these values:
|
|
1218
|
+
${expectedOutput}
|
|
1219
|
+
|
|
1220
|
+
Please update your ${tsconfig} accordingly.
|
|
1221
|
+
`.trimStart()
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
return errors;
|
|
1225
|
+
}
|
|
1226
|
+
function validatePackageType({ type }) {
|
|
1227
|
+
return type === "module" ? [] : [
|
|
1228
|
+
outdent$1`
|
|
1229
|
+
package.json must set "type": "module" — plugins built with @sanity/plugin-kit are ESM-only.
|
|
1230
|
+
Found: ${type ? `"type": "${type}"` : 'no "type" field (defaults to "commonjs")'}
|
|
1231
|
+
|
|
1232
|
+
Please add the following to package.json:
|
|
1233
|
+
|
|
1234
|
+
"type": "module"
|
|
1235
|
+
`.trimStart()
|
|
1236
|
+
];
|
|
1237
|
+
}
|
|
1238
|
+
function validatePkgUtilsDependency({ devDependencies: devDependencies2 }) {
|
|
1239
|
+
return devDependencies2?.["@sanity/pkg-utils"] ? [] : [
|
|
1240
|
+
outdent$1`
|
|
1241
|
+
package.json does not list @sanity/pkg-utils as a devDependency.
|
|
1242
|
+
@sanity/pkg-utils replaced parcel as the recommended build tool in @sanity/plugin-kit 2.0.0
|
|
1243
|
+
|
|
1244
|
+
Please add it by running 'npm install --save-dev @sanity/pkg-utils'.
|
|
1245
|
+
`.trimStart()
|
|
1246
|
+
];
|
|
1247
|
+
}
|
|
1248
|
+
function validatePkgUtilsVersion({ basePath }) {
|
|
1249
|
+
const require2 = createRequire(path.join(basePath, "package.json"));
|
|
1250
|
+
let installedVersion;
|
|
1251
|
+
try {
|
|
1252
|
+
installedVersion = require2("@sanity/pkg-utils/package.json").version;
|
|
1253
|
+
} catch {
|
|
1254
|
+
return [
|
|
1255
|
+
outdent$1`
|
|
1256
|
+
@sanity/pkg-utils is not installed.
|
|
1257
|
+
plugin-kit loads package.config.ts through @sanity/pkg-utils (a peer dependency).
|
|
1258
|
+
|
|
1259
|
+
Please install it by running 'npm install --save-dev @sanity/pkg-utils'.
|
|
1260
|
+
`.trimStart()
|
|
1261
|
+
];
|
|
1262
|
+
}
|
|
1263
|
+
const major = Number.parseInt(installedVersion?.split(".")[0] ?? "", 10);
|
|
1264
|
+
return !Number.isFinite(major) || major < minPkgUtilsMajor ? [
|
|
1265
|
+
outdent$1`
|
|
1266
|
+
@sanity/pkg-utils ${installedVersion} is too old.
|
|
1267
|
+
plugin-kit requires @sanity/pkg-utils >=${minPkgUtilsMajor} to load package.config.ts.
|
|
1268
|
+
|
|
1269
|
+
Please upgrade it by running 'npm install --save-dev @sanity/pkg-utils@latest'.
|
|
1270
|
+
`.trimStart()
|
|
1271
|
+
] : [];
|
|
1272
|
+
}
|
|
1273
|
+
function validateSanityDependencies(packageJson) {
|
|
1274
|
+
const { dependencies, devDependencies: devDependencies2, peerDependencies } = packageJson, allDependencies = { ...dependencies, ...devDependencies2, ...peerDependencies }, illegalDeps = Object.keys(allDependencies).filter((dep) => mergedPackages.includes(dep)), unique = [...new Set(illegalDeps).values()];
|
|
1275
|
+
return unique.length ? [
|
|
1276
|
+
outdent$1`
|
|
1277
|
+
package.json depends on "@sanity/*" packages that have moved into "sanity" package.
|
|
1278
|
+
|
|
1279
|
+
The following dependencies should be replaced with "sanity":
|
|
1280
|
+
- ${unique.join(`
|
|
1281
|
+
- `)}
|
|
1282
|
+
|
|
1283
|
+
Refer to the reference docs to find replacement imports:
|
|
1284
|
+
${urls.refDocs}
|
|
1285
|
+
`.trimStart()
|
|
1286
|
+
] : [];
|
|
1287
|
+
}
|
|
1288
|
+
function validateDeprecatedDependencies(packageJson) {
|
|
1289
|
+
const { dependencies, devDependencies: devDependencies2, peerDependencies } = packageJson, allDependencies = { ...dependencies, ...devDependencies2, ...peerDependencies }, illegalDeps = Object.keys(allDependencies).filter((dep) => deprecatedDevDeps.includes(dep)), unique = [...new Set(illegalDeps).values()];
|
|
1290
|
+
return unique.length ? [
|
|
1291
|
+
outdent$1`
|
|
1292
|
+
package.json contains deprecated dependencies that should be removed:
|
|
1293
|
+
- ${unique.join(`
|
|
1294
|
+
- `)}
|
|
1295
|
+
`.trimStart()
|
|
1296
|
+
] : [];
|
|
1297
|
+
}
|
|
1298
|
+
async function validateBabelConfig({ basePath }) {
|
|
1299
|
+
const filenames = [".babelrc", ...filesWithSuffixes([".babelrc", "babel.config"], ["json", "js", "cjs", "mjs"])], babelFiles = [];
|
|
1300
|
+
for (const filename of filenames) {
|
|
1301
|
+
const filepath = path.normalize(path.join(basePath, filename));
|
|
1302
|
+
await fileExists(filepath) && babelFiles.push(filename);
|
|
1303
|
+
}
|
|
1304
|
+
return babelFiles.length ? [
|
|
1305
|
+
outdent$1`
|
|
1306
|
+
Found babel-config file: [${babelFiles.join(
|
|
1307
|
+
", "
|
|
1308
|
+
)}]. When using default @sanity/plugin-kit build command,
|
|
1309
|
+
this is probably not needed.
|
|
1310
|
+
|
|
1311
|
+
Delete the file, or disable this check.
|
|
1312
|
+
`.trimStart()
|
|
1313
|
+
] : [];
|
|
1314
|
+
}
|
|
1315
|
+
async function validateStudioConfig({ basePath }) {
|
|
1316
|
+
const suffixes = ["ts", "js", "tsx", "jsx"], filenames = filesWithSuffixes(["sanity.config", "sanity.cli"], suffixes), files2 = {};
|
|
1317
|
+
for (const filename of filenames) {
|
|
1318
|
+
const filepath = path.normalize(path.join(basePath, filename));
|
|
1319
|
+
files2[filename] = await fileExists(filepath);
|
|
1320
|
+
}
|
|
1321
|
+
const sanityJson = await readJson5File({ basePath, filename: "sanity.json" }), hasConfigFile = (fileBase) => filesWithSuffixes([fileBase], suffixes).some((filename) => files2[filename]), hasCliConfig = hasConfigFile("sanity.cli"), hasStudioConfig = hasConfigFile("sanity.config"), errors = [];
|
|
1322
|
+
if (sanityJson) {
|
|
1323
|
+
const info2 = [
|
|
1324
|
+
outdent$1`
|
|
1325
|
+
Found sanity.json. This file is not used by Sanity Studio V3.
|
|
1326
|
+
|
|
1327
|
+
Please consult the Studio V3 migration guide:
|
|
1328
|
+
${urls.migrationGuideStudio}
|
|
1329
|
+
It will detail how to convert sanity.json to sanity.config.ts (or .js) and sanity.cli.ts (or .js) equivalents.
|
|
1330
|
+
`.trimStart(),
|
|
1331
|
+
sanityJson.plugins?.length && outdent$1`
|
|
1332
|
+
For V3 versions and alternatives to V2 plugins, please refer to the Sanity Exchange:
|
|
1333
|
+
${urls.sanityExchange}
|
|
1334
|
+
`.trimStart()
|
|
1335
|
+
].filter((s) => !!s);
|
|
1336
|
+
errors.push(info2.join(`
|
|
1337
|
+
|
|
1338
|
+
`));
|
|
1339
|
+
}
|
|
1340
|
+
return hasCliConfig || errors.push(
|
|
1341
|
+
outdent$1`
|
|
1342
|
+
sanity.cli.(${suffixes.join(
|
|
1343
|
+
" | "
|
|
1344
|
+
)}) missing. Please create a file named sanity.cli.ts with the following content:
|
|
1345
|
+
|
|
1346
|
+
${chalk.green(
|
|
1347
|
+
outdent$1`
|
|
1348
|
+
import {createCliConfig} from 'sanity/cli'
|
|
1349
|
+
|
|
1350
|
+
export default createCliConfig({
|
|
1351
|
+
api: {
|
|
1352
|
+
projectId: '${sanityJson?.api?.projectId ?? "project-id"}',
|
|
1353
|
+
dataset: '${sanityJson?.api?.dataset ?? "dataset"}',
|
|
1354
|
+
}
|
|
1355
|
+
})`
|
|
1356
|
+
)}
|
|
1357
|
+
|
|
1358
|
+
Make sure to replace the projectId and dataset fields with your own.
|
|
1359
|
+
|
|
1360
|
+
For more, see ${urls.migrationGuideStudio}
|
|
1361
|
+
`.trimStart()
|
|
1362
|
+
), hasStudioConfig || errors.push(
|
|
1363
|
+
outdent$1`
|
|
1364
|
+
sanity.config.(${suffixes.join(
|
|
1365
|
+
" | "
|
|
1366
|
+
)}) missing. At a minimum sanity.config.ts should contain:
|
|
1367
|
+
|
|
1368
|
+
${chalk.green(
|
|
1369
|
+
outdent$1`
|
|
1370
|
+
import { defineConfig } from "sanity"
|
|
1371
|
+
import { deskTool } from "sanity/desk"
|
|
1372
|
+
|
|
1373
|
+
export default defineConfig({
|
|
1374
|
+
name: "default",
|
|
1375
|
+
|
|
1376
|
+
projectId: '${sanityJson?.api?.projectId ?? "project-id"}',
|
|
1377
|
+
dataset: '${sanityJson?.api?.dataset ?? "dataset"}',
|
|
1378
|
+
|
|
1379
|
+
plugins: [
|
|
1380
|
+
deskTool(),
|
|
1381
|
+
],
|
|
1382
|
+
|
|
1383
|
+
schema: {
|
|
1384
|
+
types: [
|
|
1385
|
+
/* put your v2 schema-types here */
|
|
1386
|
+
],
|
|
1387
|
+
},
|
|
1388
|
+
})`
|
|
1389
|
+
).trimStart()}
|
|
1390
|
+
|
|
1391
|
+
Make sure to replace the projectId and dataset fields with your own.
|
|
1392
|
+
|
|
1393
|
+
For more, see ${urls.migrationGuideStudio}
|
|
1394
|
+
`.trimStart()
|
|
1395
|
+
), errors.length ? [errors.join(`
|
|
1396
|
+
|
|
1397
|
+
---
|
|
1398
|
+
|
|
1399
|
+
`)] : [];
|
|
1400
|
+
}
|
|
1401
|
+
async function validatePluginSanityJson({
|
|
1402
|
+
basePath,
|
|
1403
|
+
packageJson
|
|
1404
|
+
}) {
|
|
1405
|
+
const sanityJson = await readJson5File({ basePath, filename: "sanity.json" }), expectedDefaults = {
|
|
1406
|
+
parts: [
|
|
1407
|
+
{
|
|
1408
|
+
implements: "part:@sanity/base/sanity-root",
|
|
1409
|
+
path: "./v2-incompatible.js"
|
|
1410
|
+
}
|
|
1411
|
+
]
|
|
1412
|
+
}, hasSinglePart = sanityJson && Object.keys(sanityJson).length === 1 && sanityJson?.parts && sanityJson.parts.length === 1, firstPart = hasSinglePart ? sanityJson?.parts?.[0] : void 0, correctImplements = firstPart?.implements === expectedDefaults.parts[0].implements, pathExists = firstPart?.path && await fileExists(path.normalize(path.join(basePath, firstPart.path))), hasDependency = !!packageJson.dependencies?.[incompatiblePluginPackage];
|
|
1413
|
+
if (!(sanityJson && hasSinglePart && correctImplements && pathExists && hasDependency)) {
|
|
1414
|
+
const errors = [
|
|
1415
|
+
sanityJson ? null : "sanity.json does not exist",
|
|
1416
|
+
hasSinglePart ? null : 'sanity.json should have exactly one entry in "parts", but did not.',
|
|
1417
|
+
correctImplements ? null : `The part should implement ${expectedDefaults.parts[0].implements}, but did not.`,
|
|
1418
|
+
firstPart?.path && !pathExists ? `The file in "path", ${firstPart?.path}, does not exist.` : null,
|
|
1419
|
+
hasDependency ? null : outdent$1`
|
|
1420
|
+
package.json should have ${incompatiblePluginPackage} as a dependency, but did not.
|
|
1421
|
+
Install it with: npm install --save ${incompatiblePluginPackage}
|
|
1422
|
+
`.trimStart()
|
|
1423
|
+
].filter((e) => !!e);
|
|
1424
|
+
return [
|
|
1425
|
+
outdent$1`
|
|
1426
|
+
Invalid sanity.json. It is used for compatibility checking in V2 studios:
|
|
1427
|
+
|
|
1428
|
+
- ${errors.join(`
|
|
1429
|
+
- `)}
|
|
1430
|
+
|
|
1431
|
+
sanity.json will only be used when incorrectly installing a v3 plugin in a v2 Studio.
|
|
1432
|
+
|
|
1433
|
+
This check ensures that sanity.json conforms with the usage section of
|
|
1434
|
+
${urls.incompatiblePlugin}
|
|
1435
|
+
`.trimStart()
|
|
1436
|
+
];
|
|
1437
|
+
}
|
|
1438
|
+
return [];
|
|
1439
|
+
}
|
|
1440
|
+
function validatePackageName$1(packageJson) {
|
|
1441
|
+
const valid = validateNpmPackageName(
|
|
1442
|
+
packageJson.name
|
|
1443
|
+
);
|
|
1444
|
+
return valid.validForNewPackages ? !packageJson.name?.startsWith("@") && !packageJson.name?.startsWith("sanity-plugin-") ? [
|
|
1445
|
+
'Invalid package.json: "name" should be prefixed with "sanity-plugin-" (or scoped - @your-company/plugin-name)'
|
|
1446
|
+
] : [] : [`Invalid package.json: "name" is invalid: ${valid.errors.join(", ")}`];
|
|
1447
|
+
}
|
|
1448
|
+
async function validateSrcIndexFile(basePath) {
|
|
1449
|
+
const paths = ["index.js", "index.ts"].map((p) => path.join("src", p)), allowedIndexFiles = paths.map((file) => path.join(basePath, file));
|
|
1450
|
+
let hasIndex = !1;
|
|
1451
|
+
for (const indexFile of allowedIndexFiles)
|
|
1452
|
+
hasIndex = hasIndex || await fileExists(indexFile);
|
|
1453
|
+
return hasIndex ? [] : [
|
|
1454
|
+
outdent$1`
|
|
1455
|
+
Expected one of [${paths.join(", ")}] to exist.
|
|
1456
|
+
|
|
1457
|
+
@sanity/pkg-utils expects a non-jsx file to be the source entry-point for the plugin.
|
|
1458
|
+
If you currently have JSX in your index file, extract it into a separate file and import it.
|
|
1459
|
+
`
|
|
1460
|
+
];
|
|
1461
|
+
}
|
|
1462
|
+
async function disallowDuplicateConfig({
|
|
1463
|
+
basePath,
|
|
1464
|
+
pkgJson,
|
|
1465
|
+
configKey,
|
|
1466
|
+
files: files2
|
|
1467
|
+
}) {
|
|
1468
|
+
const found = [];
|
|
1469
|
+
for (const file of files2) {
|
|
1470
|
+
const filePath = path.join(basePath, file);
|
|
1471
|
+
await fileExists(filePath) && found.push(file);
|
|
1472
|
+
}
|
|
1473
|
+
return found.length > 1 ? [
|
|
1474
|
+
outdent$1`
|
|
1475
|
+
Found multiple config files that serve the same purpose: [${found.join(", ")}].
|
|
1476
|
+
|
|
1477
|
+
There should be at most one of these files. Delete the rest.
|
|
1478
|
+
`
|
|
1479
|
+
] : found.length && pkgJson[configKey] ? [
|
|
1480
|
+
outdent$1`
|
|
1481
|
+
package.json contains ${configKey}, but there also exists a config file that serves the same purpose.
|
|
1482
|
+
Config file: ${found.join("")}]
|
|
1483
|
+
|
|
1484
|
+
Either delete the file or remove ${configKey} entry from package.json.
|
|
1485
|
+
`
|
|
1486
|
+
] : [];
|
|
1487
|
+
}
|
|
1488
|
+
async function disallowDuplicateEslintConfig(basePath, pkgJson) {
|
|
1489
|
+
return disallowDuplicateConfig({
|
|
1490
|
+
basePath,
|
|
1491
|
+
pkgJson,
|
|
1492
|
+
configKey: "eslint",
|
|
1493
|
+
files: [
|
|
1494
|
+
".eslintrc",
|
|
1495
|
+
".eslintrc.js",
|
|
1496
|
+
".eslintrc.cjs",
|
|
1497
|
+
".eslintrc.yaml",
|
|
1498
|
+
".eslintrc.yml",
|
|
1499
|
+
".eslintrc.json"
|
|
1500
|
+
]
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
async function disallowDuplicatePrettierConfig(basePath, pkgJson) {
|
|
1504
|
+
return disallowDuplicateConfig({
|
|
1505
|
+
basePath,
|
|
1506
|
+
pkgJson,
|
|
1507
|
+
configKey: "prettier",
|
|
1508
|
+
files: [
|
|
1509
|
+
".prettierrc",
|
|
1510
|
+
".prettierrc.json5",
|
|
1511
|
+
".prettierrc.json",
|
|
1512
|
+
".prettierrc.yaml",
|
|
1513
|
+
".prettierrc.yml",
|
|
1514
|
+
".prettierrc.js",
|
|
1515
|
+
".prettierrc.cjs",
|
|
1516
|
+
".prettier.config,js",
|
|
1517
|
+
".prettier.config.cjs",
|
|
1518
|
+
".prettierrc.toml"
|
|
1519
|
+
]
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
const defaultDependencies = [incompatiblePluginPackage], defaultDevDependencies = [
|
|
1523
|
+
"sanity",
|
|
1524
|
+
// peer dependencies of `sanity`
|
|
1525
|
+
"react",
|
|
1526
|
+
"react-dom",
|
|
1527
|
+
"styled-components"
|
|
1528
|
+
], defaultPeerDependencies = ["react", "sanity"], readFile = util.promisify(fs.readFile), pathKeys = ["main", "module", "browser", "types"];
|
|
1529
|
+
async function getPackage(opts) {
|
|
1530
|
+
const options = { flags: {}, ...opts };
|
|
1531
|
+
validateOptions(options);
|
|
1532
|
+
const { basePath, validate: validate2 = !0 } = options, manifestPath = path.normalize(path.join(basePath, "package.json"));
|
|
1533
|
+
let content;
|
|
1534
|
+
try {
|
|
1535
|
+
content = await readFile(manifestPath, "utf8");
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
throw err.code === "ENOENT" ? new Error(
|
|
1538
|
+
`No package.json found. package.json is required to publish to npm. Use \`${cliName} init\` for a new plugin, or \`npm init\` for an existing one`
|
|
1539
|
+
) : new Error(`Failed to read "${manifestPath}": ${err.message}`);
|
|
1540
|
+
}
|
|
1541
|
+
let parsed;
|
|
1542
|
+
try {
|
|
1543
|
+
parsed = JSON.parse(content);
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
throw new Error(`Error parsing "${manifestPath}": ${err.message}`);
|
|
1546
|
+
}
|
|
1547
|
+
if (!isObject(parsed))
|
|
1548
|
+
throw new Error("Invalid package.json: Root must be an object");
|
|
1549
|
+
return validate2 && await validatePackage(parsed, options), parsed;
|
|
1550
|
+
}
|
|
1551
|
+
async function validatePackage(manifest, opts) {
|
|
1552
|
+
validateOptions(opts);
|
|
1553
|
+
const options = { isPlugin: !0, ...opts };
|
|
1554
|
+
options.isPlugin && await validatePluginPackage(manifest, options), validateLockFiles(options);
|
|
1555
|
+
}
|
|
1556
|
+
function validateOptions(opts) {
|
|
1557
|
+
const options = opts || {};
|
|
1558
|
+
if (!isObject(options))
|
|
1559
|
+
throw new Error("Options must be an object");
|
|
1560
|
+
if (typeof options.basePath != "string")
|
|
1561
|
+
throw new Error('"options.basePath" must be a string (path to plugin base path)');
|
|
1562
|
+
}
|
|
1563
|
+
async function validatePluginPackage(manifest, options) {
|
|
1564
|
+
validatePackageName(manifest), await validatePaths(manifest, options);
|
|
1565
|
+
}
|
|
1566
|
+
function validatePackageName(manifest) {
|
|
1567
|
+
if (typeof manifest.name != "string")
|
|
1568
|
+
throw new Error('Invalid package.json: "name" must be a string');
|
|
1569
|
+
const valid = validateNpmPackageName(
|
|
1570
|
+
manifest.name
|
|
1571
|
+
);
|
|
1572
|
+
if (!valid.validForNewPackages)
|
|
1573
|
+
throw new Error(`Invalid package.json: "name" is invalid: ${valid.errors.join(", ")}`);
|
|
1574
|
+
if (manifest.name[0] !== "@" && !manifest.name.startsWith("sanity-plugin-"))
|
|
1575
|
+
throw new Error(
|
|
1576
|
+
'Invalid package.json: "name" should be prefixed with "sanity-plugin-" (or scoped - @your-company/plugin-name)'
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
async function validatePaths(manifest, options) {
|
|
1580
|
+
const paths = await getPaths({
|
|
1581
|
+
...options,
|
|
1582
|
+
pluginName: manifest.name ?? "unknown",
|
|
1583
|
+
verifySourceParts: !1,
|
|
1584
|
+
verifyCompiledParts: !1
|
|
1585
|
+
}), abs = (file) => path.isAbsolute(file) ? file : path.resolve(path.join(options.basePath, file)), exists = (file) => fs.existsSync(abs(file)), willExist = (file) => paths && hasSourceEquivalent(abs(file), paths), withinSourceDir = (file) => paths?.source && abs(file).startsWith(paths.source), withinTargetDir = (file) => paths?.compiled && abs(file).startsWith(paths.compiled);
|
|
1586
|
+
for (const key of pathKeys) {
|
|
1587
|
+
if (!(key in manifest))
|
|
1588
|
+
continue;
|
|
1589
|
+
const manifestValue = manifest[key];
|
|
1590
|
+
if (typeof manifestValue != "string")
|
|
1591
|
+
throw new Error(`Invalid package.json: "${key}" must be a string if defined`);
|
|
1592
|
+
if (!options?.flags?.allowSourceTarget && paths && withinSourceDir(manifestValue))
|
|
1593
|
+
throw new Error(
|
|
1594
|
+
`Invalid package.json: "${key}" points to file within source (uncompiled) directory. Use --allow-source-target if you really want to do this.`
|
|
1595
|
+
);
|
|
1596
|
+
if (exists(manifestValue) && paths && withinTargetDir(manifestValue) && !await willExist(manifestValue))
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
`Invalid package.json: "${key}" points to file that will not exist after compiling`
|
|
1599
|
+
);
|
|
1600
|
+
if (!exists(manifestValue) && !await willExist(manifestValue)) {
|
|
1601
|
+
if (!paths)
|
|
1602
|
+
throw new Error(`Invalid package.json: "${key}" points to file that does not exist`);
|
|
1603
|
+
const inOutDir = paths.compiled && !abs(manifestValue).startsWith(paths.compiled);
|
|
1604
|
+
throw new Error(
|
|
1605
|
+
inOutDir ? `Invalid package.json: "${key}" points to file that does not exist, and "paths" is not configured to compile to this location` : `Invalid package.json: "${key}" points to file that does not exist, and no equivalent is found in source directory`
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function isObject(obj) {
|
|
1611
|
+
return !Array.isArray(obj) && obj !== null && typeof obj == "object";
|
|
1612
|
+
}
|
|
1613
|
+
function validateLockFiles(options) {
|
|
1614
|
+
const npm = fs.existsSync(path.join(options.basePath, "package-lock.json")), yarn = fs.existsSync(path.join(options.basePath, "yarn.lock"));
|
|
1615
|
+
if (npm && yarn)
|
|
1616
|
+
throw new Error("Invalid plugin: contains both package-lock.json and yarn.lock");
|
|
1617
|
+
}
|
|
1618
|
+
async function writePackageJson(data, options) {
|
|
1619
|
+
const { user, pluginName, license, description, pkg: prevPkg, gitOrigin } = data, {
|
|
1620
|
+
outDir,
|
|
1621
|
+
peerDependencies: addPeers,
|
|
1622
|
+
dependencies: addDeps,
|
|
1623
|
+
devDependencies: addDevDeps
|
|
1624
|
+
} = options, { flags } = options, prev = prevPkg || {}, usePrettier = flags.prettier !== !1, useEslint = flags.eslint !== !1, useTypescript = flags.eslint !== !1, newDevDependencies = [cliName, "@sanity/pkg-utils"];
|
|
1625
|
+
useTypescript && (log.debug("Using TypeScript. Adding to dev dependencies."), newDevDependencies.push("@types/react", "typescript")), usePrettier && (log.debug("Using prettier. Adding to dev dependencies."), newDevDependencies.push("prettier", "prettier-plugin-packagejson")), useEslint && (log.debug("Using eslint. Adding to dev dependencies."), newDevDependencies.push(
|
|
1626
|
+
"eslint",
|
|
1627
|
+
"eslint-config-sanity",
|
|
1628
|
+
"eslint-plugin-react",
|
|
1629
|
+
"eslint-plugin-react-hooks"
|
|
1630
|
+
), usePrettier && newDevDependencies.push("eslint-config-prettier", "eslint-plugin-prettier"), useTypescript && newDevDependencies.push("@typescript-eslint/eslint-plugin", "@typescript-eslint/parser")), log.debug("Resolving latest versions for %s", newDevDependencies.join(", "));
|
|
1631
|
+
const dependencies = forceDependencyVersions(
|
|
1632
|
+
{
|
|
1633
|
+
...prev.dependencies || {},
|
|
1634
|
+
...addDeps || {},
|
|
1635
|
+
...await resolveLatestVersions(defaultDependencies)
|
|
1636
|
+
},
|
|
1637
|
+
forcedPackageVersions
|
|
1638
|
+
), devDependencies2 = forceDependencyVersions(
|
|
1639
|
+
{
|
|
1640
|
+
...addDevDeps || {},
|
|
1641
|
+
...prev.devDependencies || {},
|
|
1642
|
+
...await resolveLatestVersions([...newDevDependencies, ...defaultDevDependencies])
|
|
1643
|
+
},
|
|
1644
|
+
forcedDevPackageVersions
|
|
1645
|
+
), peerDependencies = forceDependencyVersions(
|
|
1646
|
+
{
|
|
1647
|
+
...prev.peerDependencies || {},
|
|
1648
|
+
...addPeers || {},
|
|
1649
|
+
...await resolveLatestVersions(defaultPeerDependencies)
|
|
1650
|
+
},
|
|
1651
|
+
forcedPeerPackageVersions
|
|
1652
|
+
), source = flags.typescript ? "./src/index.ts" : "./src/index.js", files2 = [outDir, "sanity.json", "src", "v2-incompatible.js"];
|
|
1653
|
+
files2.sort();
|
|
1654
|
+
const forcedOrder = {
|
|
1655
|
+
name: pluginName,
|
|
1656
|
+
version: prev.version ?? "1.0.0",
|
|
1657
|
+
description: description || "",
|
|
1658
|
+
keywords: prev.keywords ?? ["sanity", "sanity-plugin"],
|
|
1659
|
+
...urlsFromOrigin(gitOrigin),
|
|
1660
|
+
...repoFromOrigin(gitOrigin),
|
|
1661
|
+
license: license ? license.id : "UNLICENSED",
|
|
1662
|
+
author: user?.email ? `${user.name} <${user.email}>` : user?.name,
|
|
1663
|
+
sideEffects: !1,
|
|
1664
|
+
type: "module",
|
|
1665
|
+
exports: {
|
|
1666
|
+
".": {
|
|
1667
|
+
source,
|
|
1668
|
+
default: `./${outDir}/index.js`
|
|
1669
|
+
},
|
|
1670
|
+
"./package.json": "./package.json"
|
|
1671
|
+
},
|
|
1672
|
+
...flags.typescript ? { types: `./${outDir}/index.d.ts` } : {},
|
|
1673
|
+
files: files2,
|
|
1674
|
+
scripts: { ...prev.scripts },
|
|
1675
|
+
dependencies: sortKeys(dependencies),
|
|
1676
|
+
devDependencies: sortKeys(devDependencies2),
|
|
1677
|
+
peerDependencies: sortKeys(peerDependencies),
|
|
1678
|
+
engines: {
|
|
1679
|
+
node: requiredNodeEngine
|
|
1680
|
+
}
|
|
1681
|
+
}, manifest = {
|
|
1682
|
+
...forcedOrder,
|
|
1683
|
+
// Use already configured values by default (if not otherwise specified)
|
|
1684
|
+
...prev || {},
|
|
1685
|
+
// We're de-declaring properties because of key order in package.json
|
|
1686
|
+
...forcedOrder
|
|
1687
|
+
}, differs = JSON.stringify(prev) !== JSON.stringify(manifest);
|
|
1688
|
+
return log.debug("Does manifest differ? %s", differs ? "yes" : "no"), differs && await writePackageJsonDirect(manifest, options), differs ? manifest : prev;
|
|
1689
|
+
}
|
|
1690
|
+
function urlsFromOrigin(gitOrigin) {
|
|
1691
|
+
const details = githubUrlToObject(gitOrigin);
|
|
1692
|
+
return details ? {
|
|
1693
|
+
homepage: `https://github.com/${details.user}/${details.repo}#readme`,
|
|
1694
|
+
bugs: {
|
|
1695
|
+
url: `https://github.com/${details.user}/${details.repo}/issues`
|
|
1696
|
+
}
|
|
1697
|
+
} : {};
|
|
1698
|
+
}
|
|
1699
|
+
function repoFromOrigin(gitOrigin) {
|
|
1700
|
+
return gitOrigin ? {
|
|
1701
|
+
repository: {
|
|
1702
|
+
type: "git",
|
|
1703
|
+
url: gitOrigin
|
|
1704
|
+
}
|
|
1705
|
+
} : {};
|
|
1706
|
+
}
|
|
1707
|
+
function addScript(cmd, existing) {
|
|
1708
|
+
return existing && existing.includes(cmd) ? existing : cmd;
|
|
1709
|
+
}
|
|
1710
|
+
async function addPackageJsonScripts(manifest, options, updateScripts) {
|
|
1711
|
+
const originalScripts = manifest.scripts || {}, scripts = updateScripts({ ...originalScripts }), differs = Object.keys(scripts).some((key) => scripts[key] !== originalScripts[key]);
|
|
1712
|
+
return differs && await writePackageJsonDirect({ ...manifest, scripts }, options), differs;
|
|
1713
|
+
}
|
|
1714
|
+
async function writePackageJsonDirect(manifest, { basePath }) {
|
|
1715
|
+
await writeJsonFile(path.join(basePath, "package.json"), manifest);
|
|
1716
|
+
}
|
|
1717
|
+
async function addBuildScripts(manifest, options) {
|
|
1718
|
+
return options.flags.scripts ? addPackageJsonScripts(manifest, options, (scripts) => (scripts.build = addScript(expectedScripts.build, scripts.build), scripts.format = addScript("prettier --write --cache --ignore-unknown .", scripts.format), scripts["link-watch"] = addScript(expectedScripts["link-watch"], scripts["link-watch"]), scripts.lint = addScript("eslint .", scripts.lint), scripts.prepublishOnly = addScript(expectedScripts.prepublishOnly, scripts.prepublishOnly), scripts.watch = addScript(expectedScripts.watch, scripts.watch), scripts)) : !1;
|
|
1719
|
+
}
|
|
1720
|
+
function sortKeys(unordered) {
|
|
1721
|
+
return Object.keys(unordered).sort().reduce((obj, key) => (obj[key] = unordered[key], obj), {});
|
|
1722
|
+
}
|
|
1723
|
+
function forceDependencyVersions(deps, versions = forcedPackageVersions) {
|
|
1724
|
+
const entries = Object.entries(deps).map((entry) => {
|
|
1725
|
+
const [pkg2] = entry, forceVersion = versions[pkg2];
|
|
1726
|
+
return forceVersion ? [pkg2, forceVersion] : entry;
|
|
1727
|
+
});
|
|
1728
|
+
return Object.fromEntries(entries);
|
|
1729
|
+
}
|
|
1730
|
+
export {
|
|
1731
|
+
disallowDuplicateEslintConfig,
|
|
1732
|
+
disallowDuplicatePrettierConfig,
|
|
1733
|
+
ensureDir,
|
|
1734
|
+
fileExists,
|
|
1735
|
+
findStudioV3Config,
|
|
1736
|
+
getPackage,
|
|
1737
|
+
hasSanityJson,
|
|
1738
|
+
inject,
|
|
1739
|
+
isEmptyish,
|
|
1740
|
+
mergedPackages,
|
|
1741
|
+
mkdir,
|
|
1742
|
+
presetHelpList,
|
|
1743
|
+
prompt,
|
|
1744
|
+
validateBabelConfig,
|
|
1745
|
+
validateDeprecatedDependencies,
|
|
1746
|
+
validateNodeEngine,
|
|
1747
|
+
validatePackageName$1 as validatePackageName,
|
|
1748
|
+
validatePackageType,
|
|
1749
|
+
validatePkgUtilsDependency,
|
|
1750
|
+
validatePkgUtilsVersion,
|
|
1751
|
+
validatePluginSanityJson,
|
|
1752
|
+
validateSanityDependencies,
|
|
1753
|
+
validateScripts,
|
|
1754
|
+
validateSrcIndexFile,
|
|
1755
|
+
validateStudioConfig,
|
|
1756
|
+
validateTsConfig,
|
|
1757
|
+
writeFile
|
|
1758
|
+
};
|
|
1759
|
+
//# sourceMappingURL=package.js.map
|