@marble-sh/backstage-plugin-scaffolder-backend-module-grafana 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @marble-sh/backstage-plugin-scaffolder-backend-module-grafana
2
+
3
+ A scaffolder backend module that adds Grafana provisioning actions to the
4
+ [Backstage Software Templates](https://backstage.io/docs/features/software-templates/)
5
+ scaffolder.
6
+
7
+ > This is the **only write path** in the Grafana plugin suite — every other
8
+ > package is strictly read-only. The action writes to Grafana using the same
9
+ > `grafana.instances` configuration (and service-account tokens) as the rest of
10
+ > the suite.
11
+
12
+ ## Actions
13
+
14
+ ### `grafana:dashboard:create`
15
+
16
+ Creates (or, with `overwrite`, updates) a dashboard in a configured Grafana
17
+ instance via the App Platform `dashboard.grafana.app/v1` API.
18
+
19
+ | Input | Type | Required | Description |
20
+ | -------------- | ---------- | -------- | -------------------------------------------------------------------------- |
21
+ | `title` | `string` | yes | The dashboard title. |
22
+ | `instanceName` | `string` | no | Which configured instance to target. Optional when only one is configured. |
23
+ | `uid` | `string` | no | Dashboard uid (`metadata.name`). Grafana generates one when omitted. |
24
+ | `folderUid` | `string` | no | The uid of the folder to create the dashboard in. |
25
+ | `tags` | `string[]` | no | Dashboard tags. |
26
+ | `dashboard` | `object` | no | Extra dashboard spec fields (panels, templating, …) merged into the spec. |
27
+ | `overwrite` | `boolean` | no | Update the dashboard if it already exists (requires `uid`). |
28
+
29
+ Outputs: `uid`, `url`, and `instanceName`.
30
+
31
+ Example template step:
32
+
33
+ ```yaml
34
+ steps:
35
+ - id: create-dashboard
36
+ name: Create Grafana dashboard
37
+ action: grafana:dashboard:create
38
+ input:
39
+ instanceName: production
40
+ title: ${{ parameters.name }} overview
41
+ tags:
42
+ - ${{ parameters.name }}
43
+ - id: log
44
+ name: Log
45
+ action: debug:log
46
+ input:
47
+ message: 'Created ${{ steps["create-dashboard"].output.url }}'
48
+ ```
49
+
50
+ ## Installation
51
+
52
+ ```sh
53
+ yarn --cwd packages/backend add @marble-sh/backstage-plugin-scaffolder-backend-module-grafana
54
+ ```
55
+
56
+ ```ts
57
+ // packages/backend/src/index.ts
58
+ backend.add(import('@backstage/plugin-scaffolder-backend'));
59
+ backend.add(
60
+ import('@marble-sh/backstage-plugin-scaffolder-backend-module-grafana'),
61
+ );
62
+ ```
63
+
64
+ The action reads its connection details from `grafana.instances` (see the
65
+ [backend plugin](../grafana-backend/README.md) for that configuration). The
66
+ service-account token must have permission to create dashboards.
67
+
68
+ ## Guard rails (`grafana.scaffolder`)
69
+
70
+ Because this module is the suite's only write path, two config options restrict
71
+ what templates may do:
72
+
73
+ ```yaml
74
+ grafana:
75
+ scaffolder:
76
+ allowedInstances: [staging] # defaults to every configured instance
77
+ allowOverwrite: false # defaults to true
78
+ ```
79
+
80
+ - **`allowedInstances`** — which instances the actions may write to.
81
+ - unset (default): every instance under `grafana.instances` is writable.
82
+ - a list of names: writes to any other instance fail with a
83
+ `NotAllowedError`, and automatic instance selection (when `instanceName`
84
+ is omitted) only considers the listed instances — so a template can omit
85
+ `instanceName` whenever exactly one instance is writable, regardless of
86
+ how many are configured. An empty list makes nothing writable. A listed
87
+ name that doesn't exist under `grafana.instances` fails the action with a
88
+ configuration error.
89
+ - **`allowOverwrite`** — whether `grafana:dashboard:create` may update
90
+ existing dashboards.
91
+ - `true` (default): the action's `overwrite: true` input updates the
92
+ dashboard with the given `uid` in place.
93
+ - `false`: any run requesting `overwrite` fails with a `NotAllowedError`;
94
+ the action can only create new dashboards.
95
+
96
+ ## Testing
97
+
98
+ ```sh
99
+ yarn workspace @marble-sh/backstage-plugin-scaffolder-backend-module-grafana test
100
+ ```
101
+
102
+ The action is unit-tested with an injected `fetch` and
103
+ `createMockActionContext`; the module is verified to register its action via
104
+ `startTestBackend`.
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "grafana": {
6
+ "type": "object",
7
+ "properties": {
8
+ "scaffolder": {
9
+ "type": "object",
10
+ "properties": {
11
+ "allowedInstances": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "string"
15
+ },
16
+ "description": "Which configured Grafana instances the scaffolder actions may write to.\n\n - unset (default): every instance under `grafana.instances` is writable. - a list of instance names: only those instances accept writes. Targeting any other instance fails the action with a 403-style error, and automatic instance selection (when `instanceName` is omitted) only considers the listed instances. A listed name that does not exist under `grafana.instances` fails the action with a configuration error."
17
+ },
18
+ "allowOverwrite": {
19
+ "type": "boolean",
20
+ "description": "Whether the `grafana:dashboard:create` action may update existing dashboards.\n\n - `true` (default): the action's `overwrite: true` input updates the dashboard with the given `uid` in place. - `false`: any run requesting `overwrite` fails; the action can only create new dashboards."
21
+ }
22
+ },
23
+ "description": "Guard rails for the Grafana scaffolder actions — the only write path in the plugin suite."
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,139 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var pluginScaffolderNode = require('@backstage/plugin-scaffolder-node');
5
+ var backstagePluginGrafanaNode = require('@marble-sh/backstage-plugin-grafana-node');
6
+
7
+ function readGuardConfig(rootConfig) {
8
+ const config = rootConfig.getOptionalConfig("grafana.scaffolder");
9
+ return {
10
+ allowedInstances: config?.getOptionalStringArray("allowedInstances"),
11
+ allowOverwrite: config?.getOptionalBoolean("allowOverwrite") ?? true
12
+ };
13
+ }
14
+ function resolveInstance(instances, guard, name) {
15
+ const { allowedInstances } = guard;
16
+ if (allowedInstances) {
17
+ const known = new Set(instances.map((instance) => instance.name));
18
+ const unknown = allowedInstances.filter((allowed) => !known.has(allowed));
19
+ if (unknown.length > 0) {
20
+ throw new errors.InputError(
21
+ `grafana.scaffolder.allowedInstances names unknown instance(s) '${unknown.join(
22
+ "', '"
23
+ )}'; configured instances are: ${[...known].join(", ")}`
24
+ );
25
+ }
26
+ }
27
+ if (name) {
28
+ const found = instances.find((instance) => instance.name === name);
29
+ if (!found) {
30
+ throw new errors.NotFoundError(
31
+ `No Grafana instance configured with name '${name}'`
32
+ );
33
+ }
34
+ if (allowedInstances && !allowedInstances.includes(found.name)) {
35
+ throw new errors.NotAllowedError(
36
+ `Grafana instance '${found.name}' is not writable by the scaffolder (grafana.scaffolder.allowedInstances)`
37
+ );
38
+ }
39
+ return found;
40
+ }
41
+ const writable = allowedInstances ? instances.filter((instance) => allowedInstances.includes(instance.name)) : instances;
42
+ if (writable.length === 0) {
43
+ throw new errors.InputError(
44
+ "No Grafana instances are configured and writable by the scaffolder"
45
+ );
46
+ }
47
+ if (writable.length > 1) {
48
+ throw new errors.InputError(
49
+ "Multiple Grafana instances are configured; set input.instanceName to choose one"
50
+ );
51
+ }
52
+ return writable[0];
53
+ }
54
+ function createGrafanaDashboardCreateAction(options) {
55
+ const fetchApi = options.fetch ?? fetch;
56
+ return pluginScaffolderNode.createTemplateAction({
57
+ id: "grafana:dashboard:create",
58
+ description: "Creates or updates a Grafana dashboard via the App Platform API",
59
+ schema: {
60
+ input: {
61
+ instanceName: (z) => z.string({
62
+ description: "The configured Grafana instance to target. Optional when only one instance is configured."
63
+ }).optional(),
64
+ title: (z) => z.string({ description: "The dashboard title" }),
65
+ uid: (z) => z.string({
66
+ description: "The dashboard uid (metadata.name). When omitted, Grafana generates one."
67
+ }).optional(),
68
+ folderUid: (z) => z.string({ description: "The uid of the folder to create it in" }).optional(),
69
+ tags: (z) => z.array(z.string(), { description: "Dashboard tags" }).optional(),
70
+ dashboard: (z) => z.record(z.any(), {
71
+ description: "Additional dashboard spec fields (panels, templating, etc.), merged into the request spec."
72
+ }).optional(),
73
+ overwrite: (z) => z.boolean({
74
+ description: "Update the dashboard if it already exists (requires uid)."
75
+ }).optional()
76
+ },
77
+ output: {
78
+ uid: (z) => z.string({ description: "The uid of the dashboard" }),
79
+ url: (z) => z.string({ description: "The URL of the dashboard" }),
80
+ instanceName: (z) => z.string({
81
+ description: "The instance the dashboard was created in"
82
+ })
83
+ }
84
+ },
85
+ async handler(ctx) {
86
+ const { title, uid, folderUid, tags, dashboard, overwrite } = ctx.input;
87
+ const guard = readGuardConfig(options.config);
88
+ const instance = resolveInstance(
89
+ backstagePluginGrafanaNode.readGrafanaInstances(options.config),
90
+ guard,
91
+ ctx.input.instanceName
92
+ );
93
+ if (overwrite && !guard.allowOverwrite) {
94
+ throw new errors.NotAllowedError(
95
+ "Updating existing dashboards is disabled by configuration (grafana.scaffolder.allowOverwrite)"
96
+ );
97
+ }
98
+ const isUpdate = Boolean(overwrite && uid);
99
+ const collection = `/apis/dashboard.grafana.app/v1/namespaces/${instance.namespace}/dashboards`;
100
+ const path = isUpdate ? `${collection}/${uid}` : collection;
101
+ ctx.logger.info(
102
+ `${isUpdate ? "Updating" : "Creating"} Grafana dashboard '${title}' in instance '${instance.name}'`
103
+ );
104
+ const response = await fetchApi(`${instance.baseUrl}${path}`, {
105
+ method: isUpdate ? "PUT" : "POST",
106
+ headers: {
107
+ Authorization: `Bearer ${instance.token}`,
108
+ "Content-Type": "application/json",
109
+ Accept: "application/json"
110
+ },
111
+ body: JSON.stringify({
112
+ metadata: {
113
+ ...uid ? { name: uid } : {},
114
+ ...folderUid ? { annotations: { "grafana.app/folder": folderUid } } : {}
115
+ },
116
+ spec: {
117
+ title,
118
+ schemaVersion: 41,
119
+ tags: tags ?? [],
120
+ panels: [],
121
+ ...dashboard ?? {}
122
+ }
123
+ })
124
+ });
125
+ if (!response.ok) {
126
+ throw await errors.ResponseError.fromResponse(response);
127
+ }
128
+ const body = await response.json();
129
+ const resultUid = body.metadata?.name ?? uid ?? "";
130
+ const url = `${instance.baseUrl}/d/${resultUid}/${backstagePluginGrafanaNode.slugify(title)}`;
131
+ ctx.output("uid", resultUid);
132
+ ctx.output("url", url);
133
+ ctx.output("instanceName", instance.name);
134
+ }
135
+ });
136
+ }
137
+
138
+ exports.createGrafanaDashboardCreateAction = createGrafanaDashboardCreateAction;
139
+ //# sourceMappingURL=createDashboard.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createDashboard.cjs.js","sources":["../../src/actions/createDashboard.ts"],"sourcesContent":["/*\n * Copyright 2026 Cassidy Marble\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n InputError,\n NotAllowedError,\n NotFoundError,\n ResponseError,\n} from '@backstage/errors';\nimport { createTemplateAction } from '@backstage/plugin-scaffolder-node';\nimport {\n FetchApi,\n GrafanaInstanceConfig,\n readGrafanaInstances,\n slugify,\n} from '@marble-sh/backstage-plugin-grafana-node';\n\ntype ScaffolderGuardConfig = {\n allowedInstances?: string[];\n allowOverwrite: boolean;\n};\n\nfunction readGuardConfig(rootConfig: Config): ScaffolderGuardConfig {\n const config = rootConfig.getOptionalConfig('grafana.scaffolder');\n return {\n allowedInstances: config?.getOptionalStringArray('allowedInstances'),\n allowOverwrite: config?.getOptionalBoolean('allowOverwrite') ?? true,\n };\n}\n\nfunction resolveInstance(\n instances: GrafanaInstanceConfig[],\n guard: ScaffolderGuardConfig,\n name?: string,\n): GrafanaInstanceConfig {\n const { allowedInstances } = guard;\n if (allowedInstances) {\n const known = new Set(instances.map(instance => instance.name));\n const unknown = allowedInstances.filter(allowed => !known.has(allowed));\n if (unknown.length > 0) {\n throw new InputError(\n `grafana.scaffolder.allowedInstances names unknown instance(s) '${unknown.join(\n \"', '\",\n )}'; configured instances are: ${[...known].join(', ')}`,\n );\n }\n }\n\n if (name) {\n const found = instances.find(instance => instance.name === name);\n if (!found) {\n throw new NotFoundError(\n `No Grafana instance configured with name '${name}'`,\n );\n }\n if (allowedInstances && !allowedInstances.includes(found.name)) {\n throw new NotAllowedError(\n `Grafana instance '${found.name}' is not writable by the scaffolder (grafana.scaffolder.allowedInstances)`,\n );\n }\n return found;\n }\n\n // Automatic selection only considers writable instances.\n const writable = allowedInstances\n ? instances.filter(instance => allowedInstances.includes(instance.name))\n : instances;\n if (writable.length === 0) {\n throw new InputError(\n 'No Grafana instances are configured and writable by the scaffolder',\n );\n }\n if (writable.length > 1) {\n throw new InputError(\n 'Multiple Grafana instances are configured; set input.instanceName to choose one',\n );\n }\n return writable[0];\n}\n\n/**\n * Creates the `grafana:dashboard:create` scaffolder action, which creates (or\n * updates) a dashboard in a configured Grafana instance via the App Platform\n * `dashboard.grafana.app/v1` API.\n *\n * @public\n */\nexport function createGrafanaDashboardCreateAction(options: {\n config: Config;\n fetch?: FetchApi;\n}) {\n const fetchApi = options.fetch ?? fetch;\n\n return createTemplateAction({\n id: 'grafana:dashboard:create',\n description:\n 'Creates or updates a Grafana dashboard via the App Platform API',\n schema: {\n input: {\n instanceName: z =>\n z\n .string({\n description:\n 'The configured Grafana instance to target. Optional when only one instance is configured.',\n })\n .optional(),\n title: z => z.string({ description: 'The dashboard title' }),\n uid: z =>\n z\n .string({\n description:\n 'The dashboard uid (metadata.name). When omitted, Grafana generates one.',\n })\n .optional(),\n folderUid: z =>\n z\n .string({ description: 'The uid of the folder to create it in' })\n .optional(),\n tags: z =>\n z.array(z.string(), { description: 'Dashboard tags' }).optional(),\n dashboard: z =>\n z\n .record(z.any(), {\n description:\n 'Additional dashboard spec fields (panels, templating, etc.), merged into the request spec.',\n })\n .optional(),\n overwrite: z =>\n z\n .boolean({\n description:\n 'Update the dashboard if it already exists (requires uid).',\n })\n .optional(),\n },\n output: {\n uid: z => z.string({ description: 'The uid of the dashboard' }),\n url: z => z.string({ description: 'The URL of the dashboard' }),\n instanceName: z =>\n z.string({\n description: 'The instance the dashboard was created in',\n }),\n },\n },\n async handler(ctx) {\n const { title, uid, folderUid, tags, dashboard, overwrite } = ctx.input;\n const guard = readGuardConfig(options.config);\n const instance = resolveInstance(\n readGrafanaInstances(options.config),\n guard,\n ctx.input.instanceName,\n );\n\n if (overwrite && !guard.allowOverwrite) {\n throw new NotAllowedError(\n 'Updating existing dashboards is disabled by configuration (grafana.scaffolder.allowOverwrite)',\n );\n }\n\n const isUpdate = Boolean(overwrite && uid);\n const collection = `/apis/dashboard.grafana.app/v1/namespaces/${instance.namespace}/dashboards`;\n const path = isUpdate ? `${collection}/${uid}` : collection;\n\n ctx.logger.info(\n `${\n isUpdate ? 'Updating' : 'Creating'\n } Grafana dashboard '${title}' in instance '${instance.name}'`,\n );\n\n const response = await fetchApi(`${instance.baseUrl}${path}`, {\n method: isUpdate ? 'PUT' : 'POST',\n headers: {\n Authorization: `Bearer ${instance.token}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n metadata: {\n ...(uid ? { name: uid } : {}),\n ...(folderUid\n ? { annotations: { 'grafana.app/folder': folderUid } }\n : {}),\n },\n spec: {\n title,\n schemaVersion: 41,\n tags: tags ?? [],\n panels: [],\n ...(dashboard ?? {}),\n },\n }),\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const body = (await response.json()) as { metadata?: { name?: string } };\n const resultUid = body.metadata?.name ?? uid ?? '';\n const url = `${instance.baseUrl}/d/${resultUid}/${slugify(title)}`;\n\n ctx.output('uid', resultUid);\n ctx.output('url', url);\n ctx.output('instanceName', instance.name);\n },\n });\n}\n"],"names":["InputError","NotFoundError","NotAllowedError","createTemplateAction","readGrafanaInstances","ResponseError","slugify"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var module$1 = require('./module.cjs.js');
6
+ var createDashboard = require('./actions/createDashboard.cjs.js');
7
+
8
+
9
+
10
+ exports.default = module$1.scaffolderModuleGrafana;
11
+ exports.createGrafanaDashboardCreateAction = createDashboard.createGrafanaDashboardCreateAction;
12
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -0,0 +1,37 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+ import * as _backstage_plugin_scaffolder_node from '@backstage/plugin-scaffolder-node';
3
+ import { Config } from '@backstage/config';
4
+ import { FetchApi } from '@marble-sh/backstage-plugin-grafana-node';
5
+
6
+ /**
7
+ * Scaffolder backend module that registers Grafana provisioning actions.
8
+ *
9
+ * @public
10
+ */
11
+ declare const scaffolderModuleGrafana: _backstage_backend_plugin_api.BackendFeature;
12
+
13
+ /**
14
+ * Creates the `grafana:dashboard:create` scaffolder action, which creates (or
15
+ * updates) a dashboard in a configured Grafana instance via the App Platform
16
+ * `dashboard.grafana.app/v1` API.
17
+ *
18
+ * @public
19
+ */
20
+ declare function createGrafanaDashboardCreateAction(options: {
21
+ config: Config;
22
+ fetch?: FetchApi;
23
+ }): _backstage_plugin_scaffolder_node.TemplateAction<{
24
+ title: string;
25
+ instanceName?: string | undefined;
26
+ uid?: string | undefined;
27
+ folderUid?: string | undefined;
28
+ tags?: string[] | undefined;
29
+ dashboard?: Record<string, any> | undefined;
30
+ overwrite?: boolean | undefined;
31
+ }, {
32
+ uid: string;
33
+ url: string;
34
+ instanceName: string;
35
+ }, "v2">;
36
+
37
+ export { createGrafanaDashboardCreateAction, scaffolderModuleGrafana as default };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var pluginScaffolderNode = require('@backstage/plugin-scaffolder-node');
5
+ var createDashboard = require('./actions/createDashboard.cjs.js');
6
+
7
+ const scaffolderModuleGrafana = backendPluginApi.createBackendModule({
8
+ moduleId: "grafana",
9
+ pluginId: "scaffolder",
10
+ register(env) {
11
+ env.registerInit({
12
+ deps: {
13
+ scaffolder: pluginScaffolderNode.scaffolderActionsExtensionPoint,
14
+ config: backendPluginApi.coreServices.rootConfig
15
+ },
16
+ async init({ scaffolder, config }) {
17
+ scaffolder.addActions(createDashboard.createGrafanaDashboardCreateAction({ config }));
18
+ }
19
+ });
20
+ }
21
+ });
22
+
23
+ exports.scaffolderModuleGrafana = scaffolderModuleGrafana;
24
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2026 Cassidy Marble\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node';\nimport { createGrafanaDashboardCreateAction } from './actions';\n\n/**\n * Scaffolder backend module that registers Grafana provisioning actions.\n *\n * @public\n */\nexport const scaffolderModuleGrafana = createBackendModule({\n moduleId: 'grafana',\n pluginId: 'scaffolder',\n register(env) {\n env.registerInit({\n deps: {\n scaffolder: scaffolderActionsExtensionPoint,\n config: coreServices.rootConfig,\n },\n async init({ scaffolder, config }) {\n scaffolder.addActions(createGrafanaDashboardCreateAction({ config }));\n },\n });\n },\n});\n"],"names":["createBackendModule","scaffolderActionsExtensionPoint","coreServices","createGrafanaDashboardCreateAction"],"mappings":";;;;;;AA4BO,MAAM,0BAA0BA,oCAAA,CAAoB;AAAA,EACzD,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,YAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,UAAA,EAAYC,oDAAA;AAAA,QACZ,QAAQC,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,UAAA,EAAY,QAAO,EAAG;AACjC,QAAA,UAAA,CAAW,UAAA,CAAWC,kDAAA,CAAmC,EAAE,MAAA,EAAQ,CAAC,CAAA;AAAA,MACtE;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@marble-sh/backstage-plugin-scaffolder-backend-module-grafana",
3
+ "version": "0.2.0",
4
+ "description": "Scaffolder backend module with actions for provisioning Grafana dashboards",
5
+ "main": "./dist/index.cjs.js",
6
+ "types": "./dist/index.d.ts",
7
+ "license": "Apache-2.0",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "backstage": {
12
+ "role": "backend-plugin-module",
13
+ "pluginId": "scaffolder",
14
+ "pluginPackage": "@backstage/plugin-scaffolder-backend",
15
+ "features": {
16
+ ".": "@backstage/BackendFeature"
17
+ }
18
+ },
19
+ "sideEffects": false,
20
+ "scripts": {
21
+ "start": "backstage-cli package start",
22
+ "build": "backstage-cli package build",
23
+ "clean": "backstage-cli package clean",
24
+ "lint": "backstage-cli package lint",
25
+ "prepack": "backstage-cli package prepack",
26
+ "postpack": "backstage-cli package postpack",
27
+ "test": "backstage-cli package test"
28
+ },
29
+ "dependencies": {
30
+ "@backstage/backend-plugin-api": "backstage:^",
31
+ "@backstage/config": "backstage:^",
32
+ "@backstage/errors": "backstage:^",
33
+ "@backstage/plugin-scaffolder-node": "backstage:^",
34
+ "@marble-sh/backstage-plugin-grafana-node": "workspace:^"
35
+ },
36
+ "devDependencies": {
37
+ "@backstage/backend-test-utils": "backstage:^",
38
+ "@backstage/cli": "backstage:^",
39
+ "@backstage/plugin-scaffolder-node-test-utils": "backstage:^",
40
+ "@backstage/types": "backstage:^"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "config.schema.json"
45
+ ],
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/marble-sh/backstage-plugins-grafana",
49
+ "directory": "plugins/scaffolder-backend-module-grafana"
50
+ },
51
+ "keywords": [
52
+ "backstage",
53
+ "plugin",
54
+ "grafana",
55
+ "scaffolder"
56
+ ],
57
+ "author": "Cassidy Marble",
58
+ "homepage": "https://github.com/marble-sh/backstage-plugins-grafana/tree/main/plugins/scaffolder-backend-module-grafana",
59
+ "bugs": "https://github.com/marble-sh/backstage-plugins-grafana/issues",
60
+ "exports": {
61
+ ".": {
62
+ "backstage": "@backstage/BackendFeature",
63
+ "require": "./dist/index.cjs.js",
64
+ "types": "./dist/index.d.ts",
65
+ "default": "./dist/index.cjs.js"
66
+ },
67
+ "./package.json": "./package.json"
68
+ },
69
+ "typesVersions": {
70
+ "*": {
71
+ "package.json": [
72
+ "package.json"
73
+ ]
74
+ }
75
+ },
76
+ "configSchema": "config.schema.json"
77
+ }