@soeditor/plugin-tools 1.4.0 → 1.5.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/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { argv, stdout, stderr } from "node:process";
3
- import { s as scaffoldPluginPackage, c as checkPluginPackage } from "./scaffold-DQxtxXfQ.js";
3
+ import { s as scaffoldPluginPackage, c as checkPluginPackage } from "./scaffold-BVE6s3e1.js";
4
4
  try {
5
5
  const [command, target, ...flags] = argv.slice(2);
6
6
  if (command === "create" && target !== void 0) {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { c, s } from "./scaffold-DQxtxXfQ.js";
1
+ import { c, s } from "./scaffold-BVE6s3e1.js";
2
2
  const pluginTemplateVersion = 3;
3
3
  export {
4
4
  c as checkPluginPackage,
@@ -154,7 +154,12 @@ async function checkPacked(root, issues) {
154
154
  const { stdout } = await execute(
155
155
  "npm",
156
156
  ["pack", "--dry-run", "--ignore-scripts", "--json"],
157
- { cwd: root, encoding: "utf8", maxBuffer: 2e6 }
157
+ {
158
+ cwd: root,
159
+ encoding: "utf8",
160
+ maxBuffer: 2e6,
161
+ timeout: 2e4
162
+ }
158
163
  );
159
164
  const result = JSON.parse(stdout);
160
165
  const entry = Array.isArray(result) ? record(result[0]) : {};
@@ -508,4 +513,4 @@ export {
508
513
  checkPluginPackage as c,
509
514
  scaffoldPluginPackage as s
510
515
  };
511
- //# sourceMappingURL=scaffold-DQxtxXfQ.js.map
516
+ //# sourceMappingURL=scaffold-BVE6s3e1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-BVE6s3e1.js","sources":["../src/plugin-check.ts","../src/scaffold.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { readFile, readdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execute = promisify(execFile);\n\nexport interface PluginCheckOptions {\n readonly packed?: boolean;\n}\n\nexport interface PluginCheckIssue {\n readonly code: string;\n readonly message: string;\n}\n\nexport interface PluginCheckReport {\n readonly issues: readonly PluginCheckIssue[];\n readonly packageName: string | undefined;\n readonly pluginIds: readonly string[];\n readonly valid: boolean;\n}\n\n/** Audits a plugin package without importing or executing its source. */\nexport async function checkPluginPackage(\n directory: string,\n options: PluginCheckOptions = {},\n): Promise<PluginCheckReport> {\n const root = resolve(directory);\n const issues: PluginCheckIssue[] = [];\n const manifest = await jsonRecord(resolve(root, 'package.json'), issues);\n checkManifest(manifest, issues);\n const sourceFiles = await sourcePaths(resolve(root, 'src'));\n const sources = await Promise.all(\n sourceFiles.map(async (path) => ({\n path,\n source: await readFile(path, 'utf8'),\n })),\n );\n const pluginIds = sources.flatMap(({ source }) =>\n [\n ...source.matchAll(\n /static\\s+readonly\\s+id\\s*=\\s*['\"]([^'\"]+)['\"]/gu,\n ),\n ].map((match) => match[1]!),\n );\n if (pluginIds.length === 0) {\n issue(\n issues,\n 'PLUGIN_ID_MISSING',\n 'No static readonly plugin ID was found.',\n );\n }\n const duplicates = pluginIds.filter(\n (id, index) => pluginIds.indexOf(id) !== index,\n );\n for (const id of pluginIds) {\n if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) {\n issue(\n issues,\n 'PLUGIN_ID_FORMAT',\n `Plugin ID \"${id}\" must use lowercase letters, numbers, dots, underscores, or hyphens.`,\n );\n }\n }\n for (const id of new Set(duplicates)) {\n issue(\n issues,\n 'PLUGIN_ID_DUPLICATE',\n `Plugin ID \"${id}\" is duplicated.`,\n );\n }\n for (const { path, source } of sources) {\n if (\n /['\"]@soeditor\\/[^/'\"]+\\/(?:src|dist|internal)(?:\\/[^'\"]*)?['\"]/u.test(\n source,\n )\n ) {\n issue(\n issues,\n 'INTERNAL_IMPORT',\n `${path} imports an unsupported SoEditor internal path.`,\n );\n }\n if (/(?:from\\s+|import\\s*\\()\\s*['\"](?:https?:)?\\/\\//u.test(source)) {\n issue(\n issues,\n 'REMOTE_IMPORT',\n `${path} imports executable code from a remote URL.`,\n );\n }\n if (/\\b(?:eval\\s*\\(|new\\s+Function\\s*\\()/u.test(source)) {\n issue(\n issues,\n 'DYNAMIC_CODE',\n `${path} contains dynamic code evaluation.`,\n );\n }\n if (\n /\\.innerHTML\\s*=|\\.outerHTML\\s*=|\\.insertAdjacentHTML\\s*\\(/u.test(\n source,\n )\n ) {\n issue(\n issues,\n 'UNSAFE_DOM_OUTPUT',\n `${path} writes untrusted HTML through a DOM injection sink.`,\n );\n }\n }\n if (\n !sources.some(({ source }) =>\n /from\\s+['\"]@soeditor\\/plugin-sdk['\"]/u.test(source),\n )\n ) {\n issue(\n issues,\n 'SDK_CONTRIBUTION_MISSING',\n 'Plugin source must consume contributions from the public @soeditor/plugin-sdk root.',\n );\n }\n const rootSource = sources.find(({ path }) =>\n path.endsWith('/src/index.ts'),\n )?.source;\n if (rootSource === undefined || !/export\\s+\\{/u.test(rootSource)) {\n issue(\n issues,\n 'ROOT_EXPORT_MISSING',\n 'src/index.ts must explicitly export the plugin contribution.',\n );\n }\n if (options.packed === true) await checkPacked(root, issues);\n return Object.freeze({\n issues: Object.freeze(issues.map((value) => Object.freeze(value))),\n packageName:\n typeof manifest.name === 'string' ? manifest.name : undefined,\n pluginIds: Object.freeze(pluginIds),\n valid: issues.length === 0,\n });\n}\n\nfunction checkManifest(\n manifest: Record<string, unknown>,\n issues: PluginCheckIssue[],\n): void {\n if (typeof manifest.name !== 'string' || manifest.name.length === 0) {\n issue(issues, 'MANIFEST_NAME', 'package.json requires a package name.');\n }\n if (\n typeof manifest.version !== 'string' ||\n !/^\\d+\\.\\d+\\.\\d+(?:-|$)/u.test(manifest.version)\n ) {\n issue(\n issues,\n 'MANIFEST_VERSION',\n 'package.json requires a SemVer version.',\n );\n }\n if (manifest.type !== 'module') {\n issue(issues, 'ESM_REQUIRED', 'Plugin packages must use type: module.');\n }\n if (manifest.sideEffects !== false) {\n issue(\n issues,\n 'TREE_SHAKING',\n 'Plugin packages must declare sideEffects: false.',\n );\n }\n const exports = record(manifest.exports);\n const rootExport = record(exports['.']);\n if (\n typeof rootExport.import !== 'string' ||\n !rootExport.import.startsWith('./dist/') ||\n typeof rootExport.types !== 'string' ||\n !rootExport.types.startsWith('./dist/')\n ) {\n issue(\n issues,\n 'EXPORT_MAP',\n 'The package root must expose dist import and types entries.',\n );\n }\n const peers = record(manifest.peerDependencies);\n const sdkRange = peers['@soeditor/plugin-sdk'];\n if (typeof sdkRange !== 'string' || !/(?:\\^1\\.|>=\\s*1\\.)/u.test(sdkRange)) {\n issue(\n issues,\n 'SDK_PEER_RANGE',\n 'Declare @soeditor/plugin-sdk as a compatible peer dependency.',\n );\n }\n}\n\nasync function checkPacked(\n root: string,\n issues: PluginCheckIssue[],\n): Promise<void> {\n try {\n const { stdout } = await execute(\n 'npm',\n ['pack', '--dry-run', '--ignore-scripts', '--json'],\n {\n cwd: root,\n encoding: 'utf8',\n maxBuffer: 2_000_000,\n timeout: 20_000,\n },\n );\n const result: unknown = JSON.parse(stdout);\n const entry = Array.isArray(result) ? record(result[0]) : {};\n const files = Array.isArray(entry.files)\n ? entry.files.flatMap((value) => {\n const path = record(value).path;\n return typeof path === 'string' ? [path] : [];\n })\n : [];\n for (const required of [\n 'dist/index.js',\n 'dist/index.d.ts',\n 'package.json',\n ]) {\n if (!files.includes(required)) {\n issue(\n issues,\n 'PACKED_FILE_MISSING',\n `Packed artifact is missing ${required}.`,\n );\n }\n }\n if (files.some((path) => path.startsWith('src/'))) {\n issue(\n issues,\n 'PACKED_SOURCE',\n 'Packed artifact must not expose src files.',\n );\n }\n } catch (error: unknown) {\n issue(\n issues,\n 'PACK_FAILED',\n `Unable to inspect packed artifact: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\nasync function sourcePaths(directory: string): Promise<string[]> {\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n return [];\n }\n const paths = await Promise.all(\n entries.map(async (entry) => {\n const path = resolve(directory, entry.name);\n return entry.isDirectory()\n ? sourcePaths(path)\n : /\\.[cm]?tsx?$/u.test(entry.name)\n ? [path]\n : [];\n }),\n );\n return paths.flat().sort();\n}\n\nasync function jsonRecord(\n path: string,\n issues: PluginCheckIssue[],\n): Promise<Record<string, unknown>> {\n try {\n return record(JSON.parse(await readFile(path, 'utf8')));\n } catch (error: unknown) {\n issue(\n issues,\n 'MANIFEST_READ',\n `Unable to read package.json: ${error instanceof Error ? error.message : String(error)}`,\n );\n return {};\n }\n}\n\nfunction record(value: unknown): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return {};\n }\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) result[key] = Reflect.get(value, key);\n return result;\n}\n\nfunction issue(\n issues: PluginCheckIssue[],\n code: string,\n message: string,\n): void {\n issues.push(Object.freeze({ code, message }));\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nexport interface ScaffoldPluginOptions {\n readonly directory: string;\n readonly kind?: PluginTemplateKind;\n readonly packageName: string;\n readonly pluginId: string;\n}\n\n/** Focused offline contribution families supported by template version 3. */\nexport type PluginTemplateKind =\n 'basic' | 'cms-widget' | 'paste' | 'theme' | 'upload';\n\n/** Creates a versioned, strict plugin package without overwriting a directory. */\nexport async function scaffoldPluginPackage(\n options: ScaffoldPluginOptions,\n): Promise<string> {\n const directory = resolve(nonEmpty(options.directory, 'directory'));\n const packageName = packageValue(options.packageName);\n const pluginId = pluginIdValue(options.pluginId);\n const kind = kindValue(options.kind ?? 'basic');\n await mkdir(directory);\n await mkdir(resolve(directory, 'src'));\n await mkdir(resolve(directory, 'tests'));\n const className = classNameFrom(packageName);\n await Promise.all([\n write(directory, 'package.json', manifest(packageName)),\n write(directory, 'tsconfig.json', tsconfig()),\n write(directory, 'vite.config.ts', viteConfig()),\n write(\n directory,\n 'pnpm-workspace.yaml',\n 'allowBuilds:\\n esbuild: true\\n\\nonlyBuiltDependencies:\\n - esbuild\\n',\n ),\n write(\n directory,\n 'README.md',\n `# ${packageName}\\n\\nSoEditor plugin package.\\n`,\n ),\n write(directory, 'src/index.ts', indexSource(className, kind)),\n write(\n directory,\n 'src/plugin.ts',\n pluginSource(className, pluginId, kind),\n ),\n ]);\n return directory;\n}\n\nfunction indexSource(className: string, kind: PluginTemplateKind): string {\n const extras =\n kind === 'cms-widget'\n ? `, cmsObjectDefinitions`\n : kind === 'theme'\n ? `, editorIcons, editorThemeVariables`\n : '';\n return `export { ${className}${extras} } from './plugin.js';\\n`;\n}\n\nfunction pluginSource(\n className: string,\n pluginId: string,\n kind: PluginTemplateKind,\n): string {\n const id = escapeSingle(pluginId);\n if (kind === 'cms-widget') {\n return `import { CmsObjectsPlugin, Plugin, UiPlugin, uiRegistryServiceToken, type CmsObjectDefinition } from '@soeditor/plugin-sdk';\\n\\nexport const cmsObjectDefinitions = Object.freeze([\\n { id: 'product-card', label: 'Product card', properties: ['product-id'] },\\n] satisfies readonly CmsObjectDefinition[]);\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n static readonly requires = [CmsObjectsPlugin, UiPlugin];\\n #commandRegistered = false;\\n #dispose: (() => void)[] = [];\\n\\n override init(): void {\\n this.editor.commands.register({\\n id: '${id}.inspect',\\n canExecute: () => true,\\n execute: () => undefined,\\n });\\n this.#commandRegistered = true;\\n try {\\n this.#dispose.push(\\n this.editor.services.get(uiRegistryServiceToken).registerContextMenuItem('${id}.inspect', {\\n command: '${id}.inspect',\\n label: 'Inspect product card',\\n when: ({ target }) => target.closest('[data-soeditor-object=\"product-card\"]') !== null,\\n }),\\n );\\n } catch (error: unknown) {\\n this.editor.commands.unregister('${id}.inspect');\\n this.#commandRegistered = false;\\n throw error;\\n }\\n }\\n\\n override destroy(): void {\\n for (const dispose of this.#dispose.reverse()) dispose();\\n this.#dispose = [];\\n if (this.#commandRegistered) this.editor.commands.unregister('${id}.inspect');\\n this.#commandRegistered = false;\\n }\\n}\\n`;\n }\n if (kind === 'paste') {\n return `import { PastePipelinePlugin, Plugin, pastePipelineServiceToken } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n static readonly requires = [PastePipelinePlugin];\\n #dispose: (() => void) | undefined;\\n\\n override init(): void {\\n this.#dispose = this.editor.services.get(pastePipelineServiceToken).register({\\n id: '${id}.processor',\\n priority: 10,\\n process: (context) => context.classification === 'web'\\n ? { html: context.html.replaceAll(/<font\\\\b[^>]*>/giu, '<span>').replaceAll('</font>', '</span>') }\\n : undefined,\\n });\\n }\\n\\n override destroy(): void {\\n this.#dispose?.();\\n this.#dispose = undefined;\\n }\\n}\\n`;\n }\n if (kind === 'upload') {\n return `import { Plugin, uploadServiceToken, type UploadService } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n #service: UploadService | undefined;\\n\\n override init(): void {\\n const service = this.editor.config.get<unknown>('cms.upload.service');\\n if (!isUploadService(service)) throw new TypeError('cms.upload.service must provide create(request).');\\n this.editor.services.register(uploadServiceToken, service);\\n this.#service = service;\\n }\\n\\n override destroy(): void {\\n if (this.editor.services.tryGet(uploadServiceToken) === this.#service) {\\n this.editor.services.unregister(uploadServiceToken);\\n }\\n this.#service = undefined;\\n }\\n}\\n\\nfunction isUploadService(value: unknown): value is UploadService {\\n return typeof value === 'object' && value !== null && typeof Reflect.get(value, 'create') === 'function';\\n}\\n`;\n }\n if (kind === 'theme') {\n return `import { Plugin, type EditorUiIconResource, type EditorUiThemeVariables } from '@soeditor/plugin-sdk';\\n\\nexport const editorThemeVariables = Object.freeze({\\n accent: '#005ea8',\\n focusRing: '#ffbf47',\\n radius: '0.25rem',\\n} satisfies EditorUiThemeVariables);\\n\\nexport const editorIcons = Object.freeze({\\n 'format.bold': 'B',\\n 'format.italic': 'I',\\n} satisfies EditorUiIconResource);\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n}\\n`;\n }\n return `import { Plugin } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n}\\n`;\n}\n\nfunction manifest(name: string): string {\n return `${JSON.stringify(\n {\n name,\n version: '0.1.0',\n license: 'MIT',\n packageManager: 'pnpm@11.20.0',\n type: 'module',\n sideEffects: false,\n files: ['dist'],\n exports: {\n '.': { types: './dist/index.d.ts', import: './dist/index.js' },\n },\n scripts: {\n build: 'vite build && tsc -p tsconfig.json --emitDeclarationOnly --declaration --outDir dist',\n check: 'soeditor-plugin check . --packed',\n },\n peerDependencies: { '@soeditor/plugin-sdk': '^1.0.0' },\n devDependencies: {\n '@soeditor/comments': '^1.0.0',\n '@soeditor/core': '^1.0.0',\n '@soeditor/engine': '^1.0.0',\n '@soeditor/file-manager': '^1.0.0',\n '@soeditor/html-tools': '^1.0.0',\n '@soeditor/html': '^1.0.0',\n '@soeditor/layout': '^1.0.0',\n '@soeditor/plugin-sdk': '^1.0.0',\n '@soeditor/plugin-tools': '^1.0.0',\n '@soeditor/projections': '^1.0.0',\n '@soeditor/revisions': '^1.0.0',\n '@soeditor/rich-text': '^1.0.0',\n '@soeditor/ui': '^1.0.0',\n typescript: '^5.9.0',\n vite: '^7.0.0',\n },\n },\n undefined,\n 4,\n )}\\n`;\n}\n\nfunction tsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n declaration: true,\n declarationMap: true,\n emitDeclarationOnly: true,\n exactOptionalPropertyTypes: true,\n module: 'ESNext',\n moduleResolution: 'Bundler',\n outDir: 'dist',\n strict: true,\n target: 'ES2022',\n },\n include: ['src/**/*.ts'],\n },\n undefined,\n 4,\n )}\\n`;\n}\n\nfunction viteConfig(): string {\n return \"import { defineConfig } from 'vite';\\n\\nexport default defineConfig({\\n build: {\\n lib: { entry: 'src/index.ts', formats: ['es'], fileName: 'index' },\\n rollupOptions: { external: ['@soeditor/plugin-sdk'] },\\n sourcemap: true,\\n },\\n});\\n\";\n}\n\nasync function write(\n root: string,\n path: string,\n source: string,\n): Promise<void> {\n await writeFile(resolve(root, path), source, {\n encoding: 'utf8',\n flag: 'wx',\n });\n}\n\nfunction packageValue(value: string): string {\n const name = nonEmpty(value, 'package name');\n if (!/^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/u.test(name)) {\n throw new TypeError(`Invalid npm package name \"${name}\".`);\n }\n return name;\n}\n\nfunction pluginIdValue(value: string): string {\n const id = nonEmpty(value, 'plugin ID');\n if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) {\n throw new TypeError(\n `Invalid plugin ID \"${id}\"; use lowercase letters, numbers, dots, underscores, or hyphens.`,\n );\n }\n return id;\n}\n\nfunction kindValue(value: string): PluginTemplateKind {\n if (\n value !== 'basic' &&\n value !== 'cms-widget' &&\n value !== 'paste' &&\n value !== 'theme' &&\n value !== 'upload'\n ) {\n throw new TypeError(\n `Invalid plugin template kind \"${value}\"; use basic, cms-widget, paste, upload, or theme.`,\n );\n }\n return value;\n}\n\nfunction classNameFrom(name: string): string {\n const stem = basename(name).replace(\n /[^a-zA-Z0-9]+(.)/gu,\n (_match, next: string) => next.toUpperCase(),\n );\n const normalized = stem.replace(/^[^a-zA-Z]+/u, '');\n return `${normalized.length === 0 ? 'SoEditor' : normalized[0]!.toUpperCase() + normalized.slice(1)}Plugin`;\n}\n\nfunction nonEmpty(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new TypeError(`Plugin ${label} must be a non-empty string.`);\n }\n return value.trim();\n}\n\nfunction escapeSingle(value: string): string {\n return value.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\");\n}\n"],"names":["manifest"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soeditor/plugin-tools",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "license": "MIT",
5
5
  "description": "Offline plugin scaffolding and package checks for SoEditor.",
6
6
  "keywords": [
@@ -1 +0,0 @@
1
- {"version":3,"file":"scaffold-DQxtxXfQ.js","sources":["../src/plugin-check.ts","../src/scaffold.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { readFile, readdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execute = promisify(execFile);\n\nexport interface PluginCheckOptions {\n readonly packed?: boolean;\n}\n\nexport interface PluginCheckIssue {\n readonly code: string;\n readonly message: string;\n}\n\nexport interface PluginCheckReport {\n readonly issues: readonly PluginCheckIssue[];\n readonly packageName: string | undefined;\n readonly pluginIds: readonly string[];\n readonly valid: boolean;\n}\n\n/** Audits a plugin package without importing or executing its source. */\nexport async function checkPluginPackage(\n directory: string,\n options: PluginCheckOptions = {},\n): Promise<PluginCheckReport> {\n const root = resolve(directory);\n const issues: PluginCheckIssue[] = [];\n const manifest = await jsonRecord(resolve(root, 'package.json'), issues);\n checkManifest(manifest, issues);\n const sourceFiles = await sourcePaths(resolve(root, 'src'));\n const sources = await Promise.all(\n sourceFiles.map(async (path) => ({\n path,\n source: await readFile(path, 'utf8'),\n })),\n );\n const pluginIds = sources.flatMap(({ source }) =>\n [\n ...source.matchAll(\n /static\\s+readonly\\s+id\\s*=\\s*['\"]([^'\"]+)['\"]/gu,\n ),\n ].map((match) => match[1]!),\n );\n if (pluginIds.length === 0) {\n issue(\n issues,\n 'PLUGIN_ID_MISSING',\n 'No static readonly plugin ID was found.',\n );\n }\n const duplicates = pluginIds.filter(\n (id, index) => pluginIds.indexOf(id) !== index,\n );\n for (const id of pluginIds) {\n if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) {\n issue(\n issues,\n 'PLUGIN_ID_FORMAT',\n `Plugin ID \"${id}\" must use lowercase letters, numbers, dots, underscores, or hyphens.`,\n );\n }\n }\n for (const id of new Set(duplicates)) {\n issue(\n issues,\n 'PLUGIN_ID_DUPLICATE',\n `Plugin ID \"${id}\" is duplicated.`,\n );\n }\n for (const { path, source } of sources) {\n if (\n /['\"]@soeditor\\/[^/'\"]+\\/(?:src|dist|internal)(?:\\/[^'\"]*)?['\"]/u.test(\n source,\n )\n ) {\n issue(\n issues,\n 'INTERNAL_IMPORT',\n `${path} imports an unsupported SoEditor internal path.`,\n );\n }\n if (/(?:from\\s+|import\\s*\\()\\s*['\"](?:https?:)?\\/\\//u.test(source)) {\n issue(\n issues,\n 'REMOTE_IMPORT',\n `${path} imports executable code from a remote URL.`,\n );\n }\n if (/\\b(?:eval\\s*\\(|new\\s+Function\\s*\\()/u.test(source)) {\n issue(\n issues,\n 'DYNAMIC_CODE',\n `${path} contains dynamic code evaluation.`,\n );\n }\n if (\n /\\.innerHTML\\s*=|\\.outerHTML\\s*=|\\.insertAdjacentHTML\\s*\\(/u.test(\n source,\n )\n ) {\n issue(\n issues,\n 'UNSAFE_DOM_OUTPUT',\n `${path} writes untrusted HTML through a DOM injection sink.`,\n );\n }\n }\n if (\n !sources.some(({ source }) =>\n /from\\s+['\"]@soeditor\\/plugin-sdk['\"]/u.test(source),\n )\n ) {\n issue(\n issues,\n 'SDK_CONTRIBUTION_MISSING',\n 'Plugin source must consume contributions from the public @soeditor/plugin-sdk root.',\n );\n }\n const rootSource = sources.find(({ path }) =>\n path.endsWith('/src/index.ts'),\n )?.source;\n if (rootSource === undefined || !/export\\s+\\{/u.test(rootSource)) {\n issue(\n issues,\n 'ROOT_EXPORT_MISSING',\n 'src/index.ts must explicitly export the plugin contribution.',\n );\n }\n if (options.packed === true) await checkPacked(root, issues);\n return Object.freeze({\n issues: Object.freeze(issues.map((value) => Object.freeze(value))),\n packageName:\n typeof manifest.name === 'string' ? manifest.name : undefined,\n pluginIds: Object.freeze(pluginIds),\n valid: issues.length === 0,\n });\n}\n\nfunction checkManifest(\n manifest: Record<string, unknown>,\n issues: PluginCheckIssue[],\n): void {\n if (typeof manifest.name !== 'string' || manifest.name.length === 0) {\n issue(issues, 'MANIFEST_NAME', 'package.json requires a package name.');\n }\n if (\n typeof manifest.version !== 'string' ||\n !/^\\d+\\.\\d+\\.\\d+(?:-|$)/u.test(manifest.version)\n ) {\n issue(\n issues,\n 'MANIFEST_VERSION',\n 'package.json requires a SemVer version.',\n );\n }\n if (manifest.type !== 'module') {\n issue(issues, 'ESM_REQUIRED', 'Plugin packages must use type: module.');\n }\n if (manifest.sideEffects !== false) {\n issue(\n issues,\n 'TREE_SHAKING',\n 'Plugin packages must declare sideEffects: false.',\n );\n }\n const exports = record(manifest.exports);\n const rootExport = record(exports['.']);\n if (\n typeof rootExport.import !== 'string' ||\n !rootExport.import.startsWith('./dist/') ||\n typeof rootExport.types !== 'string' ||\n !rootExport.types.startsWith('./dist/')\n ) {\n issue(\n issues,\n 'EXPORT_MAP',\n 'The package root must expose dist import and types entries.',\n );\n }\n const peers = record(manifest.peerDependencies);\n const sdkRange = peers['@soeditor/plugin-sdk'];\n if (typeof sdkRange !== 'string' || !/(?:\\^1\\.|>=\\s*1\\.)/u.test(sdkRange)) {\n issue(\n issues,\n 'SDK_PEER_RANGE',\n 'Declare @soeditor/plugin-sdk as a compatible peer dependency.',\n );\n }\n}\n\nasync function checkPacked(\n root: string,\n issues: PluginCheckIssue[],\n): Promise<void> {\n try {\n const { stdout } = await execute(\n 'npm',\n ['pack', '--dry-run', '--ignore-scripts', '--json'],\n { cwd: root, encoding: 'utf8', maxBuffer: 2_000_000 },\n );\n const result: unknown = JSON.parse(stdout);\n const entry = Array.isArray(result) ? record(result[0]) : {};\n const files = Array.isArray(entry.files)\n ? entry.files.flatMap((value) => {\n const path = record(value).path;\n return typeof path === 'string' ? [path] : [];\n })\n : [];\n for (const required of [\n 'dist/index.js',\n 'dist/index.d.ts',\n 'package.json',\n ]) {\n if (!files.includes(required)) {\n issue(\n issues,\n 'PACKED_FILE_MISSING',\n `Packed artifact is missing ${required}.`,\n );\n }\n }\n if (files.some((path) => path.startsWith('src/'))) {\n issue(\n issues,\n 'PACKED_SOURCE',\n 'Packed artifact must not expose src files.',\n );\n }\n } catch (error: unknown) {\n issue(\n issues,\n 'PACK_FAILED',\n `Unable to inspect packed artifact: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\nasync function sourcePaths(directory: string): Promise<string[]> {\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n return [];\n }\n const paths = await Promise.all(\n entries.map(async (entry) => {\n const path = resolve(directory, entry.name);\n return entry.isDirectory()\n ? sourcePaths(path)\n : /\\.[cm]?tsx?$/u.test(entry.name)\n ? [path]\n : [];\n }),\n );\n return paths.flat().sort();\n}\n\nasync function jsonRecord(\n path: string,\n issues: PluginCheckIssue[],\n): Promise<Record<string, unknown>> {\n try {\n return record(JSON.parse(await readFile(path, 'utf8')));\n } catch (error: unknown) {\n issue(\n issues,\n 'MANIFEST_READ',\n `Unable to read package.json: ${error instanceof Error ? error.message : String(error)}`,\n );\n return {};\n }\n}\n\nfunction record(value: unknown): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return {};\n }\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) result[key] = Reflect.get(value, key);\n return result;\n}\n\nfunction issue(\n issues: PluginCheckIssue[],\n code: string,\n message: string,\n): void {\n issues.push(Object.freeze({ code, message }));\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nexport interface ScaffoldPluginOptions {\n readonly directory: string;\n readonly kind?: PluginTemplateKind;\n readonly packageName: string;\n readonly pluginId: string;\n}\n\n/** Focused offline contribution families supported by template version 3. */\nexport type PluginTemplateKind =\n 'basic' | 'cms-widget' | 'paste' | 'theme' | 'upload';\n\n/** Creates a versioned, strict plugin package without overwriting a directory. */\nexport async function scaffoldPluginPackage(\n options: ScaffoldPluginOptions,\n): Promise<string> {\n const directory = resolve(nonEmpty(options.directory, 'directory'));\n const packageName = packageValue(options.packageName);\n const pluginId = pluginIdValue(options.pluginId);\n const kind = kindValue(options.kind ?? 'basic');\n await mkdir(directory);\n await mkdir(resolve(directory, 'src'));\n await mkdir(resolve(directory, 'tests'));\n const className = classNameFrom(packageName);\n await Promise.all([\n write(directory, 'package.json', manifest(packageName)),\n write(directory, 'tsconfig.json', tsconfig()),\n write(directory, 'vite.config.ts', viteConfig()),\n write(\n directory,\n 'pnpm-workspace.yaml',\n 'allowBuilds:\\n esbuild: true\\n\\nonlyBuiltDependencies:\\n - esbuild\\n',\n ),\n write(\n directory,\n 'README.md',\n `# ${packageName}\\n\\nSoEditor plugin package.\\n`,\n ),\n write(directory, 'src/index.ts', indexSource(className, kind)),\n write(\n directory,\n 'src/plugin.ts',\n pluginSource(className, pluginId, kind),\n ),\n ]);\n return directory;\n}\n\nfunction indexSource(className: string, kind: PluginTemplateKind): string {\n const extras =\n kind === 'cms-widget'\n ? `, cmsObjectDefinitions`\n : kind === 'theme'\n ? `, editorIcons, editorThemeVariables`\n : '';\n return `export { ${className}${extras} } from './plugin.js';\\n`;\n}\n\nfunction pluginSource(\n className: string,\n pluginId: string,\n kind: PluginTemplateKind,\n): string {\n const id = escapeSingle(pluginId);\n if (kind === 'cms-widget') {\n return `import { CmsObjectsPlugin, Plugin, UiPlugin, uiRegistryServiceToken, type CmsObjectDefinition } from '@soeditor/plugin-sdk';\\n\\nexport const cmsObjectDefinitions = Object.freeze([\\n { id: 'product-card', label: 'Product card', properties: ['product-id'] },\\n] satisfies readonly CmsObjectDefinition[]);\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n static readonly requires = [CmsObjectsPlugin, UiPlugin];\\n #commandRegistered = false;\\n #dispose: (() => void)[] = [];\\n\\n override init(): void {\\n this.editor.commands.register({\\n id: '${id}.inspect',\\n canExecute: () => true,\\n execute: () => undefined,\\n });\\n this.#commandRegistered = true;\\n try {\\n this.#dispose.push(\\n this.editor.services.get(uiRegistryServiceToken).registerContextMenuItem('${id}.inspect', {\\n command: '${id}.inspect',\\n label: 'Inspect product card',\\n when: ({ target }) => target.closest('[data-soeditor-object=\"product-card\"]') !== null,\\n }),\\n );\\n } catch (error: unknown) {\\n this.editor.commands.unregister('${id}.inspect');\\n this.#commandRegistered = false;\\n throw error;\\n }\\n }\\n\\n override destroy(): void {\\n for (const dispose of this.#dispose.reverse()) dispose();\\n this.#dispose = [];\\n if (this.#commandRegistered) this.editor.commands.unregister('${id}.inspect');\\n this.#commandRegistered = false;\\n }\\n}\\n`;\n }\n if (kind === 'paste') {\n return `import { PastePipelinePlugin, Plugin, pastePipelineServiceToken } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n static readonly requires = [PastePipelinePlugin];\\n #dispose: (() => void) | undefined;\\n\\n override init(): void {\\n this.#dispose = this.editor.services.get(pastePipelineServiceToken).register({\\n id: '${id}.processor',\\n priority: 10,\\n process: (context) => context.classification === 'web'\\n ? { html: context.html.replaceAll(/<font\\\\b[^>]*>/giu, '<span>').replaceAll('</font>', '</span>') }\\n : undefined,\\n });\\n }\\n\\n override destroy(): void {\\n this.#dispose?.();\\n this.#dispose = undefined;\\n }\\n}\\n`;\n }\n if (kind === 'upload') {\n return `import { Plugin, uploadServiceToken, type UploadService } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n #service: UploadService | undefined;\\n\\n override init(): void {\\n const service = this.editor.config.get<unknown>('cms.upload.service');\\n if (!isUploadService(service)) throw new TypeError('cms.upload.service must provide create(request).');\\n this.editor.services.register(uploadServiceToken, service);\\n this.#service = service;\\n }\\n\\n override destroy(): void {\\n if (this.editor.services.tryGet(uploadServiceToken) === this.#service) {\\n this.editor.services.unregister(uploadServiceToken);\\n }\\n this.#service = undefined;\\n }\\n}\\n\\nfunction isUploadService(value: unknown): value is UploadService {\\n return typeof value === 'object' && value !== null && typeof Reflect.get(value, 'create') === 'function';\\n}\\n`;\n }\n if (kind === 'theme') {\n return `import { Plugin, type EditorUiIconResource, type EditorUiThemeVariables } from '@soeditor/plugin-sdk';\\n\\nexport const editorThemeVariables = Object.freeze({\\n accent: '#005ea8',\\n focusRing: '#ffbf47',\\n radius: '0.25rem',\\n} satisfies EditorUiThemeVariables);\\n\\nexport const editorIcons = Object.freeze({\\n 'format.bold': 'B',\\n 'format.italic': 'I',\\n} satisfies EditorUiIconResource);\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n}\\n`;\n }\n return `import { Plugin } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${id}';\\n}\\n`;\n}\n\nfunction manifest(name: string): string {\n return `${JSON.stringify(\n {\n name,\n version: '0.1.0',\n license: 'MIT',\n packageManager: 'pnpm@11.20.0',\n type: 'module',\n sideEffects: false,\n files: ['dist'],\n exports: {\n '.': { types: './dist/index.d.ts', import: './dist/index.js' },\n },\n scripts: {\n build: 'vite build && tsc -p tsconfig.json --emitDeclarationOnly --declaration --outDir dist',\n check: 'soeditor-plugin check . --packed',\n },\n peerDependencies: { '@soeditor/plugin-sdk': '^1.0.0' },\n devDependencies: {\n '@soeditor/comments': '^1.0.0',\n '@soeditor/core': '^1.0.0',\n '@soeditor/engine': '^1.0.0',\n '@soeditor/file-manager': '^1.0.0',\n '@soeditor/html-tools': '^1.0.0',\n '@soeditor/html': '^1.0.0',\n '@soeditor/layout': '^1.0.0',\n '@soeditor/plugin-sdk': '^1.0.0',\n '@soeditor/plugin-tools': '^1.0.0',\n '@soeditor/projections': '^1.0.0',\n '@soeditor/revisions': '^1.0.0',\n '@soeditor/rich-text': '^1.0.0',\n '@soeditor/ui': '^1.0.0',\n typescript: '^5.9.0',\n vite: '^7.0.0',\n },\n },\n undefined,\n 4,\n )}\\n`;\n}\n\nfunction tsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n declaration: true,\n declarationMap: true,\n emitDeclarationOnly: true,\n exactOptionalPropertyTypes: true,\n module: 'ESNext',\n moduleResolution: 'Bundler',\n outDir: 'dist',\n strict: true,\n target: 'ES2022',\n },\n include: ['src/**/*.ts'],\n },\n undefined,\n 4,\n )}\\n`;\n}\n\nfunction viteConfig(): string {\n return \"import { defineConfig } from 'vite';\\n\\nexport default defineConfig({\\n build: {\\n lib: { entry: 'src/index.ts', formats: ['es'], fileName: 'index' },\\n rollupOptions: { external: ['@soeditor/plugin-sdk'] },\\n sourcemap: true,\\n },\\n});\\n\";\n}\n\nasync function write(\n root: string,\n path: string,\n source: string,\n): Promise<void> {\n await writeFile(resolve(root, path), source, {\n encoding: 'utf8',\n flag: 'wx',\n });\n}\n\nfunction packageValue(value: string): string {\n const name = nonEmpty(value, 'package name');\n if (!/^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/u.test(name)) {\n throw new TypeError(`Invalid npm package name \"${name}\".`);\n }\n return name;\n}\n\nfunction pluginIdValue(value: string): string {\n const id = nonEmpty(value, 'plugin ID');\n if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) {\n throw new TypeError(\n `Invalid plugin ID \"${id}\"; use lowercase letters, numbers, dots, underscores, or hyphens.`,\n );\n }\n return id;\n}\n\nfunction kindValue(value: string): PluginTemplateKind {\n if (\n value !== 'basic' &&\n value !== 'cms-widget' &&\n value !== 'paste' &&\n value !== 'theme' &&\n value !== 'upload'\n ) {\n throw new TypeError(\n `Invalid plugin template kind \"${value}\"; use basic, cms-widget, paste, upload, or theme.`,\n );\n }\n return value;\n}\n\nfunction classNameFrom(name: string): string {\n const stem = basename(name).replace(\n /[^a-zA-Z0-9]+(.)/gu,\n (_match, next: string) => next.toUpperCase(),\n );\n const normalized = stem.replace(/^[^a-zA-Z]+/u, '');\n return `${normalized.length === 0 ? 'SoEditor' : normalized[0]!.toUpperCase() + normalized.slice(1)}Plugin`;\n}\n\nfunction nonEmpty(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new TypeError(`Plugin ${label} must be a non-empty string.`);\n }\n return value.trim();\n}\n\nfunction escapeSingle(value: string): string {\n return value.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\");\n}\n"],"names":["manifest"],"mappings":"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"}