@soeditor/plugin-tools 1.0.0 → 1.1.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/README.md CHANGED
@@ -4,10 +4,17 @@ Public 1.0 offline plugin scaffolding and package audit tools.
4
4
 
5
5
  ```bash
6
6
  soeditor-plugin create ./my-plugin --name @example/my-plugin --id example.my-plugin
7
+ soeditor-plugin create ./product-card --name @example/product-card --id example.product-card --kind cms-widget
8
+ soeditor-plugin create ./paste --name @example/paste --id example.paste --kind paste
9
+ soeditor-plugin create ./upload --name @example/upload --id example.upload --kind upload
10
+ soeditor-plugin create ./theme --name @example/theme --id example.theme --kind theme
7
11
  soeditor-plugin check ./my-plugin --packed
8
12
  ```
9
13
 
10
- The checker reads source and metadata without importing plugin code. Packed
11
- inspection uses `npm pack --dry-run --ignore-scripts`.
14
+ Template version 3 creates deterministic `basic`, `cms-widget`, `paste`,
15
+ `upload`, and `theme` contribution families. The checker reads source and
16
+ metadata without importing plugin code, and rejects internal/remote imports,
17
+ dynamic evaluation, and direct DOM HTML injection sinks. Packed inspection
18
+ uses `npm pack --dry-run --ignore-scripts`.
12
19
 
13
20
  See [`docs/plugin-tooling.md`](../../docs/plugin-tooling.md).
package/dist/cli.js CHANGED
@@ -1,13 +1,15 @@
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-BD62-b7s.js";
3
+ import { s as scaffoldPluginPackage, c as checkPluginPackage } from "./scaffold-DQxtxXfQ.js";
4
4
  try {
5
5
  const [command, target, ...flags] = argv.slice(2);
6
6
  if (command === "create" && target !== void 0) {
7
7
  const packageName = flag(flags, "--name");
8
8
  const pluginId = flag(flags, "--id");
9
+ const kind = optionalKind(flags);
9
10
  const directory = await scaffoldPluginPackage({
10
11
  directory: target,
12
+ ...kind === void 0 ? {} : { kind },
11
13
  packageName,
12
14
  pluginId
13
15
  });
@@ -28,7 +30,7 @@ try {
28
30
  if (!report.valid) process.exitCode = 1;
29
31
  } else {
30
32
  throw new Error(
31
- "Usage: soeditor-plugin create <directory> --name <package> --id <plugin-id> | check <directory> [--packed]"
33
+ "Usage: soeditor-plugin create <directory> --name <package> --id <plugin-id> [--kind basic|cms-widget|paste|upload|theme] | check <directory> [--packed]"
32
34
  );
33
35
  }
34
36
  } catch (error) {
@@ -36,6 +38,18 @@ try {
36
38
  `);
37
39
  process.exitCode = 1;
38
40
  }
41
+ function optionalFlag(values, name) {
42
+ const index = values.indexOf(name);
43
+ return index < 0 ? void 0 : values[index + 1];
44
+ }
45
+ function optionalKind(values) {
46
+ const value = optionalFlag(values, "--kind");
47
+ if (value === void 0) return void 0;
48
+ if (value === "basic" || value === "cms-widget" || value === "paste" || value === "upload" || value === "theme") {
49
+ return value;
50
+ }
51
+ throw new Error(`Invalid --kind "${value}".`);
52
+ }
39
53
  function flag(values, name) {
40
54
  const index = values.indexOf(name);
41
55
  const value = index < 0 ? void 0 : values[index + 1];
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { argv, stderr, stdout } from 'node:process';\n\nimport { checkPluginPackage } from './plugin-check.js';\nimport { scaffoldPluginPackage } from './scaffold.js';\n\ntry {\n const [command, target, ...flags] = argv.slice(2);\n if (command === 'create' && target !== undefined) {\n const packageName = flag(flags, '--name');\n const pluginId = flag(flags, '--id');\n const directory = await scaffoldPluginPackage({\n directory: target,\n packageName,\n pluginId,\n });\n stdout.write(`Created SoEditor plugin package at ${directory}.\\n`);\n } else if (command === 'check' && target !== undefined) {\n const report = await checkPluginPackage(target, {\n packed: flags.includes('--packed'),\n });\n for (const issue of report.issues) {\n stderr.write(`[${issue.code}] ${issue.message}\\n`);\n }\n stdout.write(\n `${report.valid ? 'Valid' : 'Invalid'} SoEditor plugin package${report.packageName === undefined ? '' : ` ${report.packageName}`}.\\n`,\n );\n if (!report.valid) process.exitCode = 1;\n } else {\n throw new Error(\n 'Usage: soeditor-plugin create <directory> --name <package> --id <plugin-id> | check <directory> [--packed]',\n );\n }\n} catch (error: unknown) {\n stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n}\n\nfunction flag(values: string[], name: string): string {\n const index = values.indexOf(name);\n const value = index < 0 ? undefined : values[index + 1];\n if (value === undefined) throw new Error(`Missing ${name}.`);\n return value;\n}\n"],"names":[],"mappings":";;;AAMA,IAAI;AACA,QAAM,CAAC,SAAS,QAAQ,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC;AAChD,MAAI,YAAY,YAAY,WAAW,QAAW;AAC9C,UAAM,cAAc,KAAK,OAAO,QAAQ;AACxC,UAAM,WAAW,KAAK,OAAO,MAAM;AACnC,UAAM,YAAY,MAAM,sBAAsB;AAAA,MAC1C,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IAAA,CACH;AACD,WAAO,MAAM,sCAAsC,SAAS;AAAA,CAAK;AAAA,EACrE,WAAW,YAAY,WAAW,WAAW,QAAW;AACpD,UAAM,SAAS,MAAM,mBAAmB,QAAQ;AAAA,MAC5C,QAAQ,MAAM,SAAS,UAAU;AAAA,IAAA,CACpC;AACD,eAAW,SAAS,OAAO,QAAQ;AAC/B,aAAO,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM,OAAO;AAAA,CAAI;AAAA,IACrD;AACA,WAAO;AAAA,MACH,GAAG,OAAO,QAAQ,UAAU,SAAS,2BAA2B,OAAO,gBAAgB,SAAY,KAAK,IAAI,OAAO,WAAW,EAAE;AAAA;AAAA,IAAA;AAEpI,QAAI,CAAC,OAAO,MAAO,SAAQ,WAAW;AAAA,EAC1C,OAAO;AACH,UAAM,IAAI;AAAA,MACN;AAAA,IAAA;AAAA,EAER;AACJ,SAAS,OAAgB;AACrB,SAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAC1E,UAAQ,WAAW;AACvB;AAEA,SAAS,KAAK,QAAkB,MAAsB;AAClD,QAAM,QAAQ,OAAO,QAAQ,IAAI;AACjC,QAAM,QAAQ,QAAQ,IAAI,SAAY,OAAO,QAAQ,CAAC;AACtD,MAAI,UAAU,OAAW,OAAM,IAAI,MAAM,WAAW,IAAI,GAAG;AAC3D,SAAO;AACX;"}
1
+ {"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { argv, stderr, stdout } from 'node:process';\n\nimport { checkPluginPackage } from './plugin-check.js';\nimport { scaffoldPluginPackage } from './scaffold.js';\nimport type { PluginTemplateKind } from './scaffold.js';\n\ntry {\n const [command, target, ...flags] = argv.slice(2);\n if (command === 'create' && target !== undefined) {\n const packageName = flag(flags, '--name');\n const pluginId = flag(flags, '--id');\n const kind = optionalKind(flags);\n const directory = await scaffoldPluginPackage({\n directory: target,\n ...(kind === undefined ? {} : { kind }),\n packageName,\n pluginId,\n });\n stdout.write(`Created SoEditor plugin package at ${directory}.\\n`);\n } else if (command === 'check' && target !== undefined) {\n const report = await checkPluginPackage(target, {\n packed: flags.includes('--packed'),\n });\n for (const issue of report.issues) {\n stderr.write(`[${issue.code}] ${issue.message}\\n`);\n }\n stdout.write(\n `${report.valid ? 'Valid' : 'Invalid'} SoEditor plugin package${report.packageName === undefined ? '' : ` ${report.packageName}`}.\\n`,\n );\n if (!report.valid) process.exitCode = 1;\n } else {\n throw new Error(\n 'Usage: soeditor-plugin create <directory> --name <package> --id <plugin-id> [--kind basic|cms-widget|paste|upload|theme] | check <directory> [--packed]',\n );\n }\n} catch (error: unknown) {\n stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n}\n\nfunction optionalFlag(values: string[], name: string): string | undefined {\n const index = values.indexOf(name);\n return index < 0 ? undefined : values[index + 1];\n}\n\nfunction optionalKind(values: string[]): PluginTemplateKind | undefined {\n const value = optionalFlag(values, '--kind');\n if (value === undefined) return undefined;\n if (\n value === 'basic' ||\n value === 'cms-widget' ||\n value === 'paste' ||\n value === 'upload' ||\n value === 'theme'\n ) {\n return value;\n }\n throw new Error(`Invalid --kind \"${value}\".`);\n}\n\nfunction flag(values: string[], name: string): string {\n const index = values.indexOf(name);\n const value = index < 0 ? undefined : values[index + 1];\n if (value === undefined) throw new Error(`Missing ${name}.`);\n return value;\n}\n"],"names":[],"mappings":";;;AAOA,IAAI;AACA,QAAM,CAAC,SAAS,QAAQ,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC;AAChD,MAAI,YAAY,YAAY,WAAW,QAAW;AAC9C,UAAM,cAAc,KAAK,OAAO,QAAQ;AACxC,UAAM,WAAW,KAAK,OAAO,MAAM;AACnC,UAAM,OAAO,aAAa,KAAK;AAC/B,UAAM,YAAY,MAAM,sBAAsB;AAAA,MAC1C,WAAW;AAAA,MACX,GAAI,SAAS,SAAY,CAAA,IAAK,EAAE,KAAA;AAAA,MAChC;AAAA,MACA;AAAA,IAAA,CACH;AACD,WAAO,MAAM,sCAAsC,SAAS;AAAA,CAAK;AAAA,EACrE,WAAW,YAAY,WAAW,WAAW,QAAW;AACpD,UAAM,SAAS,MAAM,mBAAmB,QAAQ;AAAA,MAC5C,QAAQ,MAAM,SAAS,UAAU;AAAA,IAAA,CACpC;AACD,eAAW,SAAS,OAAO,QAAQ;AAC/B,aAAO,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM,OAAO;AAAA,CAAI;AAAA,IACrD;AACA,WAAO;AAAA,MACH,GAAG,OAAO,QAAQ,UAAU,SAAS,2BAA2B,OAAO,gBAAgB,SAAY,KAAK,IAAI,OAAO,WAAW,EAAE;AAAA;AAAA,IAAA;AAEpI,QAAI,CAAC,OAAO,MAAO,SAAQ,WAAW;AAAA,EAC1C,OAAO;AACH,UAAM,IAAI;AAAA,MACN;AAAA,IAAA;AAAA,EAER;AACJ,SAAS,OAAgB;AACrB,SAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAC1E,UAAQ,WAAW;AACvB;AAEA,SAAS,aAAa,QAAkB,MAAkC;AACtE,QAAM,QAAQ,OAAO,QAAQ,IAAI;AACjC,SAAO,QAAQ,IAAI,SAAY,OAAO,QAAQ,CAAC;AACnD;AAEA,SAAS,aAAa,QAAkD;AACpE,QAAM,QAAQ,aAAa,QAAQ,QAAQ;AAC3C,MAAI,UAAU,OAAW,QAAO;AAChC,MACI,UAAU,WACV,UAAU,gBACV,UAAU,WACV,UAAU,YACV,UAAU,SACZ;AACE,WAAO;AAAA,EACX;AACA,QAAM,IAAI,MAAM,mBAAmB,KAAK,IAAI;AAChD;AAEA,SAAS,KAAK,QAAkB,MAAsB;AAClD,QAAM,QAAQ,OAAO,QAAQ,IAAI;AACjC,QAAM,QAAQ,QAAQ,IAAI,SAAY,OAAO,QAAQ,CAAC;AACtD,MAAI,UAAU,OAAW,OAAM,IAAI,MAAM,WAAW,IAAI,GAAG;AAC3D,SAAO;AACX;"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { checkPluginPackage } from './plugin-check.js';
2
2
  export { scaffoldPluginPackage } from './scaffold.js';
3
3
  export type { PluginCheckIssue, PluginCheckOptions, PluginCheckReport, } from './plugin-check.js';
4
- export type { ScaffoldPluginOptions } from './scaffold.js';
5
- export declare const pluginTemplateVersion = 2;
4
+ export type { PluginTemplateKind, ScaffoldPluginOptions } from './scaffold.js';
5
+ export declare const pluginTemplateVersion = 3;
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACR,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,eAAO,MAAM,qBAAqB,IAAI,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACR,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE/E,eAAO,MAAM,qBAAqB,IAAI,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { c, s } from "./scaffold-BD62-b7s.js";
2
- const pluginTemplateVersion = 2;
1
+ import { c, s } from "./scaffold-DQxtxXfQ.js";
2
+ const pluginTemplateVersion = 3;
3
3
  export {
4
4
  c as checkPluginPackage,
5
5
  pluginTemplateVersion,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["export { checkPluginPackage } from './plugin-check.js';\nexport { scaffoldPluginPackage } from './scaffold.js';\nexport type {\n PluginCheckIssue,\n PluginCheckOptions,\n PluginCheckReport,\n} from './plugin-check.js';\nexport type { ScaffoldPluginOptions } from './scaffold.js';\n\nexport const pluginTemplateVersion = 2;\n"],"names":[],"mappings":";AASO,MAAM,wBAAwB;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["export { checkPluginPackage } from './plugin-check.js';\nexport { scaffoldPluginPackage } from './scaffold.js';\nexport type {\n PluginCheckIssue,\n PluginCheckOptions,\n PluginCheckReport,\n} from './plugin-check.js';\nexport type { PluginTemplateKind, ScaffoldPluginOptions } from './scaffold.js';\n\nexport const pluginTemplateVersion = 3;\n"],"names":[],"mappings":";AASO,MAAM,wBAAwB;"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-check.d.ts","sourceRoot":"","sources":["../src/plugin-check.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CAC3B;AAED,yEAAyE;AACzE,wBAAsB,kBAAkB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAuF5B"}
1
+ {"version":3,"file":"plugin-check.d.ts","sourceRoot":"","sources":["../src/plugin-check.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CAC3B;AAED,yEAAyE;AACzE,wBAAsB,kBAAkB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAgH5B"}
@@ -58,6 +58,29 @@ async function checkPluginPackage(directory, options = {}) {
58
58
  `${path} imports an unsupported SoEditor internal path.`
59
59
  );
60
60
  }
61
+ if (/(?:from\s+|import\s*\()\s*['"](?:https?:)?\/\//u.test(source)) {
62
+ issue(
63
+ issues,
64
+ "REMOTE_IMPORT",
65
+ `${path} imports executable code from a remote URL.`
66
+ );
67
+ }
68
+ if (/\b(?:eval\s*\(|new\s+Function\s*\()/u.test(source)) {
69
+ issue(
70
+ issues,
71
+ "DYNAMIC_CODE",
72
+ `${path} contains dynamic code evaluation.`
73
+ );
74
+ }
75
+ if (/\.innerHTML\s*=|\.outerHTML\s*=|\.insertAdjacentHTML\s*\(/u.test(
76
+ source
77
+ )) {
78
+ issue(
79
+ issues,
80
+ "UNSAFE_DOM_OUTPUT",
81
+ `${path} writes untrusted HTML through a DOM injection sink.`
82
+ );
83
+ }
61
84
  }
62
85
  if (!sources.some(
63
86
  ({ source }) => /from\s+['"]@soeditor\/plugin-sdk['"]/u.test(source)
@@ -209,6 +232,7 @@ async function scaffoldPluginPackage(options) {
209
232
  const directory = resolve(nonEmpty(options.directory, "directory"));
210
233
  const packageName = packageValue(options.packageName);
211
234
  const pluginId = pluginIdValue(options.pluginId);
235
+ const kind = kindValue(options.kind ?? "basic");
212
236
  await mkdir(directory);
213
237
  await mkdir(resolve(directory, "src"));
214
238
  await mkdir(resolve(directory, "tests"));
@@ -230,25 +254,144 @@ async function scaffoldPluginPackage(options) {
230
254
  SoEditor plugin package.
231
255
  `
232
256
  ),
233
- write(
234
- directory,
235
- "src/index.ts",
236
- `export { ${className} } from './plugin.js';
237
- `
238
- ),
257
+ write(directory, "src/index.ts", indexSource(className, kind)),
239
258
  write(
240
259
  directory,
241
260
  "src/plugin.ts",
242
- `import { Plugin } from '@soeditor/plugin-sdk';
243
-
244
- export class ${className} extends Plugin {
245
- static readonly id = '${escapeSingle(pluginId)}';
246
- }
247
- `
261
+ pluginSource(className, pluginId, kind)
248
262
  )
249
263
  ]);
250
264
  return directory;
251
265
  }
266
+ function indexSource(className, kind) {
267
+ const extras = kind === "cms-widget" ? `, cmsObjectDefinitions` : kind === "theme" ? `, editorIcons, editorThemeVariables` : "";
268
+ return `export { ${className}${extras} } from './plugin.js';
269
+ `;
270
+ }
271
+ function pluginSource(className, pluginId, kind) {
272
+ const id = escapeSingle(pluginId);
273
+ if (kind === "cms-widget") {
274
+ return `import { CmsObjectsPlugin, Plugin, UiPlugin, uiRegistryServiceToken, type CmsObjectDefinition } from '@soeditor/plugin-sdk';
275
+
276
+ export const cmsObjectDefinitions = Object.freeze([
277
+ { id: 'product-card', label: 'Product card', properties: ['product-id'] },
278
+ ] satisfies readonly CmsObjectDefinition[]);
279
+
280
+ export class ${className} extends Plugin {
281
+ static readonly id = '${id}';
282
+ static readonly requires = [CmsObjectsPlugin, UiPlugin];
283
+ #commandRegistered = false;
284
+ #dispose: (() => void)[] = [];
285
+
286
+ override init(): void {
287
+ this.editor.commands.register({
288
+ id: '${id}.inspect',
289
+ canExecute: () => true,
290
+ execute: () => undefined,
291
+ });
292
+ this.#commandRegistered = true;
293
+ try {
294
+ this.#dispose.push(
295
+ this.editor.services.get(uiRegistryServiceToken).registerContextMenuItem('${id}.inspect', {
296
+ command: '${id}.inspect',
297
+ label: 'Inspect product card',
298
+ when: ({ target }) => target.closest('[data-soeditor-object="product-card"]') !== null,
299
+ }),
300
+ );
301
+ } catch (error: unknown) {
302
+ this.editor.commands.unregister('${id}.inspect');
303
+ this.#commandRegistered = false;
304
+ throw error;
305
+ }
306
+ }
307
+
308
+ override destroy(): void {
309
+ for (const dispose of this.#dispose.reverse()) dispose();
310
+ this.#dispose = [];
311
+ if (this.#commandRegistered) this.editor.commands.unregister('${id}.inspect');
312
+ this.#commandRegistered = false;
313
+ }
314
+ }
315
+ `;
316
+ }
317
+ if (kind === "paste") {
318
+ return `import { PastePipelinePlugin, Plugin, pastePipelineServiceToken } from '@soeditor/plugin-sdk';
319
+
320
+ export class ${className} extends Plugin {
321
+ static readonly id = '${id}';
322
+ static readonly requires = [PastePipelinePlugin];
323
+ #dispose: (() => void) | undefined;
324
+
325
+ override init(): void {
326
+ this.#dispose = this.editor.services.get(pastePipelineServiceToken).register({
327
+ id: '${id}.processor',
328
+ priority: 10,
329
+ process: (context) => context.classification === 'web'
330
+ ? { html: context.html.replaceAll(/<font\\b[^>]*>/giu, '<span>').replaceAll('</font>', '</span>') }
331
+ : undefined,
332
+ });
333
+ }
334
+
335
+ override destroy(): void {
336
+ this.#dispose?.();
337
+ this.#dispose = undefined;
338
+ }
339
+ }
340
+ `;
341
+ }
342
+ if (kind === "upload") {
343
+ return `import { Plugin, uploadServiceToken, type UploadService } from '@soeditor/plugin-sdk';
344
+
345
+ export class ${className} extends Plugin {
346
+ static readonly id = '${id}';
347
+ #service: UploadService | undefined;
348
+
349
+ override init(): void {
350
+ const service = this.editor.config.get<unknown>('cms.upload.service');
351
+ if (!isUploadService(service)) throw new TypeError('cms.upload.service must provide create(request).');
352
+ this.editor.services.register(uploadServiceToken, service);
353
+ this.#service = service;
354
+ }
355
+
356
+ override destroy(): void {
357
+ if (this.editor.services.tryGet(uploadServiceToken) === this.#service) {
358
+ this.editor.services.unregister(uploadServiceToken);
359
+ }
360
+ this.#service = undefined;
361
+ }
362
+ }
363
+
364
+ function isUploadService(value: unknown): value is UploadService {
365
+ return typeof value === 'object' && value !== null && typeof Reflect.get(value, 'create') === 'function';
366
+ }
367
+ `;
368
+ }
369
+ if (kind === "theme") {
370
+ return `import { Plugin, type EditorUiIconResource, type EditorUiThemeVariables } from '@soeditor/plugin-sdk';
371
+
372
+ export const editorThemeVariables = Object.freeze({
373
+ accent: '#005ea8',
374
+ focusRing: '#ffbf47',
375
+ radius: '0.25rem',
376
+ } satisfies EditorUiThemeVariables);
377
+
378
+ export const editorIcons = Object.freeze({
379
+ 'format.bold': 'B',
380
+ 'format.italic': 'I',
381
+ } satisfies EditorUiIconResource);
382
+
383
+ export class ${className} extends Plugin {
384
+ static readonly id = '${id}';
385
+ }
386
+ `;
387
+ }
388
+ return `import { Plugin } from '@soeditor/plugin-sdk';
389
+
390
+ export class ${className} extends Plugin {
391
+ static readonly id = '${id}';
392
+ }
393
+ `;
394
+ }
252
395
  function manifest(name) {
253
396
  return `${JSON.stringify(
254
397
  {
@@ -336,6 +479,14 @@ function pluginIdValue(value) {
336
479
  }
337
480
  return id;
338
481
  }
482
+ function kindValue(value) {
483
+ if (value !== "basic" && value !== "cms-widget" && value !== "paste" && value !== "theme" && value !== "upload") {
484
+ throw new TypeError(
485
+ `Invalid plugin template kind "${value}"; use basic, cms-widget, paste, upload, or theme.`
486
+ );
487
+ }
488
+ return value;
489
+ }
339
490
  function classNameFrom(name) {
340
491
  const stem = basename(name).replace(
341
492
  /[^a-zA-Z0-9]+(.)/gu,
@@ -357,4 +508,4 @@ export {
357
508
  checkPluginPackage as c,
358
509
  scaffoldPluginPackage as s
359
510
  };
360
- //# sourceMappingURL=scaffold-BD62-b7s.js.map
511
+ //# sourceMappingURL=scaffold-DQxtxXfQ.js.map
@@ -0,0 +1 @@
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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"}
@@ -1,8 +1,11 @@
1
1
  export interface ScaffoldPluginOptions {
2
2
  readonly directory: string;
3
+ readonly kind?: PluginTemplateKind;
3
4
  readonly packageName: string;
4
5
  readonly pluginId: string;
5
6
  }
7
+ /** Focused offline contribution families supported by template version 3. */
8
+ export type PluginTemplateKind = 'basic' | 'cms-widget' | 'paste' | 'theme' | 'upload';
6
9
  /** Creates a versioned, strict plugin package without overwriting a directory. */
7
10
  export declare function scaffoldPluginPackage(options: ScaffoldPluginOptions): Promise<string>;
8
11
  //# sourceMappingURL=scaffold.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,kFAAkF;AAClF,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,qBAAqB,GAC/B,OAAO,CAAC,MAAM,CAAC,CAkCjB"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1D,kFAAkF;AAClF,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,qBAAqB,GAC/B,OAAO,CAAC,MAAM,CAAC,CA+BjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soeditor/plugin-tools",
3
- "version": "1.0.0",
3
+ "version": "1.1.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-BD62-b7s.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 }\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 packageName: string;\n readonly pluginId: string;\n}\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 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(\n directory,\n 'src/index.ts',\n `export { ${className} } from './plugin.js';\\n`,\n ),\n write(\n directory,\n 'src/plugin.ts',\n `import { Plugin } from '@soeditor/plugin-sdk';\\n\\nexport class ${className} extends Plugin {\\n static readonly id = '${escapeSingle(pluginId)}';\\n}\\n`,\n ),\n ]);\n return directory;\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 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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"}