@streamfox/create-streamfox-plugin 0.3.0 → 0.3.1

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
@@ -47,8 +47,8 @@ create-streamfox-plugin my-plugin --yes
47
47
  | `[directory]` | positional | `my-media-plugin` | Output directory. |
48
48
  | `--ts` | flag | `true` (unless `--js`) | Generate TypeScript template. |
49
49
  | `--js` | flag | no | Generate JavaScript template. |
50
- | `--preset <preset>` | enum | `meta` | One of: `catalog`, `meta`, `stream`, `subtitles`, `plugin_catalog`. |
51
- | `--capabilities <a,b,c>` | csv enum list | none | Adds extra capabilities on top of preset. |
50
+ | `--preset <preset>` | enum | `meta` | Legacy/compat primary template hint. Interactive mode now asks for capabilities directly. |
51
+ | `--capabilities <a,b,c>` | csv enum list | `meta` | Selected capabilities. One of: `catalog`, `meta`, `stream`, `subtitles`, `plugin_catalog`. |
52
52
  | `--advanced` | flag | `false` | Generate richer examples (torrent/usenet/archive/trailers/distribution). |
53
53
  | `--sdk-version <range>` | string | `^0.2.0` | Dependency range for `@streamfox/plugin-sdk`. |
54
54
  | `--yes` | flag | `false` | Skip prompts and use provided/default values. |
@@ -56,13 +56,12 @@ create-streamfox-plugin my-plugin --yes
56
56
 
57
57
  ## Examples
58
58
 
59
- Create subtitles preset with advanced examples:
59
+ Create a subtitles + meta + stream plugin with advanced examples:
60
60
 
61
61
  ```bash
62
62
  create-streamfox-plugin streamfox-opensubs \
63
63
  --ts \
64
- --preset subtitles \
65
- --capabilities meta,stream \
64
+ --capabilities subtitles,meta,stream \
66
65
  --advanced \
67
66
  --sdk-version ^0.2.0 \
68
67
  --yes
package/dist/cli.cjs CHANGED
@@ -269,8 +269,8 @@ ${advanced ? ` {
269
269
  return "";
270
270
  }
271
271
  }
272
- function makeInstallBlock(preset) {
273
- if (preset !== "subtitles") {
272
+ function makeInstallBlock(capabilities) {
273
+ if (!capabilities.includes("subtitles")) {
274
274
  return "";
275
275
  }
276
276
  return ` install: {
@@ -295,9 +295,9 @@ function makeInstallBlock(preset) {
295
295
  },
296
296
  `;
297
297
  }
298
- function makePluginFile(name, preset, capabilities, advanced) {
298
+ function makePluginFile(name, capabilities, advanced) {
299
299
  const resources = capabilities.map((capability) => resourceBlock(capability, advanced)).join("\n ");
300
- const install = makeInstallBlock(preset);
300
+ const install = makeInstallBlock(capabilities);
301
301
  const importSpec = install.length > 0 ? "definePlugin, settings" : "definePlugin";
302
302
  const installBlock = install.length > 0 ? `${install}` : "";
303
303
  return `import { ${importSpec} } from "@streamfox/plugin-sdk";
@@ -377,7 +377,7 @@ var prettierIgnore = `dist
377
377
  node_modules
378
378
  .DS_Store
379
379
  `;
380
- function makeReadme(projectName, preset, capabilities, advanced) {
380
+ function makeReadme(projectName, capabilities, advanced) {
381
381
  const capabilitiesList = capabilities.map((capability) => `- ${capability}`).join("\n");
382
382
  const endpointForCapability = (capability) => {
383
383
  switch (capability) {
@@ -406,7 +406,7 @@ function makeReadme(projectName, preset, capabilities, advanced) {
406
406
 
407
407
  Generated with create-streamfox-plugin.
408
408
 
409
- Preset: \`${preset}\`
409
+ Capabilities: \`${capabilities.join(", ")}\`
410
410
  Advanced template: \`${advanced ? "enabled" : "disabled"}\`
411
411
 
412
412
  ## Scripts
@@ -433,8 +433,8 @@ ${endpointLines}
433
433
  `;
434
434
  }
435
435
  async function scaffoldProject(options) {
436
- const capabilities = sortedCapabilities([
437
- options.preset,
436
+ const capabilities = options.capabilities ? sortedCapabilities(options.capabilities) : sortedCapabilities([
437
+ options.preset ?? DEFAULT_PRESET,
438
438
  ...options.extraCapabilities ?? []
439
439
  ]);
440
440
  const sdkVersion = (options.sdkVersion ?? DEFAULT_SDK_VERSION).trim() || DEFAULT_SDK_VERSION;
@@ -458,7 +458,7 @@ async function scaffoldProject(options) {
458
458
  );
459
459
  await (0, import_promises.writeFile)(
460
460
  import_node_path.default.join(options.targetDir, "README.md"),
461
- makeReadme(options.projectName, options.preset, capabilities, advanced)
461
+ makeReadme(options.projectName, capabilities, advanced)
462
462
  );
463
463
  if (options.language === "ts") {
464
464
  await (0, import_promises.writeFile)(import_node_path.default.join(options.targetDir, "tsconfig.json"), tsConfig);
@@ -466,7 +466,6 @@ async function scaffoldProject(options) {
466
466
  import_node_path.default.join(srcDir, "plugin.ts"),
467
467
  makePluginFile(
468
468
  options.projectName,
469
- options.preset,
470
469
  capabilities,
471
470
  advanced
472
471
  )
@@ -478,7 +477,6 @@ async function scaffoldProject(options) {
478
477
  import_node_path.default.join(srcDir, "plugin.js"),
479
478
  makePluginFile(
480
479
  options.projectName,
481
- options.preset,
482
480
  capabilities,
483
481
  advanced
484
482
  )
@@ -533,7 +531,12 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
533
531
  directory: directoryArg ?? "my-media-plugin",
534
532
  language: options.ts ? "ts" : options.js ? "js" : "ts",
535
533
  preset: options.preset ?? DEFAULT_PRESET,
536
- extraCapabilities: options.capabilities ?? [],
534
+ capabilities: Array.from(
535
+ /* @__PURE__ */ new Set([
536
+ options.preset ?? DEFAULT_PRESET,
537
+ ...options.capabilities ?? []
538
+ ])
539
+ ),
537
540
  advanced: options.advanced ?? false,
538
541
  sdkVersion: options.sdkVersion ?? DEFAULT_SDK_VERSION
539
542
  };
@@ -541,7 +544,7 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
541
544
  let directory = promptDefaults.directory;
542
545
  let language = promptDefaults.language;
543
546
  let preset = promptDefaults.preset;
544
- let extraCapabilities = promptDefaults.extraCapabilities;
547
+ let capabilities = promptDefaults.capabilities;
545
548
  let advanced = promptDefaults.advanced;
546
549
  let sdkVersion = promptDefaults.sdkVersion;
547
550
  if (shouldPrompt) {
@@ -563,26 +566,17 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
563
566
  ],
564
567
  initial: language === "ts" ? 0 : 1
565
568
  },
566
- {
567
- type: "select",
568
- name: "preset",
569
- message: "Plugin preset",
570
- choices: CAPABILITIES.map((capability) => ({
571
- title: capability,
572
- value: capability
573
- })),
574
- initial: CAPABILITIES.indexOf(preset)
575
- },
576
569
  {
577
570
  type: "multiselect",
578
- name: "extraCapabilities",
579
- message: "Extra capabilities (optional)",
571
+ name: "capabilities",
572
+ message: "Plugin capabilities",
580
573
  choices: CAPABILITIES.map((capability) => ({
581
574
  title: capability,
582
- value: capability
575
+ value: capability,
576
+ selected: capabilities.includes(capability)
583
577
  })),
584
578
  instructions: false,
585
- min: 0,
579
+ min: 1,
586
580
  hint: "Space to select"
587
581
  },
588
582
  {
@@ -606,8 +600,8 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
606
600
  );
607
601
  directory = answers.directory;
608
602
  language = answers.language;
609
- preset = answers.preset ?? promptDefaults.preset;
610
- extraCapabilities = answers.extraCapabilities ?? [];
603
+ capabilities = answers.capabilities ?? promptDefaults.capabilities;
604
+ preset = capabilities[0] ?? promptDefaults.preset;
611
605
  advanced = Boolean(answers.advanced ?? promptDefaults.advanced);
612
606
  sdkVersion = answers.sdkVersion ?? promptDefaults.sdkVersion;
613
607
  }
@@ -617,8 +611,8 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
617
611
  targetDir,
618
612
  projectName,
619
613
  language,
614
+ capabilities,
620
615
  preset,
621
- extraCapabilities,
622
616
  advanced,
623
617
  sdkVersion
624
618
  });
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../package.json","../src/scaffold.ts"],"sourcesContent":["import path from \"node:path\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport prompts from \"prompts\";\nimport packageJSON from \"../package.json\";\nimport {\n CAPABILITIES,\n DEFAULT_PRESET,\n DEFAULT_SDK_VERSION,\n scaffoldProject,\n type Capability,\n type Language,\n type Preset,\n} from \"./scaffold\";\n\nfunction capabilitiesLabel(): string {\n return CAPABILITIES.join(\", \");\n}\n\nfunction parsePreset(input: string): Preset {\n if (CAPABILITIES.includes(input as Capability)) {\n return input as Preset;\n }\n throw new InvalidArgumentError(\n `Invalid preset '${input}'. Use one of: ${capabilitiesLabel()}`,\n );\n}\n\nfunction parseCapabilitiesList(input: string): Capability[] {\n const parsed = input\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => value.length > 0) as Capability[];\n\n for (const capability of parsed) {\n if (!CAPABILITIES.includes(capability)) {\n throw new InvalidArgumentError(\n `Invalid capability '${capability}'. Use one of: ${capabilitiesLabel()}`,\n );\n }\n }\n\n return Array.from(new Set(parsed));\n}\n\nconst program = new Command();\n\nprogram\n .name(\"create-streamfox-plugin\")\n .version(packageJSON.version, \"-v, --version\", \"display the current CLI version\")\n .description(\"Scaffold StreamFox plugin projects with modern JS/TS presets\")\n .showHelpAfterError()\n .argument(\"[directory]\", \"output directory\")\n .option(\"--ts\", \"use TypeScript template\")\n .option(\"--js\", \"use JavaScript template\")\n .option(\n \"--preset <preset>\",\n `plugin preset: ${capabilitiesLabel()}`,\n parsePreset,\n )\n .option(\n \"--capabilities <capabilities>\",\n \"extra capabilities as comma-separated list\",\n parseCapabilitiesList,\n )\n .option(\"--advanced\", \"generate advanced capability examples\")\n .option(\n \"--sdk-version <range>\",\n \"@streamfox/plugin-sdk version/range\",\n DEFAULT_SDK_VERSION,\n )\n .option(\"--yes\", \"skip prompts and use defaults\")\n .action(\n async (\n directoryArg: string | undefined,\n options: {\n ts?: boolean;\n js?: boolean;\n yes?: boolean;\n preset?: Preset;\n capabilities?: Capability[];\n advanced?: boolean;\n sdkVersion?: string;\n },\n ) => {\n if (options.ts && options.js) {\n throw new InvalidArgumentError(\"Choose either --ts or --js, not both.\");\n }\n\n const promptDefaults = {\n directory: directoryArg ?? \"my-media-plugin\",\n language: options.ts ? \"ts\" : options.js ? \"js\" : \"ts\",\n preset: options.preset ?? DEFAULT_PRESET,\n extraCapabilities: options.capabilities ?? [],\n advanced: options.advanced ?? false,\n sdkVersion: options.sdkVersion ?? DEFAULT_SDK_VERSION,\n };\n\n const shouldPrompt = !options.yes;\n\n let directory = promptDefaults.directory;\n let language = promptDefaults.language as Language;\n let preset = promptDefaults.preset;\n let extraCapabilities = promptDefaults.extraCapabilities;\n let advanced = promptDefaults.advanced;\n let sdkVersion = promptDefaults.sdkVersion;\n\n if (shouldPrompt) {\n const answers = await prompts(\n [\n {\n type: \"text\",\n name: \"directory\",\n message: \"Project directory\",\n initial: directory,\n },\n {\n type: \"select\",\n name: \"language\",\n message: \"Template language\",\n choices: [\n { title: \"TypeScript\", value: \"ts\" },\n { title: \"JavaScript\", value: \"js\" },\n ],\n initial: language === \"ts\" ? 0 : 1,\n },\n {\n type: \"select\",\n name: \"preset\",\n message: \"Plugin preset\",\n choices: CAPABILITIES.map((capability) => ({\n title: capability,\n value: capability,\n })),\n initial: CAPABILITIES.indexOf(preset),\n },\n {\n type: \"multiselect\",\n name: \"extraCapabilities\",\n message: \"Extra capabilities (optional)\",\n choices: CAPABILITIES.map((capability) => ({\n title: capability,\n value: capability,\n })),\n instructions: false,\n min: 0,\n hint: \"Space to select\",\n },\n {\n type: \"confirm\",\n name: \"advanced\",\n message: \"Generate advanced capability examples?\",\n initial: advanced,\n },\n {\n type: \"text\",\n name: \"sdkVersion\",\n message: \"SDK dependency version/range\",\n initial: sdkVersion,\n },\n ],\n {\n onCancel: () => {\n process.exit(1);\n },\n },\n );\n\n directory = answers.directory;\n language = answers.language;\n preset = (answers.preset ?? promptDefaults.preset) as Preset;\n extraCapabilities = (answers.extraCapabilities ?? []) as Capability[];\n advanced = Boolean(answers.advanced ?? promptDefaults.advanced);\n sdkVersion = answers.sdkVersion ?? promptDefaults.sdkVersion;\n }\n\n const targetDir = path.resolve(process.cwd(), directory);\n const projectName = path.basename(targetDir);\n\n await scaffoldProject({\n targetDir,\n projectName,\n language,\n preset,\n extraCapabilities,\n advanced,\n sdkVersion,\n });\n\n console.log(`Created ${projectName} at ${targetDir}`);\n console.log(\"Next steps:\");\n console.log(` cd ${directory}`);\n console.log(\" npm install\");\n console.log(\" npm run dev\");\n },\n );\n\nvoid program.parseAsync(process.argv).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${message}`);\n process.exit(1);\n});\n","{\n \"name\": \"@streamfox/create-streamfox-plugin\",\n \"version\": \"0.3.0\",\n \"description\": \"Standalone CLI to scaffold StreamFox plugin projects\",\n \"type\": \"module\",\n \"bin\": {\n \"create-streamfox-plugin\": \"dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"check\": \"npm run format:check && npm run typecheck && npm test && npm run build\"\n },\n \"dependencies\": {\n \"commander\": \"^12.1.0\",\n \"prompts\": \"^2.4.2\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.6.0\",\n \"@types/prompts\": \"^2.4.9\",\n \"prettier\": \"^3.6.2\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.2\",\n \"vitest\": \"^2.1.9\"\n },\n \"engines\": {\n \"node\": \">=20\"\n }\n}\n","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":["import_node_path","path","prompts","path"]}
1
+ {"version":3,"sources":["../src/cli.ts","../package.json","../src/scaffold.ts"],"sourcesContent":["import path from \"node:path\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport prompts from \"prompts\";\nimport packageJSON from \"../package.json\";\nimport {\n CAPABILITIES,\n DEFAULT_PRESET,\n DEFAULT_SDK_VERSION,\n scaffoldProject,\n type Capability,\n type Language,\n type Preset,\n} from \"./scaffold\";\n\nfunction capabilitiesLabel(): string {\n return CAPABILITIES.join(\", \");\n}\n\nfunction parsePreset(input: string): Preset {\n if (CAPABILITIES.includes(input as Capability)) {\n return input as Preset;\n }\n throw new InvalidArgumentError(\n `Invalid preset '${input}'. Use one of: ${capabilitiesLabel()}`,\n );\n}\n\nfunction parseCapabilitiesList(input: string): Capability[] {\n const parsed = input\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => value.length > 0) as Capability[];\n\n for (const capability of parsed) {\n if (!CAPABILITIES.includes(capability)) {\n throw new InvalidArgumentError(\n `Invalid capability '${capability}'. Use one of: ${capabilitiesLabel()}`,\n );\n }\n }\n\n return Array.from(new Set(parsed));\n}\n\nconst program = new Command();\n\nprogram\n .name(\"create-streamfox-plugin\")\n .version(packageJSON.version, \"-v, --version\", \"display the current CLI version\")\n .description(\"Scaffold StreamFox plugin projects with modern JS/TS presets\")\n .showHelpAfterError()\n .argument(\"[directory]\", \"output directory\")\n .option(\"--ts\", \"use TypeScript template\")\n .option(\"--js\", \"use JavaScript template\")\n .option(\n \"--preset <preset>\",\n `plugin preset: ${capabilitiesLabel()}`,\n parsePreset,\n )\n .option(\n \"--capabilities <capabilities>\",\n \"extra capabilities as comma-separated list\",\n parseCapabilitiesList,\n )\n .option(\"--advanced\", \"generate advanced capability examples\")\n .option(\n \"--sdk-version <range>\",\n \"@streamfox/plugin-sdk version/range\",\n DEFAULT_SDK_VERSION,\n )\n .option(\"--yes\", \"skip prompts and use defaults\")\n .action(\n async (\n directoryArg: string | undefined,\n options: {\n ts?: boolean;\n js?: boolean;\n yes?: boolean;\n preset?: Preset;\n capabilities?: Capability[];\n advanced?: boolean;\n sdkVersion?: string;\n },\n ) => {\n if (options.ts && options.js) {\n throw new InvalidArgumentError(\"Choose either --ts or --js, not both.\");\n }\n\n const promptDefaults = {\n directory: directoryArg ?? \"my-media-plugin\",\n language: options.ts ? \"ts\" : options.js ? \"js\" : \"ts\",\n preset: options.preset ?? DEFAULT_PRESET,\n capabilities: Array.from(\n new Set([\n options.preset ?? DEFAULT_PRESET,\n ...(options.capabilities ?? []),\n ]),\n ) as Capability[],\n advanced: options.advanced ?? false,\n sdkVersion: options.sdkVersion ?? DEFAULT_SDK_VERSION,\n };\n\n const shouldPrompt = !options.yes;\n\n let directory = promptDefaults.directory;\n let language = promptDefaults.language as Language;\n let preset = promptDefaults.preset;\n let capabilities = promptDefaults.capabilities;\n let advanced = promptDefaults.advanced;\n let sdkVersion = promptDefaults.sdkVersion;\n\n if (shouldPrompt) {\n const answers = await prompts(\n [\n {\n type: \"text\",\n name: \"directory\",\n message: \"Project directory\",\n initial: directory,\n },\n {\n type: \"select\",\n name: \"language\",\n message: \"Template language\",\n choices: [\n { title: \"TypeScript\", value: \"ts\" },\n { title: \"JavaScript\", value: \"js\" },\n ],\n initial: language === \"ts\" ? 0 : 1,\n },\n {\n type: \"multiselect\",\n name: \"capabilities\",\n message: \"Plugin capabilities\",\n choices: CAPABILITIES.map((capability) => ({\n title: capability,\n value: capability,\n selected: capabilities.includes(capability),\n })),\n instructions: false,\n min: 1,\n hint: \"Space to select\",\n },\n {\n type: \"confirm\",\n name: \"advanced\",\n message: \"Generate advanced capability examples?\",\n initial: advanced,\n },\n {\n type: \"text\",\n name: \"sdkVersion\",\n message: \"SDK dependency version/range\",\n initial: sdkVersion,\n },\n ],\n {\n onCancel: () => {\n process.exit(1);\n },\n },\n );\n\n directory = answers.directory;\n language = answers.language;\n capabilities = (answers.capabilities ??\n promptDefaults.capabilities) as Capability[];\n preset =\n capabilities[0] ?? promptDefaults.preset;\n advanced = Boolean(answers.advanced ?? promptDefaults.advanced);\n sdkVersion = answers.sdkVersion ?? promptDefaults.sdkVersion;\n }\n\n const targetDir = path.resolve(process.cwd(), directory);\n const projectName = path.basename(targetDir);\n\n await scaffoldProject({\n targetDir,\n projectName,\n language,\n capabilities,\n preset,\n advanced,\n sdkVersion,\n });\n\n console.log(`Created ${projectName} at ${targetDir}`);\n console.log(\"Next steps:\");\n console.log(` cd ${directory}`);\n console.log(\" npm install\");\n console.log(\" npm run dev\");\n },\n );\n\nvoid program.parseAsync(process.argv).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${message}`);\n process.exit(1);\n});\n","{\n \"name\": \"@streamfox/create-streamfox-plugin\",\n \"version\": \"0.3.0\",\n \"description\": \"Standalone CLI to scaffold StreamFox plugin projects\",\n \"type\": \"module\",\n \"bin\": {\n \"create-streamfox-plugin\": \"dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"check\": \"npm run format:check && npm run typecheck && npm test && npm run build\"\n },\n \"dependencies\": {\n \"commander\": \"^12.1.0\",\n \"prompts\": \"^2.4.2\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.6.0\",\n \"@types/prompts\": \"^2.4.9\",\n \"prettier\": \"^3.6.2\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.2\",\n \"vitest\": \"^2.1.9\"\n },\n \"engines\": {\n \"node\": \">=20\"\n }\n}\n","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 capabilities?: Capability[];\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(capabilities: Capability[]): string {\n if (!capabilities.includes(\"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 capabilities: Capability[],\n advanced: boolean,\n): string {\n const resources = capabilities\n .map((capability) => resourceBlock(capability, advanced))\n .join(\"\\n \");\n const install = makeInstallBlock(capabilities);\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 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\nCapabilities: \\`${capabilities.join(\", \")}\\`\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 = options.capabilities\n ? sortedCapabilities(options.capabilities)\n : sortedCapabilities([\n options.preset ?? DEFAULT_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, 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 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 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":["import_node_path","path","prompts","path"]}
package/dist/cli.js CHANGED
@@ -246,8 +246,8 @@ ${advanced ? ` {
246
246
  return "";
247
247
  }
248
248
  }
249
- function makeInstallBlock(preset) {
250
- if (preset !== "subtitles") {
249
+ function makeInstallBlock(capabilities) {
250
+ if (!capabilities.includes("subtitles")) {
251
251
  return "";
252
252
  }
253
253
  return ` install: {
@@ -272,9 +272,9 @@ function makeInstallBlock(preset) {
272
272
  },
273
273
  `;
274
274
  }
275
- function makePluginFile(name, preset, capabilities, advanced) {
275
+ function makePluginFile(name, capabilities, advanced) {
276
276
  const resources = capabilities.map((capability) => resourceBlock(capability, advanced)).join("\n ");
277
- const install = makeInstallBlock(preset);
277
+ const install = makeInstallBlock(capabilities);
278
278
  const importSpec = install.length > 0 ? "definePlugin, settings" : "definePlugin";
279
279
  const installBlock = install.length > 0 ? `${install}` : "";
280
280
  return `import { ${importSpec} } from "@streamfox/plugin-sdk";
@@ -354,7 +354,7 @@ var prettierIgnore = `dist
354
354
  node_modules
355
355
  .DS_Store
356
356
  `;
357
- function makeReadme(projectName, preset, capabilities, advanced) {
357
+ function makeReadme(projectName, capabilities, advanced) {
358
358
  const capabilitiesList = capabilities.map((capability) => `- ${capability}`).join("\n");
359
359
  const endpointForCapability = (capability) => {
360
360
  switch (capability) {
@@ -383,7 +383,7 @@ function makeReadme(projectName, preset, capabilities, advanced) {
383
383
 
384
384
  Generated with create-streamfox-plugin.
385
385
 
386
- Preset: \`${preset}\`
386
+ Capabilities: \`${capabilities.join(", ")}\`
387
387
  Advanced template: \`${advanced ? "enabled" : "disabled"}\`
388
388
 
389
389
  ## Scripts
@@ -410,8 +410,8 @@ ${endpointLines}
410
410
  `;
411
411
  }
412
412
  async function scaffoldProject(options) {
413
- const capabilities = sortedCapabilities([
414
- options.preset,
413
+ const capabilities = options.capabilities ? sortedCapabilities(options.capabilities) : sortedCapabilities([
414
+ options.preset ?? DEFAULT_PRESET,
415
415
  ...options.extraCapabilities ?? []
416
416
  ]);
417
417
  const sdkVersion = (options.sdkVersion ?? DEFAULT_SDK_VERSION).trim() || DEFAULT_SDK_VERSION;
@@ -435,7 +435,7 @@ async function scaffoldProject(options) {
435
435
  );
436
436
  await writeFile(
437
437
  path.join(options.targetDir, "README.md"),
438
- makeReadme(options.projectName, options.preset, capabilities, advanced)
438
+ makeReadme(options.projectName, capabilities, advanced)
439
439
  );
440
440
  if (options.language === "ts") {
441
441
  await writeFile(path.join(options.targetDir, "tsconfig.json"), tsConfig);
@@ -443,7 +443,6 @@ async function scaffoldProject(options) {
443
443
  path.join(srcDir, "plugin.ts"),
444
444
  makePluginFile(
445
445
  options.projectName,
446
- options.preset,
447
446
  capabilities,
448
447
  advanced
449
448
  )
@@ -455,7 +454,6 @@ async function scaffoldProject(options) {
455
454
  path.join(srcDir, "plugin.js"),
456
455
  makePluginFile(
457
456
  options.projectName,
458
- options.preset,
459
457
  capabilities,
460
458
  advanced
461
459
  )
@@ -510,7 +508,12 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
510
508
  directory: directoryArg ?? "my-media-plugin",
511
509
  language: options.ts ? "ts" : options.js ? "js" : "ts",
512
510
  preset: options.preset ?? DEFAULT_PRESET,
513
- extraCapabilities: options.capabilities ?? [],
511
+ capabilities: Array.from(
512
+ /* @__PURE__ */ new Set([
513
+ options.preset ?? DEFAULT_PRESET,
514
+ ...options.capabilities ?? []
515
+ ])
516
+ ),
514
517
  advanced: options.advanced ?? false,
515
518
  sdkVersion: options.sdkVersion ?? DEFAULT_SDK_VERSION
516
519
  };
@@ -518,7 +521,7 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
518
521
  let directory = promptDefaults.directory;
519
522
  let language = promptDefaults.language;
520
523
  let preset = promptDefaults.preset;
521
- let extraCapabilities = promptDefaults.extraCapabilities;
524
+ let capabilities = promptDefaults.capabilities;
522
525
  let advanced = promptDefaults.advanced;
523
526
  let sdkVersion = promptDefaults.sdkVersion;
524
527
  if (shouldPrompt) {
@@ -540,26 +543,17 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
540
543
  ],
541
544
  initial: language === "ts" ? 0 : 1
542
545
  },
543
- {
544
- type: "select",
545
- name: "preset",
546
- message: "Plugin preset",
547
- choices: CAPABILITIES.map((capability) => ({
548
- title: capability,
549
- value: capability
550
- })),
551
- initial: CAPABILITIES.indexOf(preset)
552
- },
553
546
  {
554
547
  type: "multiselect",
555
- name: "extraCapabilities",
556
- message: "Extra capabilities (optional)",
548
+ name: "capabilities",
549
+ message: "Plugin capabilities",
557
550
  choices: CAPABILITIES.map((capability) => ({
558
551
  title: capability,
559
- value: capability
552
+ value: capability,
553
+ selected: capabilities.includes(capability)
560
554
  })),
561
555
  instructions: false,
562
- min: 0,
556
+ min: 1,
563
557
  hint: "Space to select"
564
558
  },
565
559
  {
@@ -583,8 +577,8 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
583
577
  );
584
578
  directory = answers.directory;
585
579
  language = answers.language;
586
- preset = answers.preset ?? promptDefaults.preset;
587
- extraCapabilities = answers.extraCapabilities ?? [];
580
+ capabilities = answers.capabilities ?? promptDefaults.capabilities;
581
+ preset = capabilities[0] ?? promptDefaults.preset;
588
582
  advanced = Boolean(answers.advanced ?? promptDefaults.advanced);
589
583
  sdkVersion = answers.sdkVersion ?? promptDefaults.sdkVersion;
590
584
  }
@@ -594,8 +588,8 @@ program.name("create-streamfox-plugin").version(package_default.version, "-v, --
594
588
  targetDir,
595
589
  projectName,
596
590
  language,
591
+ capabilities,
597
592
  preset,
598
- extraCapabilities,
599
593
  advanced,
600
594
  sdkVersion
601
595
  });
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../package.json","../src/scaffold.ts"],"sourcesContent":["import path from \"node:path\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport prompts from \"prompts\";\nimport packageJSON from \"../package.json\";\nimport {\n CAPABILITIES,\n DEFAULT_PRESET,\n DEFAULT_SDK_VERSION,\n scaffoldProject,\n type Capability,\n type Language,\n type Preset,\n} from \"./scaffold\";\n\nfunction capabilitiesLabel(): string {\n return CAPABILITIES.join(\", \");\n}\n\nfunction parsePreset(input: string): Preset {\n if (CAPABILITIES.includes(input as Capability)) {\n return input as Preset;\n }\n throw new InvalidArgumentError(\n `Invalid preset '${input}'. Use one of: ${capabilitiesLabel()}`,\n );\n}\n\nfunction parseCapabilitiesList(input: string): Capability[] {\n const parsed = input\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => value.length > 0) as Capability[];\n\n for (const capability of parsed) {\n if (!CAPABILITIES.includes(capability)) {\n throw new InvalidArgumentError(\n `Invalid capability '${capability}'. Use one of: ${capabilitiesLabel()}`,\n );\n }\n }\n\n return Array.from(new Set(parsed));\n}\n\nconst program = new Command();\n\nprogram\n .name(\"create-streamfox-plugin\")\n .version(packageJSON.version, \"-v, --version\", \"display the current CLI version\")\n .description(\"Scaffold StreamFox plugin projects with modern JS/TS presets\")\n .showHelpAfterError()\n .argument(\"[directory]\", \"output directory\")\n .option(\"--ts\", \"use TypeScript template\")\n .option(\"--js\", \"use JavaScript template\")\n .option(\n \"--preset <preset>\",\n `plugin preset: ${capabilitiesLabel()}`,\n parsePreset,\n )\n .option(\n \"--capabilities <capabilities>\",\n \"extra capabilities as comma-separated list\",\n parseCapabilitiesList,\n )\n .option(\"--advanced\", \"generate advanced capability examples\")\n .option(\n \"--sdk-version <range>\",\n \"@streamfox/plugin-sdk version/range\",\n DEFAULT_SDK_VERSION,\n )\n .option(\"--yes\", \"skip prompts and use defaults\")\n .action(\n async (\n directoryArg: string | undefined,\n options: {\n ts?: boolean;\n js?: boolean;\n yes?: boolean;\n preset?: Preset;\n capabilities?: Capability[];\n advanced?: boolean;\n sdkVersion?: string;\n },\n ) => {\n if (options.ts && options.js) {\n throw new InvalidArgumentError(\"Choose either --ts or --js, not both.\");\n }\n\n const promptDefaults = {\n directory: directoryArg ?? \"my-media-plugin\",\n language: options.ts ? \"ts\" : options.js ? \"js\" : \"ts\",\n preset: options.preset ?? DEFAULT_PRESET,\n extraCapabilities: options.capabilities ?? [],\n advanced: options.advanced ?? false,\n sdkVersion: options.sdkVersion ?? DEFAULT_SDK_VERSION,\n };\n\n const shouldPrompt = !options.yes;\n\n let directory = promptDefaults.directory;\n let language = promptDefaults.language as Language;\n let preset = promptDefaults.preset;\n let extraCapabilities = promptDefaults.extraCapabilities;\n let advanced = promptDefaults.advanced;\n let sdkVersion = promptDefaults.sdkVersion;\n\n if (shouldPrompt) {\n const answers = await prompts(\n [\n {\n type: \"text\",\n name: \"directory\",\n message: \"Project directory\",\n initial: directory,\n },\n {\n type: \"select\",\n name: \"language\",\n message: \"Template language\",\n choices: [\n { title: \"TypeScript\", value: \"ts\" },\n { title: \"JavaScript\", value: \"js\" },\n ],\n initial: language === \"ts\" ? 0 : 1,\n },\n {\n type: \"select\",\n name: \"preset\",\n message: \"Plugin preset\",\n choices: CAPABILITIES.map((capability) => ({\n title: capability,\n value: capability,\n })),\n initial: CAPABILITIES.indexOf(preset),\n },\n {\n type: \"multiselect\",\n name: \"extraCapabilities\",\n message: \"Extra capabilities (optional)\",\n choices: CAPABILITIES.map((capability) => ({\n title: capability,\n value: capability,\n })),\n instructions: false,\n min: 0,\n hint: \"Space to select\",\n },\n {\n type: \"confirm\",\n name: \"advanced\",\n message: \"Generate advanced capability examples?\",\n initial: advanced,\n },\n {\n type: \"text\",\n name: \"sdkVersion\",\n message: \"SDK dependency version/range\",\n initial: sdkVersion,\n },\n ],\n {\n onCancel: () => {\n process.exit(1);\n },\n },\n );\n\n directory = answers.directory;\n language = answers.language;\n preset = (answers.preset ?? promptDefaults.preset) as Preset;\n extraCapabilities = (answers.extraCapabilities ?? []) as Capability[];\n advanced = Boolean(answers.advanced ?? promptDefaults.advanced);\n sdkVersion = answers.sdkVersion ?? promptDefaults.sdkVersion;\n }\n\n const targetDir = path.resolve(process.cwd(), directory);\n const projectName = path.basename(targetDir);\n\n await scaffoldProject({\n targetDir,\n projectName,\n language,\n preset,\n extraCapabilities,\n advanced,\n sdkVersion,\n });\n\n console.log(`Created ${projectName} at ${targetDir}`);\n console.log(\"Next steps:\");\n console.log(` cd ${directory}`);\n console.log(\" npm install\");\n console.log(\" npm run dev\");\n },\n );\n\nvoid program.parseAsync(process.argv).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${message}`);\n process.exit(1);\n});\n","{\n \"name\": \"@streamfox/create-streamfox-plugin\",\n \"version\": \"0.3.0\",\n \"description\": \"Standalone CLI to scaffold StreamFox plugin projects\",\n \"type\": \"module\",\n \"bin\": {\n \"create-streamfox-plugin\": \"dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"check\": \"npm run format:check && npm run typecheck && npm test && npm run build\"\n },\n \"dependencies\": {\n \"commander\": \"^12.1.0\",\n \"prompts\": \"^2.4.2\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.6.0\",\n \"@types/prompts\": \"^2.4.9\",\n \"prettier\": \"^3.6.2\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.2\",\n \"vitest\": \"^2.1.9\"\n },\n \"engines\": {\n \"node\": \">=20\"\n }\n}\n","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":["path","path"]}
1
+ {"version":3,"sources":["../src/cli.ts","../package.json","../src/scaffold.ts"],"sourcesContent":["import path from \"node:path\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport prompts from \"prompts\";\nimport packageJSON from \"../package.json\";\nimport {\n CAPABILITIES,\n DEFAULT_PRESET,\n DEFAULT_SDK_VERSION,\n scaffoldProject,\n type Capability,\n type Language,\n type Preset,\n} from \"./scaffold\";\n\nfunction capabilitiesLabel(): string {\n return CAPABILITIES.join(\", \");\n}\n\nfunction parsePreset(input: string): Preset {\n if (CAPABILITIES.includes(input as Capability)) {\n return input as Preset;\n }\n throw new InvalidArgumentError(\n `Invalid preset '${input}'. Use one of: ${capabilitiesLabel()}`,\n );\n}\n\nfunction parseCapabilitiesList(input: string): Capability[] {\n const parsed = input\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => value.length > 0) as Capability[];\n\n for (const capability of parsed) {\n if (!CAPABILITIES.includes(capability)) {\n throw new InvalidArgumentError(\n `Invalid capability '${capability}'. Use one of: ${capabilitiesLabel()}`,\n );\n }\n }\n\n return Array.from(new Set(parsed));\n}\n\nconst program = new Command();\n\nprogram\n .name(\"create-streamfox-plugin\")\n .version(packageJSON.version, \"-v, --version\", \"display the current CLI version\")\n .description(\"Scaffold StreamFox plugin projects with modern JS/TS presets\")\n .showHelpAfterError()\n .argument(\"[directory]\", \"output directory\")\n .option(\"--ts\", \"use TypeScript template\")\n .option(\"--js\", \"use JavaScript template\")\n .option(\n \"--preset <preset>\",\n `plugin preset: ${capabilitiesLabel()}`,\n parsePreset,\n )\n .option(\n \"--capabilities <capabilities>\",\n \"extra capabilities as comma-separated list\",\n parseCapabilitiesList,\n )\n .option(\"--advanced\", \"generate advanced capability examples\")\n .option(\n \"--sdk-version <range>\",\n \"@streamfox/plugin-sdk version/range\",\n DEFAULT_SDK_VERSION,\n )\n .option(\"--yes\", \"skip prompts and use defaults\")\n .action(\n async (\n directoryArg: string | undefined,\n options: {\n ts?: boolean;\n js?: boolean;\n yes?: boolean;\n preset?: Preset;\n capabilities?: Capability[];\n advanced?: boolean;\n sdkVersion?: string;\n },\n ) => {\n if (options.ts && options.js) {\n throw new InvalidArgumentError(\"Choose either --ts or --js, not both.\");\n }\n\n const promptDefaults = {\n directory: directoryArg ?? \"my-media-plugin\",\n language: options.ts ? \"ts\" : options.js ? \"js\" : \"ts\",\n preset: options.preset ?? DEFAULT_PRESET,\n capabilities: Array.from(\n new Set([\n options.preset ?? DEFAULT_PRESET,\n ...(options.capabilities ?? []),\n ]),\n ) as Capability[],\n advanced: options.advanced ?? false,\n sdkVersion: options.sdkVersion ?? DEFAULT_SDK_VERSION,\n };\n\n const shouldPrompt = !options.yes;\n\n let directory = promptDefaults.directory;\n let language = promptDefaults.language as Language;\n let preset = promptDefaults.preset;\n let capabilities = promptDefaults.capabilities;\n let advanced = promptDefaults.advanced;\n let sdkVersion = promptDefaults.sdkVersion;\n\n if (shouldPrompt) {\n const answers = await prompts(\n [\n {\n type: \"text\",\n name: \"directory\",\n message: \"Project directory\",\n initial: directory,\n },\n {\n type: \"select\",\n name: \"language\",\n message: \"Template language\",\n choices: [\n { title: \"TypeScript\", value: \"ts\" },\n { title: \"JavaScript\", value: \"js\" },\n ],\n initial: language === \"ts\" ? 0 : 1,\n },\n {\n type: \"multiselect\",\n name: \"capabilities\",\n message: \"Plugin capabilities\",\n choices: CAPABILITIES.map((capability) => ({\n title: capability,\n value: capability,\n selected: capabilities.includes(capability),\n })),\n instructions: false,\n min: 1,\n hint: \"Space to select\",\n },\n {\n type: \"confirm\",\n name: \"advanced\",\n message: \"Generate advanced capability examples?\",\n initial: advanced,\n },\n {\n type: \"text\",\n name: \"sdkVersion\",\n message: \"SDK dependency version/range\",\n initial: sdkVersion,\n },\n ],\n {\n onCancel: () => {\n process.exit(1);\n },\n },\n );\n\n directory = answers.directory;\n language = answers.language;\n capabilities = (answers.capabilities ??\n promptDefaults.capabilities) as Capability[];\n preset =\n capabilities[0] ?? promptDefaults.preset;\n advanced = Boolean(answers.advanced ?? promptDefaults.advanced);\n sdkVersion = answers.sdkVersion ?? promptDefaults.sdkVersion;\n }\n\n const targetDir = path.resolve(process.cwd(), directory);\n const projectName = path.basename(targetDir);\n\n await scaffoldProject({\n targetDir,\n projectName,\n language,\n capabilities,\n preset,\n advanced,\n sdkVersion,\n });\n\n console.log(`Created ${projectName} at ${targetDir}`);\n console.log(\"Next steps:\");\n console.log(` cd ${directory}`);\n console.log(\" npm install\");\n console.log(\" npm run dev\");\n },\n );\n\nvoid program.parseAsync(process.argv).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${message}`);\n process.exit(1);\n});\n","{\n \"name\": \"@streamfox/create-streamfox-plugin\",\n \"version\": \"0.3.0\",\n \"description\": \"Standalone CLI to scaffold StreamFox plugin projects\",\n \"type\": \"module\",\n \"bin\": {\n \"create-streamfox-plugin\": \"dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"check\": \"npm run format:check && npm run typecheck && npm test && npm run build\"\n },\n \"dependencies\": {\n \"commander\": \"^12.1.0\",\n \"prompts\": \"^2.4.2\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.6.0\",\n \"@types/prompts\": \"^2.4.9\",\n \"prettier\": \"^3.6.2\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.2\",\n \"vitest\": \"^2.1.9\"\n },\n \"engines\": {\n \"node\": \">=20\"\n }\n}\n","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 capabilities?: Capability[];\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(capabilities: Capability[]): string {\n if (!capabilities.includes(\"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 capabilities: Capability[],\n advanced: boolean,\n): string {\n const resources = capabilities\n .map((capability) => resourceBlock(capability, advanced))\n .join(\"\\n \");\n const install = makeInstallBlock(capabilities);\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 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\nCapabilities: \\`${capabilities.join(\", \")}\\`\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 = options.capabilities\n ? sortedCapabilities(options.capabilities)\n : sortedCapabilities([\n options.preset ?? DEFAULT_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, 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 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 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":["path","path"]}
package/dist/index.cjs CHANGED
@@ -231,8 +231,8 @@ ${advanced ? ` {
231
231
  return "";
232
232
  }
233
233
  }
234
- function makeInstallBlock(preset) {
235
- if (preset !== "subtitles") {
234
+ function makeInstallBlock(capabilities) {
235
+ if (!capabilities.includes("subtitles")) {
236
236
  return "";
237
237
  }
238
238
  return ` install: {
@@ -257,9 +257,9 @@ function makeInstallBlock(preset) {
257
257
  },
258
258
  `;
259
259
  }
260
- function makePluginFile(name, preset, capabilities, advanced) {
260
+ function makePluginFile(name, capabilities, advanced) {
261
261
  const resources = capabilities.map((capability) => resourceBlock(capability, advanced)).join("\n ");
262
- const install = makeInstallBlock(preset);
262
+ const install = makeInstallBlock(capabilities);
263
263
  const importSpec = install.length > 0 ? "definePlugin, settings" : "definePlugin";
264
264
  const installBlock = install.length > 0 ? `${install}` : "";
265
265
  return `import { ${importSpec} } from "@streamfox/plugin-sdk";
@@ -339,7 +339,7 @@ var prettierIgnore = `dist
339
339
  node_modules
340
340
  .DS_Store
341
341
  `;
342
- function makeReadme(projectName, preset, capabilities, advanced) {
342
+ function makeReadme(projectName, capabilities, advanced) {
343
343
  const capabilitiesList = capabilities.map((capability) => `- ${capability}`).join("\n");
344
344
  const endpointForCapability = (capability) => {
345
345
  switch (capability) {
@@ -368,7 +368,7 @@ function makeReadme(projectName, preset, capabilities, advanced) {
368
368
 
369
369
  Generated with create-streamfox-plugin.
370
370
 
371
- Preset: \`${preset}\`
371
+ Capabilities: \`${capabilities.join(", ")}\`
372
372
  Advanced template: \`${advanced ? "enabled" : "disabled"}\`
373
373
 
374
374
  ## Scripts
@@ -395,8 +395,8 @@ ${endpointLines}
395
395
  `;
396
396
  }
397
397
  async function scaffoldProject(options) {
398
- const capabilities = sortedCapabilities([
399
- options.preset,
398
+ const capabilities = options.capabilities ? sortedCapabilities(options.capabilities) : sortedCapabilities([
399
+ options.preset ?? DEFAULT_PRESET,
400
400
  ...options.extraCapabilities ?? []
401
401
  ]);
402
402
  const sdkVersion = (options.sdkVersion ?? DEFAULT_SDK_VERSION).trim() || DEFAULT_SDK_VERSION;
@@ -420,7 +420,7 @@ async function scaffoldProject(options) {
420
420
  );
421
421
  await (0, import_promises.writeFile)(
422
422
  import_node_path.default.join(options.targetDir, "README.md"),
423
- makeReadme(options.projectName, options.preset, capabilities, advanced)
423
+ makeReadme(options.projectName, capabilities, advanced)
424
424
  );
425
425
  if (options.language === "ts") {
426
426
  await (0, import_promises.writeFile)(import_node_path.default.join(options.targetDir, "tsconfig.json"), tsConfig);
@@ -428,7 +428,6 @@ async function scaffoldProject(options) {
428
428
  import_node_path.default.join(srcDir, "plugin.ts"),
429
429
  makePluginFile(
430
430
  options.projectName,
431
- options.preset,
432
431
  capabilities,
433
432
  advanced
434
433
  )
@@ -440,7 +439,6 @@ async function scaffoldProject(options) {
440
439
  import_node_path.default.join(srcDir, "plugin.js"),
441
440
  makePluginFile(
442
441
  options.projectName,
443
- options.preset,
444
442
  capabilities,
445
443
  advanced
446
444
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/scaffold.ts"],"sourcesContent":["export * from \"./scaffold\";\n","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":["path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/scaffold.ts"],"sourcesContent":["export * from \"./scaffold\";\n","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 capabilities?: Capability[];\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(capabilities: Capability[]): string {\n if (!capabilities.includes(\"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 capabilities: Capability[],\n advanced: boolean,\n): string {\n const resources = capabilities\n .map((capability) => resourceBlock(capability, advanced))\n .join(\"\\n \");\n const install = makeInstallBlock(capabilities);\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 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\nCapabilities: \\`${capabilities.join(\", \")}\\`\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 = options.capabilities\n ? sortedCapabilities(options.capabilities)\n : sortedCapabilities([\n options.preset ?? DEFAULT_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, 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 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 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":["path"]}
package/dist/index.d.cts CHANGED
@@ -8,7 +8,8 @@ interface ScaffoldOptions {
8
8
  targetDir: string;
9
9
  projectName: string;
10
10
  language: Language;
11
- preset: Preset;
11
+ preset?: Preset;
12
+ capabilities?: Capability[];
12
13
  advanced?: boolean;
13
14
  extraCapabilities?: Capability[];
14
15
  sdkVersion?: string;
package/dist/index.d.ts CHANGED
@@ -8,7 +8,8 @@ interface ScaffoldOptions {
8
8
  targetDir: string;
9
9
  projectName: string;
10
10
  language: Language;
11
- preset: Preset;
11
+ preset?: Preset;
12
+ capabilities?: Capability[];
12
13
  advanced?: boolean;
13
14
  extraCapabilities?: Capability[];
14
15
  sdkVersion?: string;
package/dist/index.js CHANGED
@@ -193,8 +193,8 @@ ${advanced ? ` {
193
193
  return "";
194
194
  }
195
195
  }
196
- function makeInstallBlock(preset) {
197
- if (preset !== "subtitles") {
196
+ function makeInstallBlock(capabilities) {
197
+ if (!capabilities.includes("subtitles")) {
198
198
  return "";
199
199
  }
200
200
  return ` install: {
@@ -219,9 +219,9 @@ function makeInstallBlock(preset) {
219
219
  },
220
220
  `;
221
221
  }
222
- function makePluginFile(name, preset, capabilities, advanced) {
222
+ function makePluginFile(name, capabilities, advanced) {
223
223
  const resources = capabilities.map((capability) => resourceBlock(capability, advanced)).join("\n ");
224
- const install = makeInstallBlock(preset);
224
+ const install = makeInstallBlock(capabilities);
225
225
  const importSpec = install.length > 0 ? "definePlugin, settings" : "definePlugin";
226
226
  const installBlock = install.length > 0 ? `${install}` : "";
227
227
  return `import { ${importSpec} } from "@streamfox/plugin-sdk";
@@ -301,7 +301,7 @@ var prettierIgnore = `dist
301
301
  node_modules
302
302
  .DS_Store
303
303
  `;
304
- function makeReadme(projectName, preset, capabilities, advanced) {
304
+ function makeReadme(projectName, capabilities, advanced) {
305
305
  const capabilitiesList = capabilities.map((capability) => `- ${capability}`).join("\n");
306
306
  const endpointForCapability = (capability) => {
307
307
  switch (capability) {
@@ -330,7 +330,7 @@ function makeReadme(projectName, preset, capabilities, advanced) {
330
330
 
331
331
  Generated with create-streamfox-plugin.
332
332
 
333
- Preset: \`${preset}\`
333
+ Capabilities: \`${capabilities.join(", ")}\`
334
334
  Advanced template: \`${advanced ? "enabled" : "disabled"}\`
335
335
 
336
336
  ## Scripts
@@ -357,8 +357,8 @@ ${endpointLines}
357
357
  `;
358
358
  }
359
359
  async function scaffoldProject(options) {
360
- const capabilities = sortedCapabilities([
361
- options.preset,
360
+ const capabilities = options.capabilities ? sortedCapabilities(options.capabilities) : sortedCapabilities([
361
+ options.preset ?? DEFAULT_PRESET,
362
362
  ...options.extraCapabilities ?? []
363
363
  ]);
364
364
  const sdkVersion = (options.sdkVersion ?? DEFAULT_SDK_VERSION).trim() || DEFAULT_SDK_VERSION;
@@ -382,7 +382,7 @@ async function scaffoldProject(options) {
382
382
  );
383
383
  await writeFile(
384
384
  path.join(options.targetDir, "README.md"),
385
- makeReadme(options.projectName, options.preset, capabilities, advanced)
385
+ makeReadme(options.projectName, capabilities, advanced)
386
386
  );
387
387
  if (options.language === "ts") {
388
388
  await writeFile(path.join(options.targetDir, "tsconfig.json"), tsConfig);
@@ -390,7 +390,6 @@ async function scaffoldProject(options) {
390
390
  path.join(srcDir, "plugin.ts"),
391
391
  makePluginFile(
392
392
  options.projectName,
393
- options.preset,
394
393
  capabilities,
395
394
  advanced
396
395
  )
@@ -402,7 +401,6 @@ async function scaffoldProject(options) {
402
401
  path.join(srcDir, "plugin.js"),
403
402
  makePluginFile(
404
403
  options.projectName,
405
- options.preset,
406
404
  capabilities,
407
405
  advanced
408
406
  )
package/dist/index.js.map CHANGED
@@ -1 +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":[]}
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 capabilities?: Capability[];\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(capabilities: Capability[]): string {\n if (!capabilities.includes(\"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 capabilities: Capability[],\n advanced: boolean,\n): string {\n const resources = capabilities\n .map((capability) => resourceBlock(capability, advanced))\n .join(\"\\n \");\n const install = makeInstallBlock(capabilities);\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 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\nCapabilities: \\`${capabilities.join(\", \")}\\`\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 = options.capabilities\n ? sortedCapabilities(options.capabilities)\n : sortedCapabilities([\n options.preset ?? DEFAULT_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, 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 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 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamfox/create-streamfox-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Standalone CLI to scaffold StreamFox plugin projects",
5
5
  "type": "module",
6
6
  "bin": {