-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathmain.ts
More file actions
185 lines (148 loc) · 7.35 KB
/
main.ts
File metadata and controls
185 lines (148 loc) · 7.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*--------------------------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
*-------------------------------------------------------------------------------------------------------------*/
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as path from 'path';
import { PublishResult } from './contracts/collection';
import { generateFeaturesDocumentation, generateTemplateDocumentation } from './generateDocs';
import { addRepoTagForPublishedTag, ensureDevcontainerCliPresent, getGitHubMetadata, readdirLocal, validateFeatureSchema } from './utils';
async function run(): Promise<void> {
core.debug('Reading input parameters...');
// Read inputs
const shouldGenerateDocumentation = core.getInput('generate-docs').toLowerCase() === 'true';
const sourceMetadata = getGitHubMetadata();
// Read inputs - Features
const shouldPublishFeatures = core.getInput('publish-features').toLowerCase() === 'true';
const featuresBasePath = core.getInput('base-path-to-features');
const inputFeaturesOciRegistry = core.getInput('oci-registry');
const featuresOciRegistry = !!inputFeaturesOciRegistry ? inputFeaturesOciRegistry : 'ghcr.io';
const inputFeaturesNamespace = core.getInput('features-namespace');
const featuresNamespace = !!inputFeaturesNamespace ? inputFeaturesNamespace : `${sourceMetadata.owner}/${sourceMetadata.repo}`;
// Read inputs - Templates
const shouldPublishTemplates = core.getInput('publish-templates').toLowerCase() === 'true';
const templatesBasePath = core.getInput('base-path-to-templates');
const inputTemplatesOciRegistry = core.getInput('oci-registry-for-templates');
const templatesOciRegistry = !!inputTemplatesOciRegistry ? inputTemplatesOciRegistry : 'ghcr.io';
const inputTemplateNamespace = core.getInput('templates-namespace');
const templatesNamespace = !!inputTemplateNamespace ? inputTemplateNamespace : `${sourceMetadata.owner}/${sourceMetadata.repo}`;
const cliDebugMode = core.getInput('devcontainer-cli-debug-mode').toLowerCase() === 'true';
const disableSchemaValidationAsError = core.getInput('disable-schema-validation').toLowerCase() === 'true';
const validateOnly = core.getInput('validate-only').toLowerCase() === 'true';
const disableRepoTagging = core.getInput('disable-repo-tagging').toLowerCase() === 'true';
// -- Publish
if (shouldPublishFeatures && shouldPublishTemplates) {
core.setFailed('(!) Features and Templates should be published from different repositories.');
return;
}
if ((shouldPublishFeatures && validateOnly) || (shouldPublishTemplates && validateOnly)) {
core.setFailed('(!) publishing steps and "validateOnly" are mutually exclusive.');
return;
}
if (shouldGenerateDocumentation && featuresBasePath && templatesBasePath) {
core.setFailed('(!) Features and Templates should exist in different repositories.');
return;
}
if (shouldPublishFeatures || validateOnly) {
core.info('Validating Feature metadata...');
if (!(await prePublish('feature', featuresBasePath))) {
if (disableSchemaValidationAsError) {
core.warning('Failed to validate Feature metadata. NOTE: This warning will be a fatal error in future releases.')
} else {
core.setFailed('(!) Failed to validate Feature metadata.');
return;
}
}
}
if (shouldPublishFeatures) {
core.info('Publishing Features...');
const publishedFeatures = await publish('feature', featuresBasePath, featuresOciRegistry, featuresNamespace, cliDebugMode);
if (!publishedFeatures) {
core.setFailed('(!) Failed to publish Features.');
return;
}
// Add repo tag for this version at the current commit.
if (!disableRepoTagging) {
for (const featureId in publishedFeatures) {
const version = publishedFeatures[featureId]?.version;
if (!version) {
core.debug(`No version available for '${featureId}', so no repo tag was added for Feature`);
continue;
}
await addRepoTagForPublishedTag('feature', featureId, version);
}
}
}
if (shouldPublishTemplates) {
core.info('Publishing Templates...');
const publishedTemplates = await publish('template', templatesBasePath, templatesOciRegistry, templatesNamespace, cliDebugMode);
if (!publishedTemplates) {
core.setFailed('(!) Failed to publish Templates.');
return;
}
// Add repo tag for this version at the current commit.
if (!disableRepoTagging) {
for (const templateId in publishedTemplates) {
const version = publishedTemplates[templateId]?.version;
if (!version) {
core.debug(`No version available for '${templateId}', so no repo tag was added for Feature`);
continue;
}
await addRepoTagForPublishedTag('template', templateId, version);
}
}
}
// -- Generate Documentation
if (shouldGenerateDocumentation && featuresBasePath) {
core.info('Generating documentation for Features...');
await generateFeaturesDocumentation(featuresBasePath, featuresOciRegistry, featuresNamespace);
}
if (shouldGenerateDocumentation && templatesBasePath) {
core.info('Generating documentation for Templates...');
await generateTemplateDocumentation(templatesBasePath);
}
}
async function prePublish(collectionType: 'feature' | 'template', basePath: string): Promise<boolean> {
let hasFailed = false;
// Iterate each (Feature|Template) in 'basePath'
for (const folder of await readdirLocal(basePath)) {
const pathToArtifact = path.join(basePath, folder);
if (collectionType === 'feature') {
if (!await validateFeatureSchema(pathToArtifact)) {
hasFailed = true;
}
}
// if (collectionType == 'template') { }
}
return !hasFailed;
}
async function publish(
collectionType: 'feature' | 'template',
basePath: string,
ociRegistry: string,
namespace: string,
cliDebugMode = false
): Promise<{ [featureId: string]: PublishResult } | undefined> {
// Ensures we have the devcontainer CLI installed.
if (!(await ensureDevcontainerCliPresent(cliDebugMode))) {
core.setFailed('Failed to install devcontainer CLI');
return;
}
try {
let cmd: string = 'devcontainer';
let args: string[] = [`${collectionType}s`, 'publish', '-r', ociRegistry, '-n', namespace, basePath];
if (cliDebugMode) {
cmd = 'npx';
args = ['-y', './devcontainer.tgz', ...args];
}
// Fails on non-zero exit code from the invoked process
const res = await exec.getExecOutput(cmd, args, {});
const result: { [featureId: string]: PublishResult } = JSON.parse(res.stdout);
return result;
} catch (err: any) {
core.setFailed(err?.message);
return;
}
}
run();