From 7a83832a44e891eb18d1dd3e3170028512138b2c Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Wed, 10 Sep 2025 18:42:17 +0200 Subject: [PATCH] [FEATURE] Ability to specify composer packages manually A new config option is introduced, which allows to bypass the automatic package discovery and to supply the package paths manually. Resolves: #21 --- .gitignore | 2 + README.md | 5 ++ src/types.ts | 2 + src/typo3project.ts | 18 +++++-- src/utils.ts | 26 +++++++++- tests/integration/typo3extension.test.ts | 45 +++++++++++------ tests/integration/typo3project.test.ts | 25 ++++++++-- .../uninitializedProject/composer.json | 3 ++ .../Configuration/ViteEntrypoints.json | 1 + .../Resources/Private/Alt.entry.ts | 1 + .../Resources/Private/Css/Main.css | 3 ++ .../Resources/Private/JavaScript/Main.ts | 1 + .../Resources/Private/Main.entry.ts | 2 + .../packages/test_extension/composer.json | 9 ++++ .../Configuration/ViteEntrypoints.json | 1 + .../Resources/Private/Css/Vendor.css | 3 ++ .../Resources/Private/JavaScript/Vendor.ts | 1 + .../Resources/Private/Vendor.entry.ts | 2 + .../vendor-extension/composer.json | 8 +++ .../determineRelevantTypo3Extensions.test.ts | 50 ++++++++++++++++--- .../composer.json | 3 ++ .../Configuration/ViteEntrypoints.json | 1 + .../Resources/Private/Main.entry.js | 1 + .../packages/composerExtension/composer.json | 8 +++ .../Configuration/ViteEntrypoints.json | 1 + .../Resources/Private/Main.entry.js | 1 + .../packages/composerExtension2/composer.json | 8 +++ .../packages/library/composer.json | 3 ++ 28 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 tests/integration/uninitializedProject/composer.json create mode 100644 tests/integration/uninitializedProject/packages/test_extension/Configuration/ViteEntrypoints.json create mode 100644 tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Alt.entry.ts create mode 100644 tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Css/Main.css create mode 100644 tests/integration/uninitializedProject/packages/test_extension/Resources/Private/JavaScript/Main.ts create mode 100644 tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Main.entry.ts create mode 100644 tests/integration/uninitializedProject/packages/test_extension/composer.json create mode 100644 tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Configuration/ViteEntrypoints.json create mode 100644 tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Css/Vendor.css create mode 100644 tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/JavaScript/Vendor.ts create mode 100644 tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Vendor.entry.ts create mode 100644 tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/composer.json create mode 100644 tests/unit/fixtures/uninitializedComposerProject/composer.json create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Configuration/ViteEntrypoints.json create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Resources/Private/Main.entry.js create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/composer.json create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Configuration/ViteEntrypoints.json create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Resources/Private/Main.entry.js create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/composer.json create mode 100644 tests/unit/fixtures/uninitializedComposerProject/packages/library/composer.json diff --git a/.gitignore b/.gitignore index cfa551b..ccebc66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ tests/distribution/vite-plugin-typo3*.tgz tests/distribution/**/public tests/integration/project/public tests/integration/project/packages/test_extension/Resources/Public +tests/integration/uninitializedProject/public +tests/integration/uninitializedProject/packages/test_extension/Resources/Public diff --git a/README.md b/README.md index 51562ac..8abc07b 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ You can provide additional configuration to the plugin, for example: and `@` are created for all available extension folders. If set to `@`, only `@` aliases are created, if set to `EXT`, only `EXT:` aliases are created. If set to `false`, alias creation is skipped altogether. +- `composerPackagePaths` (array of paths, default `null`): If specified, the automatic + discovery of composer packages via `vendor/composer/installed.json` will be skipped. Instead, + the specified paths will be scanned for `composer.json` files to be used instead. This can + be helpful in CI scenarios where PHP dependencies aren't available in the frontend build + step. ### Fixing CORS issues diff --git a/src/types.ts b/src/types.ts index bf6123f..14d996d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,7 @@ export interface UserConfig { target?: PluginTarget; entrypointFile?: string; entrypointIgnorePatterns?: string[]; + composerPackagePaths?: string[]; debug?: boolean; aliases?: AliasConfig; } @@ -10,6 +11,7 @@ export interface PluginConfig extends UserConfig { target: PluginTarget; entrypointFile: string; composerContext: T; + composerPackagePaths?: string[]; entrypointIgnorePatterns: string[]; debug: boolean; aliases: AliasConfig; diff --git a/src/typo3project.ts b/src/typo3project.ts index 3c3213d..80db72f 100644 --- a/src/typo3project.ts +++ b/src/typo3project.ts @@ -10,7 +10,8 @@ import type { import { addAliases, addRollupInputs, - determineAvailableTypo3Extensions, + determineAvailableTypo3ExtensionsFromComposer, + determineAvailableTypo3ExtensionsFromPaths, findEntrypointsInExtensions, getDefaultAllowedOrigins, getDefaultIgnoreList, @@ -90,9 +91,18 @@ export default function typo3project( ); // Extract relevant TYPO3 extensions from composer metadata - availableExtensions = determineAvailableTypo3Extensions( - pluginConfig.composerContext, - ); + if (pluginConfig.composerPackagePaths) { + availableExtensions = + determineAvailableTypo3ExtensionsFromPaths( + config.root ?? process.cwd(), + pluginConfig.composerPackagePaths, + ); + } else { + availableExtensions = + determineAvailableTypo3ExtensionsFromComposer( + pluginConfig.composerContext, + ); + } // Add path alias for each extension config.resolve ??= {}; diff --git a/src/utils.ts b/src/utils.ts index 16d8b86..a1b1aec 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -140,7 +140,7 @@ export function findEntrypointsInExtensions( return entrypoints; } -export function determineAvailableTypo3Extensions( +export function determineAvailableTypo3ExtensionsFromComposer( composerContext: Typo3ProjectContext, ): Typo3ExtensionContext[] { const composerInstalled = join( @@ -179,6 +179,30 @@ export function determineAvailableTypo3Extensions( return installedExtensions; } +export function determineAvailableTypo3ExtensionsFromPaths( + rootPath: string, + composerPackagePaths: string[], +): Typo3ExtensionContext[] { + return composerPackagePaths + .map((path: string) => resolve(rootPath, path)) + .map((absolutePath: string) => { + const composerFile = absolutePath + "/composer.json"; + if (!fs.existsSync(composerFile)) { + throw new Error( + `Invalid composer package in "${absolutePath}", composer.json not found.`, + ); + } + return createComposerContext( + readJsonFile(composerFile), + absolutePath, + ); + }) + .filter( + (context: ComposerContext) => + context.type === "typo3-cms-extension", + ) as Typo3ExtensionContext[]; +} + export function outputDebugInformation( availableExtensions: Typo3ExtensionContext[], entrypoints: string[], diff --git a/tests/integration/typo3extension.test.ts b/tests/integration/typo3extension.test.ts index 5a8a71b..f3a164b 100644 --- a/tests/integration/typo3extension.test.ts +++ b/tests/integration/typo3extension.test.ts @@ -3,15 +3,28 @@ import { build } from "vite"; import typo3 from "../../src"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; +import { UserConfig } from "../../src/types"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -test("vite build works for TYPO3 extension", async () => { - const root = join(__dirname, "project/packages/test_extension"); +interface TestCase { + projectName: string; + pluginConfig: UserConfig; +} + +test.for([ + { projectName: "project", pluginConfig: {} }, + { projectName: "uninitializedProject", pluginConfig: {} }, +])("vite build works for TYPO3 extension in $projectName", async (testCase) => { + const root = join( + __dirname, + `${testCase.projectName}/packages/test_extension`, + ); const output = await build({ root, - plugins: [typo3({ target: "extension" })], + plugins: [typo3({ target: "extension", ...testCase.pluginConfig })], build: { + // @ts-expect-error "entry" is specified by plugin, so no need to specify it here lib: { cssFileName: "style", }, @@ -26,21 +39,23 @@ test("vite build works for TYPO3 extension", async () => { expect(esmOutput.output[0].fileName).toMatchInlineSnapshot( `"Alt.entry.js"`, ); - expect(esmOutput.output[0].code).toMatchInlineSnapshot(` - "//#region tests/integration/project/packages/test_extension/Resources/Private/Alt.entry.ts - console.log("Alt.entry.ts"); - //#endregion - " - `); + expect(esmOutput.output[0].code).toMatch( + ` +//#region tests/integration/${testCase.projectName}/packages/test_extension/Resources/Private/Alt.entry.ts +console.log("Alt.entry.ts"); +//#endregion + `.trim(), + ); expect(esmOutput.output[1].fileName).toMatchInlineSnapshot( `"Main.entry.js"`, ); - expect(esmOutput.output[1].code).toMatchInlineSnapshot(` - "//#region tests/integration/project/packages/test_extension/Resources/Private/JavaScript/Main.ts - console.log("Main.ts"); - //#endregion - " - `); + expect(esmOutput.output[1].code).toMatch( + ` +//#region tests/integration/${testCase.projectName}/packages/test_extension/Resources/Private/JavaScript/Main.ts +console.log("Main.ts"); +//#endregion + `.trim(), + ); expect(esmOutput.output[2].fileName).toMatchInlineSnapshot(`"style.css"`); expect(esmOutput.output[2].source).toMatchInlineSnapshot(` "body{background:red} diff --git a/tests/integration/typo3project.test.ts b/tests/integration/typo3project.test.ts index 4b505a5..f6a672a 100644 --- a/tests/integration/typo3project.test.ts +++ b/tests/integration/typo3project.test.ts @@ -3,15 +3,32 @@ import { build } from "vite"; import typo3 from "../../src"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; -import { RolldownOutput, OutputChunk, OutputAsset } from "rolldown"; +import type { RolldownOutput, OutputChunk, OutputAsset } from "rolldown"; +import { UserConfig } from "../../src/types"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -test("vite build works for TYPO3 project", async () => { - const root = join(__dirname, "project"); +interface TestCase { + projectName: string; + pluginConfig: UserConfig; +} + +test.for([ + { projectName: "project", pluginConfig: {} }, + { + projectName: "uninitializedProject", + pluginConfig: { + composerPackagePaths: [ + "packages/test_extension/", + "vendor/test-vendor/vendor-extension/", + ], + }, + }, +])("vite build works for TYPO3 project $projectName", async (testCase) => { + const root = join(__dirname, testCase.projectName); const output = (await build({ root, - plugins: [typo3()], + plugins: [typo3(testCase.pluginConfig)], })) as RolldownOutput; const sortedOutput: (OutputAsset | OutputChunk)[] = output.output.sort( diff --git a/tests/integration/uninitializedProject/composer.json b/tests/integration/uninitializedProject/composer.json new file mode 100644 index 0000000..a13a33c --- /dev/null +++ b/tests/integration/uninitializedProject/composer.json @@ -0,0 +1,3 @@ +{ + "type": "project" +} diff --git a/tests/integration/uninitializedProject/packages/test_extension/Configuration/ViteEntrypoints.json b/tests/integration/uninitializedProject/packages/test_extension/Configuration/ViteEntrypoints.json new file mode 100644 index 0000000..90f899a --- /dev/null +++ b/tests/integration/uninitializedProject/packages/test_extension/Configuration/ViteEntrypoints.json @@ -0,0 +1 @@ +["../Resources/Private/*.entry.ts"] diff --git a/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Alt.entry.ts b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Alt.entry.ts new file mode 100644 index 0000000..92192b9 --- /dev/null +++ b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Alt.entry.ts @@ -0,0 +1 @@ +console.log("Alt.entry.ts"); diff --git a/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Css/Main.css b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Css/Main.css new file mode 100644 index 0000000..5c5e887 --- /dev/null +++ b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Css/Main.css @@ -0,0 +1,3 @@ +body { + background: red; +} diff --git a/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/JavaScript/Main.ts b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/JavaScript/Main.ts new file mode 100644 index 0000000..7ee9eba --- /dev/null +++ b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/JavaScript/Main.ts @@ -0,0 +1 @@ +console.log("Main.ts"); diff --git a/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Main.entry.ts b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Main.entry.ts new file mode 100644 index 0000000..ba65d87 --- /dev/null +++ b/tests/integration/uninitializedProject/packages/test_extension/Resources/Private/Main.entry.ts @@ -0,0 +1,2 @@ +import "./Css/Main.css"; +import "./JavaScript/Main.ts"; diff --git a/tests/integration/uninitializedProject/packages/test_extension/composer.json b/tests/integration/uninitializedProject/packages/test_extension/composer.json new file mode 100644 index 0000000..ff3c499 --- /dev/null +++ b/tests/integration/uninitializedProject/packages/test_extension/composer.json @@ -0,0 +1,9 @@ +{ + "name": "praetorius/test-extension", + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "test_extension" + } + } +} diff --git a/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Configuration/ViteEntrypoints.json b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Configuration/ViteEntrypoints.json new file mode 100644 index 0000000..4144a79 --- /dev/null +++ b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Configuration/ViteEntrypoints.json @@ -0,0 +1 @@ +["../Resources/Private/Vendor.entry.ts"] diff --git a/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Css/Vendor.css b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Css/Vendor.css new file mode 100644 index 0000000..fa2302e --- /dev/null +++ b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Css/Vendor.css @@ -0,0 +1,3 @@ +body { + background: blue; +} diff --git a/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/JavaScript/Vendor.ts b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/JavaScript/Vendor.ts new file mode 100644 index 0000000..0e542d7 --- /dev/null +++ b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/JavaScript/Vendor.ts @@ -0,0 +1 @@ +console.log("Vendor.ts"); diff --git a/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Vendor.entry.ts b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Vendor.entry.ts new file mode 100644 index 0000000..f097d0b --- /dev/null +++ b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/Resources/Private/Vendor.entry.ts @@ -0,0 +1,2 @@ +import "./Css/Vendor.css"; +import "./JavaScript/Vendor.ts"; diff --git a/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/composer.json b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/composer.json new file mode 100644 index 0000000..da4c542 --- /dev/null +++ b/tests/integration/uninitializedProject/vendor/test-vendor/vendor-extension/composer.json @@ -0,0 +1,8 @@ +{ + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "vendor_extension" + } + } +} diff --git a/tests/unit/determineRelevantTypo3Extensions.test.ts b/tests/unit/determineRelevantTypo3Extensions.test.ts index eae6284..9ec4d21 100644 --- a/tests/unit/determineRelevantTypo3Extensions.test.ts +++ b/tests/unit/determineRelevantTypo3Extensions.test.ts @@ -1,5 +1,8 @@ import { describe, test, expect, vi, beforeEach } from "vitest"; -import { determineAvailableTypo3Extensions } from "../../src/utils"; +import { + determineAvailableTypo3ExtensionsFromComposer, + determineAvailableTypo3ExtensionsFromPaths, +} from "../../src/utils"; import { vol } from "memfs"; import fixtureDirectoryStructure from "./fixtureDirectoryStructure"; @@ -10,10 +13,10 @@ beforeEach(() => { vol.fromJSON(fixtureDirectoryStructure); }); -describe("determineAvailableTypo3Extensions", () => { - test("determineAvailableTypo3Extensions", () => { +describe("determineAvailableTypo3ExtensionsFromComposer", () => { + test("determineAvailableTypo3ExtensionsFromComposer", () => { expect( - determineAvailableTypo3Extensions({ + determineAvailableTypo3ExtensionsFromComposer({ type: "project", path: "/path/to/fixtures/composerProject", vendorDir: "vendor", @@ -35,7 +38,7 @@ describe("determineAvailableTypo3Extensions", () => { test("no vendor path", () => { expect(() => { - determineAvailableTypo3Extensions({ + determineAvailableTypo3ExtensionsFromComposer({ type: "project", path: "/path/to/fixtures/composerProjectWIthoutVendor", vendorDir: "vendor", @@ -46,7 +49,7 @@ describe("determineAvailableTypo3Extensions", () => { test("invalid installed.json", () => { expect(() => { - determineAvailableTypo3Extensions({ + determineAvailableTypo3ExtensionsFromComposer({ type: "project", path: "/path/to/fixtures/composerProjectInvalidVendor", vendorDir: "vendor", @@ -55,3 +58,38 @@ describe("determineAvailableTypo3Extensions", () => { }).toThrow(); }); }); + +describe("determineAvailableTypo3ExtensionsFromPaths", () => { + test("determineAvailableTypo3ExtensionsFromPaths", () => { + expect( + determineAvailableTypo3ExtensionsFromPaths( + "/path/to/fixtures/uninitializedComposerProject", + [ + "packages/composerExtension", + "packages/composerExtension2", + "packages/library", + ], + ), + ).toEqual([ + { + type: "typo3-cms-extension", + extensionKey: "composer_extension", + path: "/path/to/fixtures/uninitializedComposerProject/packages/composerExtension", + }, + { + type: "typo3-cms-extension", + extensionKey: "namespace_extension", + path: "/path/to/fixtures/uninitializedComposerProject/packages/composerExtension2", + }, + ]); + }); + + test("invalid composer package", () => { + expect(() => { + determineAvailableTypo3ExtensionsFromPaths( + "/path/to/fixtures/uninitializedComposerProject", + ["some/random/folder"], + ); + }).toThrow(); + }); +}); diff --git a/tests/unit/fixtures/uninitializedComposerProject/composer.json b/tests/unit/fixtures/uninitializedComposerProject/composer.json new file mode 100644 index 0000000..a13a33c --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/composer.json @@ -0,0 +1,3 @@ +{ + "type": "project" +} diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Configuration/ViteEntrypoints.json b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Configuration/ViteEntrypoints.json new file mode 100644 index 0000000..b15e7d5 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Configuration/ViteEntrypoints.json @@ -0,0 +1 @@ +["../Resources/Private/Main.entry.js"] diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Resources/Private/Main.entry.js b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Resources/Private/Main.entry.js new file mode 100644 index 0000000..a3714f2 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/Resources/Private/Main.entry.js @@ -0,0 +1 @@ +console.log("Main.entry.js"); diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/composer.json b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/composer.json new file mode 100644 index 0000000..82a97e1 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension/composer.json @@ -0,0 +1,8 @@ +{ + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "composer_extension" + } + } +} diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Configuration/ViteEntrypoints.json b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Configuration/ViteEntrypoints.json new file mode 100644 index 0000000..b15e7d5 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Configuration/ViteEntrypoints.json @@ -0,0 +1 @@ +["../Resources/Private/Main.entry.js"] diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Resources/Private/Main.entry.js b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Resources/Private/Main.entry.js new file mode 100644 index 0000000..a3714f2 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/Resources/Private/Main.entry.js @@ -0,0 +1 @@ +console.log("Main.entry.js"); diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/composer.json b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/composer.json new file mode 100644 index 0000000..caf9cb5 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/composerExtension2/composer.json @@ -0,0 +1,8 @@ +{ + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "namespace_extension" + } + } +} diff --git a/tests/unit/fixtures/uninitializedComposerProject/packages/library/composer.json b/tests/unit/fixtures/uninitializedComposerProject/packages/library/composer.json new file mode 100644 index 0000000..9eccfd3 --- /dev/null +++ b/tests/unit/fixtures/uninitializedComposerProject/packages/library/composer.json @@ -0,0 +1,3 @@ +{ + "type": "library" +}