@streamfox/create-streamfox-plugin 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/scaffold.ts
4
+ import { access, mkdir, writeFile } from "fs/promises";
5
+ import path from "path";
6
+ var CAPABILITIES = [
7
+ "catalog",
8
+ "meta",
9
+ "stream",
10
+ "subtitles",
11
+ "plugin_catalog"
12
+ ];
13
+ var DEFAULT_PRESET = "meta";
14
+ var DEFAULT_SDK_VERSION = "^0.2.0";
15
+ function sortedCapabilities(values) {
16
+ const unique = Array.from(new Set(values));
17
+ return CAPABILITIES.filter((capability) => unique.includes(capability));
18
+ }
19
+ async function ensureTargetDoesNotExist(targetDir) {
20
+ try {
21
+ await access(targetDir);
22
+ throw new Error(`Target directory already exists: ${targetDir}`);
23
+ } catch (error) {
24
+ if (error.code === "ENOENT") {
25
+ return;
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+ function makePackageJson(name, language, sdkVersion) {
31
+ const scripts = language === "ts" ? {
32
+ dev: "tsx watch src/server.ts",
33
+ build: "tsc -p tsconfig.json",
34
+ format: "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ start: "node dist/server.js",
37
+ test: "vitest run",
38
+ typecheck: "tsc --noEmit"
39
+ } : {
40
+ dev: "node --watch src/server.js",
41
+ build: 'echo "No build step for JavaScript template"',
42
+ format: "prettier --write .",
43
+ "format:check": "prettier --check .",
44
+ start: "node src/server.js",
45
+ test: "vitest run"
46
+ };
47
+ const normalizedScripts = {
48
+ ...scripts,
49
+ check: language === "ts" ? "npm run format:check && npm run typecheck && npm test && npm run build" : "npm run format:check && npm test && npm run build"
50
+ };
51
+ const packageJson = {
52
+ name,
53
+ version: "0.1.0",
54
+ private: true,
55
+ type: "module",
56
+ scripts: normalizedScripts,
57
+ dependencies: {
58
+ "@streamfox/plugin-sdk": sdkVersion
59
+ },
60
+ devDependencies: language === "ts" ? {
61
+ "@types/node": "^24.6.0",
62
+ prettier: "^3.6.2",
63
+ tsx: "^4.20.5",
64
+ typescript: "^5.9.2",
65
+ vitest: "^2.1.9"
66
+ } : {
67
+ prettier: "^3.6.2",
68
+ vitest: "^2.1.9"
69
+ }
70
+ };
71
+ return `${JSON.stringify(packageJson, null, 2)}
72
+ `;
73
+ }
74
+ function resourceBlock(capability, advanced) {
75
+ switch (capability) {
76
+ case "catalog":
77
+ return `catalog: {
78
+ endpoints: [
79
+ {
80
+ id: "top",
81
+ name: "Top",
82
+ mediaTypes: ["movie"],
83
+ filters: [{ key: "genre", valueType: "string" }],
84
+ },
85
+ ],
86
+ handler: async () => ({
87
+ items: [],
88
+ }),
89
+ },`;
90
+ case "meta":
91
+ return `meta: {
92
+ mediaTypes: ["movie"],
93
+ includes: ["videos", "links"],
94
+ handler: async () => ({
95
+ item: ${advanced ? `{
96
+ summary: {
97
+ id: { namespace: "imdb", value: "tt1254207" },
98
+ mediaType: "movie",
99
+ title: "Big Buck Bunny",
100
+ links: [],
101
+ },
102
+ defaultVideoID: "main",
103
+ trailers: [{ transport: { kind: "youtube", id: "aqz-KE-bpKQ" } }],
104
+ videos: [
105
+ {
106
+ id: "main",
107
+ title: "Main",
108
+ streams: [{ transport: { kind: "http", url: "https://example.com/video.mp4" } }],
109
+ },
110
+ ],
111
+ }` : "null"},
112
+ }),
113
+ },`;
114
+ case "stream":
115
+ return `stream: {
116
+ mediaTypes: ["movie"],
117
+ supportedTransports: ${advanced ? `["http", "torrent", "usenet", "archive", "youtube"]` : `["http"]`},
118
+ handler: async () => ({
119
+ streams: [
120
+ {
121
+ transport: { kind: "http", url: "https://example.com/video.mp4", mode: "stream" },
122
+ hints: {
123
+ notWebReady: true,
124
+ proxyHeaders: { request: { "User-Agent": "StreamFox" } },
125
+ },
126
+ },
127
+ ${advanced ? ` {
128
+ transport: { kind: "torrent", infoHash: "abcdef", peerDiscovery: ["tracker:udp://tracker.example.com:80"] },
129
+ selection: { fileIndex: 0 },
130
+ },
131
+ {
132
+ transport: { kind: "usenet", nzbURL: "https://example.com/file.nzb", servers: ["nntps://user:pass@news.example.com:563/4"] },
133
+ },
134
+ {
135
+ transport: {
136
+ kind: "archive",
137
+ format: "zip",
138
+ files: [{ url: "https://example.com/archive.zip", bytes: 1024 }],
139
+ },
140
+ selection: { fileMustInclude: "movie.mkv" },
141
+ },
142
+ ` : ""} ],
143
+ }),
144
+ },`;
145
+ case "subtitles":
146
+ return `subtitles: {
147
+ mediaTypes: ["movie", "episode"],
148
+ defaultLanguages: ["en"],
149
+ handler: async (request, { settings }) => {
150
+ const configuredLanguages = Array.isArray(settings.languages)
151
+ ? settings.languages
152
+ : [];
153
+ const languagePreferences =
154
+ configuredLanguages.length > 0 ? configuredLanguages : (request.languagePreferences ?? []);
155
+
156
+ void languagePreferences;
157
+ void settings.includeHI;
158
+
159
+ return {
160
+ subtitles: [],
161
+ };
162
+ },
163
+ },`;
164
+ case "plugin_catalog":
165
+ return `pluginCatalog: {
166
+ endpoints: [
167
+ {
168
+ id: "featured",
169
+ name: "Featured",
170
+ pluginKinds: ["catalog", "meta", "stream", "subtitles"],
171
+ tags: ["official"],
172
+ },
173
+ ],
174
+ handler: async () => ({
175
+ plugins: [
176
+ {
177
+ id: "com.example.recommended",
178
+ name: "Recommended",
179
+ version: "1.0.0",
180
+ pluginKinds: ["catalog", "meta"],
181
+ distribution: {
182
+ transport: "https",
183
+ manifestURL: "https://plugins.example.com/recommended/manifest",
184
+ },
185
+ manifestSnapshot: {
186
+ plugin: { id: "com.example.recommended" },
187
+ },
188
+ },
189
+ ],
190
+ }),
191
+ },`;
192
+ default:
193
+ return "";
194
+ }
195
+ }
196
+ function makeInstallBlock(preset) {
197
+ if (preset !== "subtitles") {
198
+ return "";
199
+ }
200
+ return ` install: {
201
+ configurationRequired: true,
202
+ title: "Subtitle Settings",
203
+ description: "Configure subtitle defaults before installing this plugin.",
204
+ fields: [
205
+ settings.multiSelect("languages", {
206
+ label: "Languages",
207
+ options: [
208
+ { label: "English", value: "en" },
209
+ { label: "Greek", value: "el" },
210
+ { label: "Spanish", value: "es" },
211
+ ],
212
+ defaultValue: ["en"],
213
+ }),
214
+ settings.checkbox("includeHI", {
215
+ label: "Include hearing impaired",
216
+ defaultValue: true,
217
+ }),
218
+ ],
219
+ },
220
+ `;
221
+ }
222
+ function makePluginFile(name, preset, capabilities, advanced) {
223
+ const resources = capabilities.map((capability) => resourceBlock(capability, advanced)).join("\n ");
224
+ const install = makeInstallBlock(preset);
225
+ const importSpec = install.length > 0 ? "definePlugin, settings" : "definePlugin";
226
+ const installBlock = install.length > 0 ? `${install}` : "";
227
+ return `import { ${importSpec} } from "@streamfox/plugin-sdk";
228
+
229
+ export const plugin = definePlugin({
230
+ plugin: {
231
+ id: "com.example.${name}",
232
+ name: "${name}",
233
+ version: "0.1.0",
234
+ description: "Generated StreamFox plugin scaffold",
235
+ },
236
+ ${installBlock} resources: {
237
+ ${resources}
238
+ },
239
+ });
240
+ `;
241
+ }
242
+ function makeServerFile(language) {
243
+ const pluginImport = language === "ts" ? "./plugin" : "./plugin.js";
244
+ return `import { serve } from "@streamfox/plugin-sdk";
245
+ import { plugin } from "${pluginImport}";
246
+
247
+ const { url, installURL, launchURL } = await serve(plugin, {
248
+ port: Number(process.env.PORT ?? 7000),
249
+ integration: {
250
+ installScheme: "streamfox",
251
+ launchBaseURL: "https://streamfox.app/#",
252
+ autoOpen: "none",
253
+ },
254
+ });
255
+
256
+ console.log("Plugin manifest:", url);
257
+ console.log("Plugin installer deeplink:", installURL);
258
+ console.log("Plugin launch URL:", launchURL);
259
+ `;
260
+ }
261
+ function makeVitestFile(language) {
262
+ const pluginImport = language === "ts" ? "../src/plugin" : "../src/plugin.js";
263
+ return `import { describe, expect, it } from "vitest";
264
+ import { createServer } from "@streamfox/plugin-sdk";
265
+ import { plugin } from "${pluginImport}";
266
+
267
+ describe("scaffold smoke", () => {
268
+ it("serves manifest and studio config", async () => {
269
+ const app = createServer(plugin, { frontend: false });
270
+
271
+ const manifestResponse = await app.request("/manifest");
272
+ expect(manifestResponse.status).toBe(200);
273
+
274
+ const studioResponse = await app.request("/studio-config");
275
+ expect(studioResponse.status).toBe(200);
276
+ });
277
+ });
278
+ `;
279
+ }
280
+ var tsConfig = `{
281
+ "compilerOptions": {
282
+ "target": "ES2022",
283
+ "module": "ESNext",
284
+ "moduleResolution": "Bundler",
285
+ "strict": true,
286
+ "declaration": true,
287
+ "outDir": "dist",
288
+ "rootDir": "src",
289
+ "types": ["node"]
290
+ },
291
+ "include": ["src"]
292
+ }
293
+ `;
294
+ var prettierConfig = `{
295
+ "semi": true,
296
+ "singleQuote": false,
297
+ "trailingComma": "all"
298
+ }
299
+ `;
300
+ var prettierIgnore = `dist
301
+ node_modules
302
+ .DS_Store
303
+ `;
304
+ function makeReadme(projectName, preset, capabilities, advanced) {
305
+ const capabilitiesList = capabilities.map((capability) => `- ${capability}`).join("\n");
306
+ const endpointForCapability = (capability) => {
307
+ switch (capability) {
308
+ case "catalog":
309
+ return "/catalog/:mediaType/:catalogID";
310
+ case "meta":
311
+ return "/meta/:mediaType/:itemID";
312
+ case "stream":
313
+ return "/stream/:mediaType/:itemID";
314
+ case "subtitles":
315
+ return "/subtitles/:mediaType/:itemID";
316
+ case "plugin_catalog":
317
+ return "/plugin_catalog/:catalogID/:pluginKind";
318
+ default:
319
+ return `/${capability}`;
320
+ }
321
+ };
322
+ const endpointLines = [
323
+ "- GET /manifest",
324
+ "- GET /studio-config",
325
+ ...capabilities.map(
326
+ (capability) => `- GET ${endpointForCapability(capability)}`
327
+ )
328
+ ].join("\n");
329
+ return `# ${projectName}
330
+
331
+ Generated with create-streamfox-plugin.
332
+
333
+ Preset: \`${preset}\`
334
+ Advanced template: \`${advanced ? "enabled" : "disabled"}\`
335
+
336
+ ## Scripts
337
+
338
+ - npm run dev
339
+ - npm run build
340
+ - npm run format
341
+ - npm run start
342
+ - npm run test
343
+
344
+ ## Implemented Capabilities
345
+
346
+ ${capabilitiesList}
347
+
348
+ ## Stream Model
349
+
350
+ - Unified transport model via \`stream.transport\`
351
+ - Capability declaration via \`resources.stream.supportedTransports\`
352
+ - Optional selection controls via \`stream.selection\`
353
+
354
+ ## Endpoints
355
+
356
+ ${endpointLines}
357
+ `;
358
+ }
359
+ async function scaffoldProject(options) {
360
+ const capabilities = sortedCapabilities([
361
+ options.preset,
362
+ ...options.extraCapabilities ?? []
363
+ ]);
364
+ const sdkVersion = (options.sdkVersion ?? DEFAULT_SDK_VERSION).trim() || DEFAULT_SDK_VERSION;
365
+ const advanced = options.advanced ?? false;
366
+ await ensureTargetDoesNotExist(options.targetDir);
367
+ const srcDir = path.join(options.targetDir, "src");
368
+ const testDir = path.join(options.targetDir, "test");
369
+ await mkdir(srcDir, { recursive: true });
370
+ await mkdir(testDir, { recursive: true });
371
+ await writeFile(
372
+ path.join(options.targetDir, "package.json"),
373
+ makePackageJson(options.projectName, options.language, sdkVersion)
374
+ );
375
+ await writeFile(
376
+ path.join(options.targetDir, ".prettierrc.json"),
377
+ prettierConfig
378
+ );
379
+ await writeFile(
380
+ path.join(options.targetDir, ".prettierignore"),
381
+ prettierIgnore
382
+ );
383
+ await writeFile(
384
+ path.join(options.targetDir, "README.md"),
385
+ makeReadme(options.projectName, options.preset, capabilities, advanced)
386
+ );
387
+ if (options.language === "ts") {
388
+ await writeFile(path.join(options.targetDir, "tsconfig.json"), tsConfig);
389
+ await writeFile(
390
+ path.join(srcDir, "plugin.ts"),
391
+ makePluginFile(
392
+ options.projectName,
393
+ options.preset,
394
+ capabilities,
395
+ advanced
396
+ )
397
+ );
398
+ await writeFile(path.join(srcDir, "server.ts"), makeServerFile("ts"));
399
+ await writeFile(path.join(testDir, "plugin.test.ts"), makeVitestFile("ts"));
400
+ } else {
401
+ await writeFile(
402
+ path.join(srcDir, "plugin.js"),
403
+ makePluginFile(
404
+ options.projectName,
405
+ options.preset,
406
+ capabilities,
407
+ advanced
408
+ )
409
+ );
410
+ await writeFile(path.join(srcDir, "server.js"), makeServerFile("js"));
411
+ await writeFile(path.join(testDir, "plugin.test.js"), makeVitestFile("js"));
412
+ }
413
+ }
414
+ export {
415
+ CAPABILITIES,
416
+ DEFAULT_PRESET,
417
+ DEFAULT_SDK_VERSION,
418
+ scaffoldProject
419
+ };
420
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scaffold.ts"],"sourcesContent":["import { access, mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport const CAPABILITIES = [\n \"catalog\",\n \"meta\",\n \"stream\",\n \"subtitles\",\n \"plugin_catalog\",\n] as const;\nexport type Capability = (typeof CAPABILITIES)[number];\nexport type Preset = Capability;\nexport type Language = \"ts\" | \"js\";\nexport const DEFAULT_PRESET: Preset = \"meta\";\nexport const DEFAULT_SDK_VERSION = \"^0.2.0\";\n\nexport interface ScaffoldOptions {\n targetDir: string;\n projectName: string;\n language: Language;\n preset: Preset;\n advanced?: boolean;\n extraCapabilities?: Capability[];\n sdkVersion?: string;\n}\n\nfunction sortedCapabilities(values: Capability[]): Capability[] {\n const unique = Array.from(new Set(values));\n return CAPABILITIES.filter((capability) => unique.includes(capability));\n}\n\nasync function ensureTargetDoesNotExist(targetDir: string): Promise<void> {\n try {\n await access(targetDir);\n throw new Error(`Target directory already exists: ${targetDir}`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nfunction makePackageJson(\n name: string,\n language: Language,\n sdkVersion: string,\n): string {\n const scripts =\n language === \"ts\"\n ? {\n dev: \"tsx watch src/server.ts\",\n build: \"tsc -p tsconfig.json\",\n format: \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n start: \"node dist/server.js\",\n test: \"vitest run\",\n typecheck: \"tsc --noEmit\",\n }\n : {\n dev: \"node --watch src/server.js\",\n build: 'echo \"No build step for JavaScript template\"',\n format: \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n start: \"node src/server.js\",\n test: \"vitest run\",\n };\n\n const normalizedScripts = {\n ...scripts,\n check:\n language === \"ts\"\n ? \"npm run format:check && npm run typecheck && npm test && npm run build\"\n : \"npm run format:check && npm test && npm run build\",\n };\n\n const packageJson = {\n name,\n version: \"0.1.0\",\n private: true,\n type: \"module\",\n scripts: normalizedScripts,\n dependencies: {\n \"@streamfox/plugin-sdk\": sdkVersion,\n },\n devDependencies:\n language === \"ts\"\n ? {\n \"@types/node\": \"^24.6.0\",\n prettier: \"^3.6.2\",\n tsx: \"^4.20.5\",\n typescript: \"^5.9.2\",\n vitest: \"^2.1.9\",\n }\n : {\n prettier: \"^3.6.2\",\n vitest: \"^2.1.9\",\n },\n };\n\n return `${JSON.stringify(packageJson, null, 2)}\\n`;\n}\n\nfunction resourceBlock(capability: Capability, advanced: boolean): string {\n switch (capability) {\n case \"catalog\":\n return `catalog: {\n endpoints: [\n {\n id: \"top\",\n name: \"Top\",\n mediaTypes: [\"movie\"],\n filters: [{ key: \"genre\", valueType: \"string\" }],\n },\n ],\n handler: async () => ({\n items: [],\n }),\n },`;\n case \"meta\":\n return `meta: {\n mediaTypes: [\"movie\"],\n includes: [\"videos\", \"links\"],\n handler: async () => ({\n item: ${\n advanced\n ? `{\n summary: {\n id: { namespace: \"imdb\", value: \"tt1254207\" },\n mediaType: \"movie\",\n title: \"Big Buck Bunny\",\n links: [],\n },\n defaultVideoID: \"main\",\n trailers: [{ transport: { kind: \"youtube\", id: \"aqz-KE-bpKQ\" } }],\n videos: [\n {\n id: \"main\",\n title: \"Main\",\n streams: [{ transport: { kind: \"http\", url: \"https://example.com/video.mp4\" } }],\n },\n ],\n }`\n : \"null\"\n },\n }),\n },`;\n case \"stream\":\n return `stream: {\n mediaTypes: [\"movie\"],\n supportedTransports: ${advanced ? `[\"http\", \"torrent\", \"usenet\", \"archive\", \"youtube\"]` : `[\"http\"]`},\n handler: async () => ({\n streams: [\n {\n transport: { kind: \"http\", url: \"https://example.com/video.mp4\", mode: \"stream\" },\n hints: {\n notWebReady: true,\n proxyHeaders: { request: { \"User-Agent\": \"StreamFox\" } },\n },\n },\n${\n advanced\n ? ` {\n transport: { kind: \"torrent\", infoHash: \"abcdef\", peerDiscovery: [\"tracker:udp://tracker.example.com:80\"] },\n selection: { fileIndex: 0 },\n },\n {\n transport: { kind: \"usenet\", nzbURL: \"https://example.com/file.nzb\", servers: [\"nntps://user:pass@news.example.com:563/4\"] },\n },\n {\n transport: {\n kind: \"archive\",\n format: \"zip\",\n files: [{ url: \"https://example.com/archive.zip\", bytes: 1024 }],\n },\n selection: { fileMustInclude: \"movie.mkv\" },\n },\n`\n : \"\"\n} ],\n }),\n },`;\n case \"subtitles\":\n return `subtitles: {\n mediaTypes: [\"movie\", \"episode\"],\n defaultLanguages: [\"en\"],\n handler: async (request, { settings }) => {\n const configuredLanguages = Array.isArray(settings.languages)\n ? settings.languages\n : [];\n const languagePreferences =\n configuredLanguages.length > 0 ? configuredLanguages : (request.languagePreferences ?? []);\n\n void languagePreferences;\n void settings.includeHI;\n\n return {\n subtitles: [],\n };\n },\n },`;\n case \"plugin_catalog\":\n return `pluginCatalog: {\n endpoints: [\n {\n id: \"featured\",\n name: \"Featured\",\n pluginKinds: [\"catalog\", \"meta\", \"stream\", \"subtitles\"],\n tags: [\"official\"],\n },\n ],\n handler: async () => ({\n plugins: [\n {\n id: \"com.example.recommended\",\n name: \"Recommended\",\n version: \"1.0.0\",\n pluginKinds: [\"catalog\", \"meta\"],\n distribution: {\n transport: \"https\",\n manifestURL: \"https://plugins.example.com/recommended/manifest\",\n },\n manifestSnapshot: {\n plugin: { id: \"com.example.recommended\" },\n },\n },\n ],\n }),\n },`;\n default:\n return \"\";\n }\n}\n\nfunction makeInstallBlock(preset: Preset): string {\n if (preset !== \"subtitles\") {\n return \"\";\n }\n\n return ` install: {\n configurationRequired: true,\n title: \"Subtitle Settings\",\n description: \"Configure subtitle defaults before installing this plugin.\",\n fields: [\n settings.multiSelect(\"languages\", {\n label: \"Languages\",\n options: [\n { label: \"English\", value: \"en\" },\n { label: \"Greek\", value: \"el\" },\n { label: \"Spanish\", value: \"es\" },\n ],\n defaultValue: [\"en\"],\n }),\n settings.checkbox(\"includeHI\", {\n label: \"Include hearing impaired\",\n defaultValue: true,\n }),\n ],\n },\n`;\n}\n\nfunction makePluginFile(\n name: string,\n preset: Preset,\n capabilities: Capability[],\n advanced: boolean,\n): string {\n const resources = capabilities\n .map((capability) => resourceBlock(capability, advanced))\n .join(\"\\n \");\n const install = makeInstallBlock(preset);\n const importSpec =\n install.length > 0 ? \"definePlugin, settings\" : \"definePlugin\";\n const installBlock = install.length > 0 ? `${install}` : \"\";\n\n return `import { ${importSpec} } from \"@streamfox/plugin-sdk\";\n\nexport const plugin = definePlugin({\n plugin: {\n id: \"com.example.${name}\",\n name: \"${name}\",\n version: \"0.1.0\",\n description: \"Generated StreamFox plugin scaffold\",\n },\n${installBlock} resources: {\n ${resources}\n },\n});\n`;\n}\n\nfunction makeServerFile(language: Language): string {\n const pluginImport = language === \"ts\" ? \"./plugin\" : \"./plugin.js\";\n return `import { serve } from \"@streamfox/plugin-sdk\";\nimport { plugin } from \"${pluginImport}\";\n\nconst { url, installURL, launchURL } = await serve(plugin, {\n port: Number(process.env.PORT ?? 7000),\n integration: {\n installScheme: \"streamfox\",\n launchBaseURL: \"https://streamfox.app/#\",\n autoOpen: \"none\",\n },\n});\n\nconsole.log(\"Plugin manifest:\", url);\nconsole.log(\"Plugin installer deeplink:\", installURL);\nconsole.log(\"Plugin launch URL:\", launchURL);\n`;\n}\n\nfunction makeVitestFile(language: Language): string {\n const pluginImport = language === \"ts\" ? \"../src/plugin\" : \"../src/plugin.js\";\n return `import { describe, expect, it } from \"vitest\";\nimport { createServer } from \"@streamfox/plugin-sdk\";\nimport { plugin } from \"${pluginImport}\";\n\ndescribe(\"scaffold smoke\", () => {\n it(\"serves manifest and studio config\", async () => {\n const app = createServer(plugin, { frontend: false });\n\n const manifestResponse = await app.request(\"/manifest\");\n expect(manifestResponse.status).toBe(200);\n\n const studioResponse = await app.request(\"/studio-config\");\n expect(studioResponse.status).toBe(200);\n });\n});\n`;\n}\n\nconst tsConfig = `{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"strict\": true,\n \"declaration\": true,\n \"outDir\": \"dist\",\n \"rootDir\": \"src\",\n \"types\": [\"node\"]\n },\n \"include\": [\"src\"]\n}\n`;\n\nconst prettierConfig = `{\n \"semi\": true,\n \"singleQuote\": false,\n \"trailingComma\": \"all\"\n}\n`;\n\nconst prettierIgnore = `dist\nnode_modules\n.DS_Store\n`;\n\nfunction makeReadme(\n projectName: string,\n preset: Preset,\n capabilities: Capability[],\n advanced: boolean,\n): string {\n const capabilitiesList = capabilities\n .map((capability) => `- ${capability}`)\n .join(\"\\n\");\n\n const endpointForCapability = (capability: Capability): string => {\n switch (capability) {\n case \"catalog\":\n return \"/catalog/:mediaType/:catalogID\";\n case \"meta\":\n return \"/meta/:mediaType/:itemID\";\n case \"stream\":\n return \"/stream/:mediaType/:itemID\";\n case \"subtitles\":\n return \"/subtitles/:mediaType/:itemID\";\n case \"plugin_catalog\":\n return \"/plugin_catalog/:catalogID/:pluginKind\";\n default:\n return `/${capability}`;\n }\n };\n\n const endpointLines = [\n \"- GET /manifest\",\n \"- GET /studio-config\",\n ...capabilities.map(\n (capability) => `- GET ${endpointForCapability(capability)}`,\n ),\n ].join(\"\\n\");\n\n return `# ${projectName}\n\nGenerated with create-streamfox-plugin.\n\nPreset: \\`${preset}\\`\nAdvanced template: \\`${advanced ? \"enabled\" : \"disabled\"}\\`\n\n## Scripts\n\n- npm run dev\n- npm run build\n- npm run format\n- npm run start\n- npm run test\n\n## Implemented Capabilities\n\n${capabilitiesList}\n\n## Stream Model\n\n- Unified transport model via \\`stream.transport\\`\n- Capability declaration via \\`resources.stream.supportedTransports\\`\n- Optional selection controls via \\`stream.selection\\`\n\n## Endpoints\n\n${endpointLines}\n`;\n}\n\nexport async function scaffoldProject(options: ScaffoldOptions): Promise<void> {\n const capabilities = sortedCapabilities([\n options.preset,\n ...(options.extraCapabilities ?? []),\n ]);\n const sdkVersion =\n (options.sdkVersion ?? DEFAULT_SDK_VERSION).trim() || DEFAULT_SDK_VERSION;\n const advanced = options.advanced ?? false;\n\n await ensureTargetDoesNotExist(options.targetDir);\n\n const srcDir = path.join(options.targetDir, \"src\");\n const testDir = path.join(options.targetDir, \"test\");\n\n await mkdir(srcDir, { recursive: true });\n await mkdir(testDir, { recursive: true });\n\n await writeFile(\n path.join(options.targetDir, \"package.json\"),\n makePackageJson(options.projectName, options.language, sdkVersion),\n );\n await writeFile(\n path.join(options.targetDir, \".prettierrc.json\"),\n prettierConfig,\n );\n await writeFile(\n path.join(options.targetDir, \".prettierignore\"),\n prettierIgnore,\n );\n await writeFile(\n path.join(options.targetDir, \"README.md\"),\n makeReadme(options.projectName, options.preset, capabilities, advanced),\n );\n\n if (options.language === \"ts\") {\n await writeFile(path.join(options.targetDir, \"tsconfig.json\"), tsConfig);\n await writeFile(\n path.join(srcDir, \"plugin.ts\"),\n makePluginFile(\n options.projectName,\n options.preset,\n capabilities,\n advanced,\n ),\n );\n await writeFile(path.join(srcDir, \"server.ts\"), makeServerFile(\"ts\"));\n await writeFile(path.join(testDir, \"plugin.test.ts\"), makeVitestFile(\"ts\"));\n } else {\n await writeFile(\n path.join(srcDir, \"plugin.js\"),\n makePluginFile(\n options.projectName,\n options.preset,\n capabilities,\n advanced,\n ),\n );\n await writeFile(path.join(srcDir, \"server.js\"), makeServerFile(\"js\"));\n await writeFile(path.join(testDir, \"plugin.test.js\"), makeVitestFile(\"js\"));\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@streamfox/create-streamfox-plugin",
3
+ "version": "0.3.0",
4
+ "description": "Standalone CLI to scaffold StreamFox plugin projects",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-streamfox-plugin": "dist/cli.cjs"
8
+ },
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "format": "prettier --write .",
26
+ "format:check": "prettier --check .",
27
+ "test": "vitest run",
28
+ "typecheck": "tsc --noEmit",
29
+ "check": "npm run format:check && npm run typecheck && npm test && npm run build"
30
+ },
31
+ "dependencies": {
32
+ "commander": "^12.1.0",
33
+ "prompts": "^2.4.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^24.6.0",
37
+ "@types/prompts": "^2.4.9",
38
+ "prettier": "^3.6.2",
39
+ "tsup": "^8.5.0",
40
+ "typescript": "^5.9.2",
41
+ "vitest": "^2.1.9"
42
+ },
43
+ "engines": {
44
+ "node": ">=20"
45
+ }
46
+ }