@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 +9 -2
- package/dist/cli.js +16 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin-check.d.ts.map +1 -1
- package/dist/{scaffold-BD62-b7s.js → scaffold-DQxtxXfQ.js} +164 -13
- package/dist/scaffold-DQxtxXfQ.js.map +1 -0
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/scaffold-BD62-b7s.js.map +0 -1
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
|
-
|
|
11
|
-
|
|
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-
|
|
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":";;;
|
|
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 =
|
|
4
|
+
export type { PluginTemplateKind, ScaffoldPluginOptions } from './scaffold.js';
|
|
5
|
+
export declare const pluginTemplateVersion = 3;
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
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 =
|
|
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,
|
|
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
|
-
|
|
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-
|
|
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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"}
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -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
|
package/dist/scaffold.d.ts.map
CHANGED
|
@@ -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,
|
|
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 +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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"}
|