@eclipse-che/che-devworkspace-generator 0.0.1-d7aefd7 → 0.0.1-ea73e8b

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.
Files changed (41) hide show
  1. package/README.md +56 -0
  2. package/lib/api/devfile-context.d.ts +19 -0
  3. package/lib/devfile/dev-container-component-finder.d.ts +18 -0
  4. package/lib/devfile/dev-container-component-finder.js +20 -7
  5. package/lib/devfile/dev-container-component-finder.js.map +1 -1
  6. package/lib/devfile/dev-container-component-inserter.d.ts +18 -0
  7. package/lib/devfile/dev-container-component-inserter.js +98 -0
  8. package/lib/devfile/dev-container-component-inserter.js.map +1 -0
  9. package/lib/devfile/devfile-module.d.ts +12 -0
  10. package/lib/devfile/devfile-module.js +2 -0
  11. package/lib/devfile/devfile-module.js.map +1 -1
  12. package/lib/entrypoint.d.ts +11 -0
  13. package/lib/fetch/fetch-module.d.ts +12 -0
  14. package/lib/fetch/url-fetcher.d.ts +9 -0
  15. package/lib/generate.d.ts +16 -0
  16. package/lib/generate.js +32 -21
  17. package/lib/generate.js.map +1 -1
  18. package/lib/github/github-module.d.ts +12 -0
  19. package/lib/github/github-resolver.d.ts +18 -0
  20. package/lib/github/github-resolver.js +4 -2
  21. package/lib/github/github-resolver.js.map +1 -1
  22. package/lib/github/github-url.d.ts +29 -0
  23. package/lib/github/github-url.js +7 -6
  24. package/lib/github/github-url.js.map +1 -1
  25. package/lib/inversify/inversify-binding.d.ts +26 -0
  26. package/lib/main.d.ts +38 -0
  27. package/lib/main.js +100 -60
  28. package/lib/main.js.map +1 -1
  29. package/lib/plugin-registry/plugin-registry-module.d.ts +12 -0
  30. package/lib/plugin-registry/plugin-registry-resolver.d.ts +17 -0
  31. package/lib/plugin-registry/plugin-registry-resolver.js +1 -1
  32. package/lib/plugin-registry/plugin-registry-resolver.js.map +1 -1
  33. package/package.json +6 -4
  34. package/src/devfile/dev-container-component-finder.ts +23 -4
  35. package/src/devfile/dev-container-component-inserter.ts +49 -0
  36. package/src/devfile/devfile-module.ts +2 -0
  37. package/src/generate.ts +59 -25
  38. package/src/github/github-resolver.ts +4 -2
  39. package/src/github/github-url.ts +6 -6
  40. package/src/main.ts +119 -55
  41. package/src/plugin-registry/plugin-registry-resolver.ts +1 -1
package/src/generate.ts CHANGED
@@ -9,9 +9,11 @@
9
9
  ***********************************************************************/
10
10
 
11
11
  import {
12
+ V221Devfile,
13
+ V221DevfileMetadata,
12
14
  V1alpha2DevWorkspace,
15
+ V1alpha2DevWorkspaceMetadata,
13
16
  V1alpha2DevWorkspaceSpecContributions,
14
- V1alpha2DevWorkspaceSpecTemplateComponents,
15
17
  V1alpha2DevWorkspaceTemplate,
16
18
  V1alpha2DevWorkspaceTemplateSpec,
17
19
  } from '@devfile/api';
@@ -21,30 +23,52 @@ import * as fs from 'fs-extra';
21
23
  import { DevfileContext } from './api/devfile-context';
22
24
  import { DevContainerComponentFinder } from './devfile/dev-container-component-finder';
23
25
 
26
+ type DevfileLike = V221Devfile & {
27
+ metadata: V221DevfileMetadata & {
28
+ generateName?: string;
29
+ };
30
+ };
31
+
24
32
  @injectable()
25
33
  export class Generate {
26
- static readonly MERGE_CONTRIBUTION = 'controller.devfile.io/merge-contribution';
27
-
28
34
  @inject(DevContainerComponentFinder)
29
35
  private devContainerComponentFinder: DevContainerComponentFinder;
30
36
 
31
- async generate(devfileContent: string, editorContent: string, outputFile: string): Promise<DevfileContext> {
32
- const context = await this.generateContent(devfileContent, editorContent);
37
+ async generate(
38
+ devfileContent: string,
39
+ editorContent: string,
40
+ outputFile?: string,
41
+ injectDefaultComponent?: string,
42
+ defaultComponentImage?: string
43
+ ): Promise<DevfileContext> {
44
+ const context = await this.generateContent(
45
+ devfileContent,
46
+ editorContent,
47
+ injectDefaultComponent,
48
+ defaultComponentImage
49
+ );
33
50
 
34
51
  // write the result
35
- // write templates and then DevWorkspace in a single file
36
- const allContentArray = context.devWorkspaceTemplates.map(template => jsYaml.dump(template));
37
- allContentArray.push(jsYaml.dump(context.devWorkspace));
52
+ if (outputFile) {
53
+ // write templates and then DevWorkspace in a single file
54
+ const allContentArray = context.devWorkspaceTemplates.map(template => jsYaml.dump(template));
55
+ allContentArray.push(jsYaml.dump(context.devWorkspace));
38
56
 
39
- const generatedContent = allContentArray.join('---\n');
57
+ const generatedContent = allContentArray.join('---\n');
40
58
 
41
- await fs.writeFile(outputFile, generatedContent, 'utf-8');
59
+ await fs.writeFile(outputFile, generatedContent, 'utf-8');
60
+ }
42
61
 
43
62
  console.log(`DevWorkspace ${context.devWorkspaceTemplates[0].metadata.name} was generated.`);
44
63
  return context;
45
64
  }
46
65
 
47
- async generateContent(devfileContent: string, editorContent: string): Promise<DevfileContext> {
66
+ async generateContent(
67
+ devfileContent: string,
68
+ editorContent: string,
69
+ injectDefaultComponent?: string,
70
+ defaultComponentImage?: string
71
+ ): Promise<DevfileContext> {
48
72
  const devfile = jsYaml.load(devfileContent);
49
73
 
50
74
  // const originalDevfile = Object.assign({}, devfile);
@@ -55,7 +79,7 @@ export class Generate {
55
79
  const editorDevfile = jsYaml.load(editorContent);
56
80
 
57
81
  // transform it into a devWorkspace template
58
- const metadata = editorDevfile.metadata;
82
+ const metadata = this.createDevWorkspaceMetadata(editorDevfile);
59
83
  // add sufix
60
84
  metadata.name = `${metadata.name}-${suffix}`;
61
85
  delete editorDevfile.metadata;
@@ -68,7 +92,7 @@ export class Generate {
68
92
  };
69
93
 
70
94
  // transform it into a devWorkspace
71
- const devfileMetadata = devfile.metadata;
95
+ const devfileMetadata = this.createDevWorkspaceMetadata(devfile, true);
72
96
  const devfileCopy = Object.assign({}, devfile);
73
97
  delete devfileCopy.schemaVersion;
74
98
  delete devfileCopy.metadata;
@@ -84,6 +108,7 @@ export class Generate {
84
108
  metadata: devfileMetadata,
85
109
  spec: {
86
110
  started: true,
111
+ routingClass: 'che',
87
112
  template: devfileCopy,
88
113
  contributions: [editorSpecContribution],
89
114
  },
@@ -99,19 +124,28 @@ export class Generate {
99
124
  suffix,
100
125
  };
101
126
 
102
- // grab container where to inject controller.devfile.io/merge-contribution attribute
103
- let devContainer: V1alpha2DevWorkspaceSpecTemplateComponents = await this.devContainerComponentFinder.find(context);
104
-
105
- // add attributes
106
- let devContainerAttributes = devContainer.attributes;
107
- if (!devContainerAttributes) {
108
- devContainerAttributes = {};
109
- devContainerAttributes[Generate.MERGE_CONTRIBUTION] = true;
110
- devContainer.attributes = devContainerAttributes;
111
- } else {
112
- devContainerAttributes[Generate.MERGE_CONTRIBUTION] = true;
113
- }
127
+ // find devContainer component, add a default one if not found
128
+ await this.devContainerComponentFinder.find(context, injectDefaultComponent, defaultComponentImage);
114
129
 
115
130
  return context;
116
131
  }
132
+
133
+ private createDevWorkspaceMetadata(devfile: DevfileLike, addDevfileContent = false): V1alpha2DevWorkspaceMetadata {
134
+ const devWorkspaceMetadata = {} as V1alpha2DevWorkspaceMetadata;
135
+ const devfileMetadata = devfile.metadata;
136
+
137
+ if (devfileMetadata.name) {
138
+ devWorkspaceMetadata.name = devfileMetadata.name;
139
+ }
140
+ if (devfileMetadata.generateName) {
141
+ devWorkspaceMetadata.generateName = devfileMetadata.generateName;
142
+ }
143
+ if (addDevfileContent) {
144
+ devWorkspaceMetadata.annotations = {
145
+ 'che.eclipse.org/devfile': jsYaml.dump(devfile),
146
+ };
147
+ }
148
+
149
+ return devWorkspaceMetadata;
150
+ }
117
151
  }
@@ -18,18 +18,20 @@ import { injectable } from 'inversify';
18
18
  export class GithubResolver {
19
19
  // eslint-disable-next-line max-len
20
20
  static readonly GITHUB_URL_PATTERN =
21
- /^(?:http)(?:s)?(?:\:\/\/)github\.com\/(?<repoUser>[^\/]+)\/(?<repoName>[^\/]+)((\/)|(?:\/(blob|tree)\/(?<branchName>[^\/]+)(?:\/(?<subFolder>.*))?))?$/;
21
+ /^(?<scheme>https?):\/\/(?<host>github(\..+)?\.[^\/]+)\/(?<repoUser>[^\/]+)\/(?<repoName>[^\/]+)((\/)|\/(blob|tree)\/(?<branchName>[^\/]+)(?:\/(?<subFolder>.*))?)?$/;
22
22
 
23
23
  resolve(link: string): GithubUrl {
24
24
  const match = GithubResolver.GITHUB_URL_PATTERN.exec(link);
25
25
  if (!match) {
26
26
  throw new Error(`Invalid github URL: ${link}`);
27
27
  }
28
+ const scheme = this.getGroup(match, 'scheme');
29
+ const hostName = this.getGroup(match, 'host');
28
30
  const repoUser = this.getGroup(match, 'repoUser');
29
31
  const repoName = this.getGroup(match, 'repoName');
30
32
  const branchName = this.getGroup(match, 'branchName', 'HEAD');
31
33
  const subFolder = this.getGroup(match, 'subFolder');
32
- return new GithubUrl(repoUser, repoName, branchName, subFolder);
34
+ return new GithubUrl(scheme, hostName, repoUser, repoName, branchName, subFolder);
33
35
  }
34
36
 
35
37
  getGroup(match: RegExpExecArray, groupName: string, defaultValue?: string) {
@@ -12,10 +12,9 @@
12
12
  * Provides helper methods on top of github URL to get for example raw content of get relative links
13
13
  */
14
14
  export class GithubUrl {
15
- // raw link
16
- static readonly RAW_LINK = 'https://raw.githubusercontent.com';
17
-
18
15
  constructor(
16
+ private readonly scheme: string,
17
+ private readonly hostName: string,
19
18
  private readonly repoUser: string,
20
19
  private readonly repoName: string,
21
20
  private readonly branchName: string,
@@ -26,15 +25,16 @@ export class GithubUrl {
26
25
  * Provides the raw link to the given path based on the current repository information
27
26
  */
28
27
  getContentUrl(path: string): string {
29
- return `${GithubUrl.RAW_LINK}/${this.repoUser}/${this.repoName}/${this.branchName}/${path}`;
28
+ const hostName = this.hostName === 'github.com' ? 'githubusercontent.com' : this.hostName;
29
+ return `${this.scheme}://raw.${hostName}/${this.repoUser}/${this.repoName}/${this.branchName}/${path}`;
30
30
  }
31
31
 
32
32
  getUrl(): string {
33
- return `https://github.com/${this.repoUser}/${this.repoName}/tree/${this.branchName}/${this.subFolder}`;
33
+ return `${this.scheme}://${this.hostName}/${this.repoUser}/${this.repoName}/tree/${this.branchName}/${this.subFolder}`;
34
34
  }
35
35
 
36
36
  getCloneUrl(): string {
37
- return `https://github.com/${this.repoUser}/${this.repoName}.git`;
37
+ return `${this.scheme}://${this.hostName}/${this.repoUser}/${this.repoName}.git`;
38
38
  }
39
39
 
40
40
  getRepoName(): string {
package/src/main.ts CHANGED
@@ -20,58 +20,45 @@ import { V1alpha2DevWorkspaceSpecTemplate } from '@devfile/api';
20
20
  import { DevfileContext } from './api/devfile-context';
21
21
 
22
22
  export class Main {
23
- protected async doStart(): Promise<DevfileContext> {
24
- let devfilePath: string | undefined;
25
- let devfileUrl: string | undefined;
26
- let outputFile: string | undefined;
27
- let editorPath: string | undefined;
28
- let pluginRegistryUrl: string | undefined;
29
- let editorEntry: string | undefined;
30
- const projects: { name: string; location: string }[] = [];
23
+ /**
24
+ * Default constructor.
25
+ */
26
+ constructor() {
27
+ // no-op
28
+ }
29
+ // Generates a devfile context object based on params
30
+ public async generateDevfileContext(
31
+ params: {
32
+ devfilePath?: string;
33
+ devfileUrl?: string;
34
+ devfileContent?: string;
35
+ outputFile?: string;
36
+ editorPath?: string;
37
+ editorContent?: string;
38
+ editorEntry?: string;
39
+ pluginRegistryUrl?: string;
40
+ projects: { name: string; location: string }[];
41
+ injectDefaultComponent?: string;
42
+ defaultComponentImage?: string;
43
+ },
44
+ axiosInstance: axios.AxiosInstance
45
+ ): Promise<DevfileContext> {
46
+ if (!params.editorPath && !params.editorEntry && !params.editorContent) {
47
+ throw new Error('missing editorPath or editorEntry or editorContent');
48
+ }
49
+ if (!params.devfilePath && !params.devfileUrl && !params.devfileContent) {
50
+ throw new Error('missing devfilePath or devfileUrl or devfileContent');
51
+ }
31
52
 
32
- const args = process.argv.slice(2);
33
- args.forEach(arg => {
34
- if (arg.startsWith('--devfile-path:')) {
35
- devfilePath = arg.substring('--devfile-path:'.length);
36
- }
37
- if (arg.startsWith('--devfile-url:')) {
38
- devfileUrl = arg.substring('--devfile-url:'.length);
39
- }
40
- if (arg.startsWith('--plugin-registry-url:')) {
41
- pluginRegistryUrl = arg.substring('--plugin-registry-url:'.length);
42
- }
43
- if (arg.startsWith('--editor-entry:')) {
44
- editorEntry = arg.substring('--editor-entry:'.length);
45
- }
46
- if (arg.startsWith('--editor-path:')) {
47
- editorPath = arg.substring('--editor-path:'.length);
48
- }
49
- if (arg.startsWith('--output-file:')) {
50
- outputFile = arg.substring('--output-file:'.length);
51
- }
52
- if (arg.startsWith('--project.')) {
53
- const name = arg.substring('--project.'.length, arg.indexOf('='));
54
- let location = arg.substring(arg.indexOf('=') + 1);
55
- location = location.replace('{{_INTERNAL_URL_}}', '{{ INTERNAL_URL }}');
53
+ let pluginRegistryUrl: string;
56
54
 
57
- projects.push({ name, location });
58
- }
59
- });
60
- if (!editorPath && !editorEntry) {
61
- throw new Error('missing --editor-path: or --editor-entry: parameter');
62
- }
63
- if (editorEntry && !pluginRegistryUrl) {
55
+ if (params.pluginRegistryUrl) {
56
+ pluginRegistryUrl = params.pluginRegistryUrl;
57
+ } else {
64
58
  pluginRegistryUrl = 'https://eclipse-che.github.io/che-plugin-registry/main/v3';
65
59
  console.log(`No plug-in registry url. Setting to ${pluginRegistryUrl}`);
66
60
  }
67
- if (!devfilePath && !devfileUrl) {
68
- throw new Error('missing --devfile-path: or --devfile-url: parameter');
69
- }
70
- if (!outputFile) {
71
- throw new Error('missing --output-file: parameter');
72
- }
73
61
 
74
- const axiosInstance = axios.default;
75
62
  const inversifyBinbding = new InversifyBinding();
76
63
  const container = await inversifyBinbding.initBindings({
77
64
  pluginRegistryUrl,
@@ -83,9 +70,9 @@ export class Main {
83
70
  let editorContent;
84
71
 
85
72
  // gets the github URL
86
- if (devfileUrl) {
73
+ if (params.devfileUrl) {
87
74
  const githubResolver = container.get(GithubResolver);
88
- const githubUrl = githubResolver.resolve(devfileUrl);
75
+ const githubUrl = githubResolver.resolve(params.devfileUrl);
89
76
  // user devfile
90
77
  devfileContent = await container.get(UrlFetcher).fetchText(githubUrl.getContentUrl('devfile.yaml'));
91
78
 
@@ -107,23 +94,33 @@ export class Main {
107
94
  }
108
95
  // get back the content
109
96
  devfileContent = jsYaml.dump(devfileParsed);
97
+ } else if (params.devfilePath) {
98
+ devfileContent = await fs.readFile(params.devfilePath);
110
99
  } else {
111
- devfileContent = await fs.readFile(devfilePath);
100
+ devfileContent = params.devfileContent;
112
101
  }
113
102
 
114
103
  // enhance projects
115
- devfileContent = this.replaceIfExistingProjects(devfileContent, projects);
104
+ devfileContent = this.replaceIfExistingProjects(devfileContent, params.projects);
116
105
 
117
- if (editorEntry) {
106
+ if (params.editorContent) {
107
+ editorContent = params.editorContent;
108
+ } else if (params.editorEntry) {
118
109
  // devfile of the editor
119
- const editorDevfile = await container.get(PluginRegistryResolver).loadDevfilePlugin(editorEntry);
110
+ const editorDevfile = await container.get(PluginRegistryResolver).loadDevfilePlugin(params.editorEntry);
120
111
  editorContent = jsYaml.dump(editorDevfile);
121
112
  } else {
122
- editorContent = await fs.readFile(editorPath);
113
+ editorContent = await fs.readFile(params.editorPath);
123
114
  }
124
115
 
125
116
  const generate = container.get(Generate);
126
- return generate.generate(devfileContent, editorContent, outputFile);
117
+ return generate.generate(
118
+ devfileContent,
119
+ editorContent,
120
+ params.outputFile,
121
+ params.injectDefaultComponent,
122
+ params.defaultComponentImage
123
+ );
127
124
  }
128
125
 
129
126
  // Update project entry based on the projects passed as parameter
@@ -154,8 +151,75 @@ export class Main {
154
151
  }
155
152
 
156
153
  async start(): Promise<boolean> {
154
+ let devfilePath: string | undefined;
155
+ let devfileUrl: string | undefined;
156
+ let outputFile: string | undefined;
157
+ let editorPath: string | undefined;
158
+ let pluginRegistryUrl: string | undefined;
159
+ let editorEntry: string | undefined;
160
+ let injectDefaultComponent: string | undefined;
161
+ let defaultComponentImage: string | undefined;
162
+ const projects: { name: string; location: string }[] = [];
163
+
164
+ const args = process.argv.slice(2);
165
+ args.forEach(arg => {
166
+ if (arg.startsWith('--devfile-path:')) {
167
+ devfilePath = arg.substring('--devfile-path:'.length);
168
+ }
169
+ if (arg.startsWith('--devfile-url:')) {
170
+ devfileUrl = arg.substring('--devfile-url:'.length);
171
+ }
172
+ if (arg.startsWith('--plugin-registry-url:')) {
173
+ pluginRegistryUrl = arg.substring('--plugin-registry-url:'.length);
174
+ }
175
+ if (arg.startsWith('--editor-entry:')) {
176
+ editorEntry = arg.substring('--editor-entry:'.length);
177
+ }
178
+ if (arg.startsWith('--editor-path:')) {
179
+ editorPath = arg.substring('--editor-path:'.length);
180
+ }
181
+ if (arg.startsWith('--output-file:')) {
182
+ outputFile = arg.substring('--output-file:'.length);
183
+ }
184
+ if (arg.startsWith('--project.')) {
185
+ const name = arg.substring('--project.'.length, arg.indexOf('='));
186
+ let location = arg.substring(arg.indexOf('=') + 1);
187
+ location = location.replace('{{_INTERNAL_URL_}}', '{{ INTERNAL_URL }}');
188
+
189
+ projects.push({ name, location });
190
+ }
191
+ if (arg.startsWith('--injectDefaultComponent:')) {
192
+ injectDefaultComponent = arg.substring('--injectDefaultComponent:'.length);
193
+ }
194
+ if (arg.startsWith('--defaultComponentImage:')) {
195
+ defaultComponentImage = arg.substring('--defaultComponentImage:'.length);
196
+ }
197
+ });
198
+
157
199
  try {
158
- await this.doStart();
200
+ if (!editorPath && !editorEntry) {
201
+ throw new Error('missing --editor-path: or --editor-entry: parameter');
202
+ }
203
+ if (!devfilePath && !devfileUrl) {
204
+ throw new Error('missing --devfile-path: or --devfile-url: parameter');
205
+ }
206
+ if (!outputFile) {
207
+ throw new Error('missing --output-file: parameter');
208
+ }
209
+ await this.generateDevfileContext(
210
+ {
211
+ devfilePath,
212
+ devfileUrl,
213
+ editorPath,
214
+ outputFile,
215
+ pluginRegistryUrl,
216
+ editorEntry,
217
+ projects,
218
+ injectDefaultComponent,
219
+ defaultComponentImage,
220
+ },
221
+ axios.default
222
+ );
159
223
  return true;
160
224
  } catch (error) {
161
225
  console.error('stack=' + error.stack);
@@ -26,7 +26,7 @@ export class PluginRegistryResolver {
26
26
  @inject(UrlFetcher)
27
27
  private urlFetcher: UrlFetcher;
28
28
 
29
- // FQN id (like eclipse/che-theia/next)
29
+ // FQN id (like che-incubator/che-code/next)
30
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
31
  async loadDevfilePlugin(devfileId: string): Promise<any> {
32
32
  const devfileUrl = `${this.pluginRegistryUrl}/plugins/${devfileId}/devfile.yaml`;