@backstage/plugin-scaffolder-backend 1.10.0-next.1 → 1.10.0-next.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 1.10.0-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 2fadff2a25: Change scaffolder task actions to include markdown to demonstrate the new `ActionsPage` markdown feature.
8
+ - b44eb68bcb: This patch adds changes to provide examples alongside scaffolder task actions.
9
+
10
+ The `createTemplateAction` function now takes a list of examples e.g.
11
+
12
+ ```typescript
13
+ const actionExamples = [
14
+ {
15
+ description: 'Example 1',
16
+ example: yaml.stringify({
17
+ steps: [
18
+ {
19
+ action: 'test:action',
20
+ id: 'test',
21
+ input: {
22
+ input1: 'value',
23
+ },
24
+ },
25
+ ],
26
+ }),
27
+ },
28
+ ];
29
+
30
+ export function createTestAction() {
31
+ return createTemplateAction({
32
+ id: 'test:action',
33
+ examples: [
34
+ {
35
+ description: 'Example 1',
36
+ examples: actionExamples
37
+ }
38
+ ],
39
+ ...,
40
+ });
41
+ ```
42
+
43
+ These examples can be retrieved later from the api.
44
+
45
+ ```bash
46
+ curl http://localhost:7007/api/scaffolder/v2/actions
47
+ ```
48
+
49
+ ```json
50
+ [
51
+ {
52
+ "id": "test:action",
53
+ "examples": [
54
+ {
55
+ "description": "Example 1",
56
+ "example": "steps:\n - action: test:action\n id: test\n input:\n input1: value\n"
57
+ }
58
+ ],
59
+ "schema": {
60
+ "input": {
61
+ "type": "object",
62
+ "properties": {
63
+ "input1": {
64
+ "title": "Input 1",
65
+ "type": "string"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ]
72
+ ```
73
+
74
+ - 8e06f3cf00: Switched imports of `loggerToWinstonLogger` to `@backstage/backend-common`.
75
+ - Updated dependencies
76
+ - @backstage/backend-plugin-api@0.3.0-next.1
77
+ - @backstage/backend-common@0.18.0-next.1
78
+ - @backstage/backend-tasks@0.4.1-next.1
79
+ - @backstage/catalog-client@1.3.0-next.2
80
+ - @backstage/plugin-catalog-backend@1.7.0-next.2
81
+ - @backstage/plugin-catalog-node@1.3.1-next.2
82
+ - @backstage/plugin-auth-node@0.2.9-next.1
83
+ - @backstage/catalog-model@1.1.5-next.1
84
+ - @backstage/config@1.0.6-next.0
85
+ - @backstage/errors@1.1.4
86
+ - @backstage/integration@1.4.2-next.0
87
+ - @backstage/types@1.0.2
88
+ - @backstage/plugin-scaffolder-common@1.2.4-next.1
89
+
3
90
  ## 1.10.0-next.1
4
91
 
5
92
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-scaffolder-backend",
3
- "version": "1.10.0-next.1",
3
+ "version": "1.10.0-next.2",
4
4
  "main": "../dist/index.cjs.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
@@ -1063,6 +1063,10 @@ export declare class TaskWorker {
1063
1063
  export declare type TemplateAction<Input extends JsonObject> = {
1064
1064
  id: string;
1065
1065
  description?: string;
1066
+ examples?: {
1067
+ description: string;
1068
+ example: string;
1069
+ }[];
1066
1070
  supportsDryRun?: boolean;
1067
1071
  schema?: {
1068
1072
  input?: Schema;
@@ -1045,6 +1045,10 @@ export declare class TaskWorker {
1045
1045
  export declare type TemplateAction<Input extends JsonObject> = {
1046
1046
  id: string;
1047
1047
  description?: string;
1048
+ examples?: {
1049
+ description: string;
1050
+ example: string;
1051
+ }[];
1048
1052
  supportsDryRun?: boolean;
1049
1053
  schema?: {
1050
1054
  input?: Schema;
package/dist/index.cjs.js CHANGED
@@ -4,8 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var errors = require('@backstage/errors');
6
6
  var catalogModel = require('@backstage/catalog-model');
7
- var fs = require('fs-extra');
8
7
  var yaml = require('yaml');
8
+ var fs = require('fs-extra');
9
9
  var backendCommon = require('@backstage/backend-common');
10
10
  var integration = require('@backstage/integration');
11
11
  var path = require('path');
@@ -62,8 +62,9 @@ function _interopNamespace(e) {
62
62
  return Object.freeze(n);
63
63
  }
64
64
 
65
- var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
65
+ var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
66
66
  var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
67
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
67
68
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
68
69
  var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
69
70
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
@@ -81,11 +82,30 @@ const createTemplateAction = (templateAction) => {
81
82
  return templateAction;
82
83
  };
83
84
 
85
+ const id$2 = "catalog:register";
86
+ const examples$2 = [
87
+ {
88
+ description: "Register with the catalog",
89
+ example: yaml__default["default"].stringify({
90
+ steps: [
91
+ {
92
+ action: id$2,
93
+ id: "register-with-catalog",
94
+ name: "Register with the catalog",
95
+ input: {
96
+ catalogInfoUrl: "http://github.com/backstage/backstage/blob/master/catalog-info.yaml"
97
+ }
98
+ }
99
+ ]
100
+ })
101
+ }
102
+ ];
84
103
  function createCatalogRegisterAction(options) {
85
104
  const { catalogClient, integrations } = options;
86
105
  return createTemplateAction({
87
- id: "catalog:register",
106
+ id: id$2,
88
107
  description: "Registers entities from a catalog descriptor file in the workspace into the software catalog.",
108
+ examples: examples$2,
89
109
  schema: {
90
110
  input: {
91
111
  oneOf: [
@@ -203,10 +223,41 @@ function createCatalogRegisterAction(options) {
203
223
  });
204
224
  }
205
225
 
226
+ const id$1 = "catalog:write";
227
+ const examples$1 = [
228
+ {
229
+ description: "Write a catalog yaml file",
230
+ example: yaml__namespace.stringify({
231
+ steps: [
232
+ {
233
+ action: id$1,
234
+ id: "create-catalog-info-file",
235
+ name: "Create catalog file",
236
+ input: {
237
+ entity: {
238
+ apiVersion: "backstage.io/v1alpha1",
239
+ kind: "Component",
240
+ metadata: {
241
+ name: "test",
242
+ annotations: {}
243
+ },
244
+ spec: {
245
+ type: "service",
246
+ lifecycle: "production",
247
+ owner: "default/owner"
248
+ }
249
+ }
250
+ }
251
+ }
252
+ ]
253
+ })
254
+ }
255
+ ];
206
256
  function createCatalogWriteAction() {
207
257
  return createTemplateAction({
208
- id: "catalog:write",
258
+ id: id$1,
209
259
  description: "Writes the catalog-info.yaml for your template",
260
+ examples: examples$1,
210
261
  schema: {
211
262
  input: {
212
263
  type: "object",
@@ -237,10 +288,44 @@ function createCatalogWriteAction() {
237
288
  });
238
289
  }
239
290
 
291
+ const id = "debug:log";
292
+ const examples = [
293
+ {
294
+ description: "Write a debug message",
295
+ example: yaml__default["default"].stringify({
296
+ steps: [
297
+ {
298
+ action: id,
299
+ id: "write-debug-line",
300
+ name: 'Write "Hello Backstage!" log line',
301
+ input: {
302
+ message: "Hello Backstage!"
303
+ }
304
+ }
305
+ ]
306
+ })
307
+ },
308
+ {
309
+ description: "List the workspace directory",
310
+ example: yaml__default["default"].stringify({
311
+ steps: [
312
+ {
313
+ action: id,
314
+ id: "write-workspace-directory",
315
+ name: "List the workspace directory",
316
+ input: {
317
+ listWorkspace: true
318
+ }
319
+ }
320
+ ]
321
+ })
322
+ }
323
+ ];
240
324
  function createDebugLogAction() {
241
325
  return createTemplateAction({
242
- id: "debug:log",
326
+ id,
243
327
  description: "Writes a message into the log or lists all files in the workspace.",
328
+ examples,
244
329
  schema: {
245
330
  input: {
246
331
  type: "object",
@@ -327,7 +412,7 @@ function createFetchPlainAction(options) {
327
412
  const { reader, integrations } = options;
328
413
  return createTemplateAction({
329
414
  id: "fetch:plain",
330
- description: "Downloads content and places it in the workspace, or optionally in a subdirectory specified by the 'targetPath' input option.",
415
+ description: "Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.",
331
416
  schema: {
332
417
  input: {
333
418
  type: "object",
@@ -512,7 +597,7 @@ function createFetchTemplateAction(options) {
512
597
  } = options;
513
598
  return createTemplateAction({
514
599
  id: "fetch:template",
515
- description: "Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the 'targetPath' input option.",
600
+ description: "Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.",
516
601
  schema: {
517
602
  input: {
518
603
  type: "object",
@@ -1007,6 +1092,13 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
1007
1092
  try {
1008
1093
  await client.rest.repos.updateBranchProtection({
1009
1094
  mediaType: {
1095
+ /**
1096
+ * 👇 we need this preview because allowing a custom
1097
+ * reviewer count on branch protection is a preview
1098
+ * feature
1099
+ *
1100
+ * More here: https://docs.github.com/en/rest/overview/api-previews#require-multiple-approving-reviews
1101
+ */
1010
1102
  previews: ["luke-cage-preview"]
1011
1103
  },
1012
1104
  owner,
@@ -1056,6 +1148,7 @@ async function getOctokitOptions(options) {
1056
1148
  const { integrations, credentialsProvider, repoUrl, token } = options;
1057
1149
  const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
1058
1150
  const requestOptions = {
1151
+ // set timeout to 60 seconds
1059
1152
  timeout: DEFAULT_TIMEOUT_MS
1060
1153
  };
1061
1154
  if (!owner) {
@@ -3155,6 +3248,8 @@ async function serializeDirectoryContents(sourcePath, options) {
3155
3248
  dot: true,
3156
3249
  gitignore: options == null ? void 0 : options.gitignore,
3157
3250
  followSymbolicLinks: false,
3251
+ // In order to pick up 'broken' symlinks, we oxymoronically request files AND folders yet we filter out folders
3252
+ // This is because broken symlinks aren't classed as files so we need to glob everything
3158
3253
  onlyFiles: false,
3159
3254
  objectMode: true,
3160
3255
  stats: true
@@ -3352,7 +3447,16 @@ const createPublishGithubPullRequestAction = ({
3352
3447
  directoryContents.map((file) => [
3353
3448
  targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
3354
3449
  {
3450
+ // See the properties of tree items
3451
+ // in https://docs.github.com/en/rest/reference/git#trees
3355
3452
  mode: determineFileMode(file),
3453
+ // Always use base64 encoding where possible to avoid doubling a binary file in size
3454
+ // due to interpreting a binary file as utf-8 and sending github
3455
+ // the utf-8 encoded content. Symlinks are kept as utf-8 to avoid them
3456
+ // being formatted as a series of scrambled characters
3457
+ //
3458
+ // For example, the original gradle-wrapper.jar is 57.8k in https://github.com/kennethzfeng/pull-request-test/pull/5/files.
3459
+ // Its size could be doubled to 98.3K (See https://github.com/kennethzfeng/pull-request-test/pull/4/files)
3356
3460
  encoding: determineFileEncoding(file),
3357
3461
  content: file.content.toString(determineFileEncoding(file))
3358
3462
  }
@@ -3589,6 +3693,7 @@ const createPublishGitlabMergeRequestAction = (options) => {
3589
3693
  title: "Repository Location",
3590
3694
  description: `Accepts the format 'gitlab.com/group_name/project_name' where 'project_name' is the repository name and 'group_name' is a group or username`
3591
3695
  },
3696
+ /** @deprecated projectID is passed as query parameters in the repoUrl */
3592
3697
  projectid: {
3593
3698
  type: "string",
3594
3699
  title: "projectid",
@@ -3990,6 +4095,7 @@ class DatabaseTaskStore {
3990
4095
  const updateCount = await tx("tasks").where({ id: task.id, status: "open" }).update({
3991
4096
  status: "processing",
3992
4097
  last_heartbeat_at: this.db.fn.now(),
4098
+ // remove the secrets when moving moving to processing state.
3993
4099
  secrets: null
3994
4100
  });
3995
4101
  if (updateCount < 1) {
@@ -4140,6 +4246,7 @@ class DatabaseTaskStore {
4140
4246
  }
4141
4247
 
4142
4248
  class TaskManager {
4249
+ // Runs heartbeat internally
4143
4250
  constructor(task, storage, logger) {
4144
4251
  this.task = task;
4145
4252
  this.storage = storage;
@@ -4223,6 +4330,9 @@ class StorageTaskBroker {
4223
4330
  }
4224
4331
  return await this.storage.list({ createdBy: options == null ? void 0 : options.createdBy });
4225
4332
  }
4333
+ /**
4334
+ * {@inheritdoc TaskBroker.claim}
4335
+ */
4226
4336
  async claim() {
4227
4337
  for (; ; ) {
4228
4338
  const pendingTask = await this.storage.claimTask();
@@ -4241,6 +4351,9 @@ class StorageTaskBroker {
4241
4351
  await this.waitForDispatch();
4242
4352
  }
4243
4353
  }
4354
+ /**
4355
+ * {@inheritdoc TaskBroker.dispatch}
4356
+ */
4244
4357
  async dispatch(options) {
4245
4358
  const taskRow = await this.storage.createTask(options);
4246
4359
  this.signalDispatch();
@@ -4248,9 +4361,15 @@ class StorageTaskBroker {
4248
4361
  taskId: taskRow.taskId
4249
4362
  };
4250
4363
  }
4364
+ /**
4365
+ * {@inheritdoc TaskBroker.get}
4366
+ */
4251
4367
  async get(taskId) {
4252
4368
  return this.storage.getTask(taskId);
4253
4369
  }
4370
+ /**
4371
+ * {@inheritdoc TaskBroker.event$}
4372
+ */
4254
4373
  event$(options) {
4255
4374
  return new ObservableImpl__default["default"]((observer) => {
4256
4375
  const { taskId } = options;
@@ -4272,6 +4391,9 @@ class StorageTaskBroker {
4272
4391
  };
4273
4392
  });
4274
4393
  }
4394
+ /**
4395
+ * {@inheritdoc TaskBroker.vacuumTasks}
4396
+ */
4275
4397
  async vacuumTasks(options) {
4276
4398
  const { tasks } = await this.storage.listStaleTasks(options);
4277
4399
  await Promise.all(
@@ -4441,6 +4563,10 @@ class NunjucksWorkflowRunner {
4441
4563
  );
4442
4564
  const { integrations } = this.options;
4443
4565
  const renderTemplate = await SecureTemplater.loadRenderer({
4566
+ // TODO(blam): let's work out how we can deprecate this.
4567
+ // We shouldn't really need to be exposing these now we can deal with
4568
+ // objects in the params block.
4569
+ // Maybe we can expose a new RepoUrlPicker with secrets for V3 that provides an object already.
4444
4570
  parseRepoUrl(url) {
4445
4571
  return parseRepoUrl(url, integrations);
4446
4572
  },
@@ -4693,6 +4819,7 @@ class TaskWorker {
4693
4819
  workingDirectory,
4694
4820
  additionalTemplateFilters,
4695
4821
  concurrentTasksLimit = 10,
4822
+ // from 1 to Infinity
4696
4823
  additionalTemplateGlobals
4697
4824
  } = options;
4698
4825
  const workflowRunner = new NunjucksWorkflowRunner({
@@ -4809,6 +4936,7 @@ function createDryRunner(options) {
4809
4936
  }
4810
4937
  },
4811
4938
  secrets: input.secrets,
4939
+ // No need to update this at the end of the run, so just hard-code it
4812
4940
  done: false,
4813
4941
  isDryRun: true,
4814
4942
  getWorkspaceName: async () => `dry-run-${dryRunId}`,
@@ -4963,6 +5091,7 @@ async function createRouter(options) {
4963
5091
  await scheduler.scheduleTask({
4964
5092
  id: "close_stale_tasks",
4965
5093
  frequency: { cron: "*/5 * * * *" },
5094
+ // every 5 minutes, also supports Duration
4966
5095
  timeout: { minutes: 15 },
4967
5096
  fn: async () => {
4968
5097
  const { tasks } = await databaseTaskStore.listStaleTasks({
@@ -5051,6 +5180,7 @@ async function createRouter(options) {
5051
5180
  return {
5052
5181
  id: action.id,
5053
5182
  description: action.description,
5183
+ examples: action.examples,
5054
5184
  schema: action.schema
5055
5185
  };
5056
5186
  });
@@ -5409,7 +5539,7 @@ const scaffolderPlugin = backendPluginApi.createBackendPlugin({
5409
5539
  taskWorkers,
5410
5540
  additionalTemplateGlobals
5411
5541
  } = options;
5412
- const log = backendPluginApi.loggerToWinstonLogger(logger);
5542
+ const log = backendCommon.loggerToWinstonLogger(logger);
5413
5543
  const actions = options.actions || [
5414
5544
  ...actionsExtensions.actions,
5415
5545
  ...createBuiltinActions({