aboutsummaryrefslogtreecommitdiff
path: root/.github/devcontainers-action/lib
diff options
context:
space:
mode:
Diffstat (limited to '.github/devcontainers-action/lib')
-rw-r--r--.github/devcontainers-action/lib/generateDocs.js13
-rw-r--r--.github/devcontainers-action/lib/main.js82
-rw-r--r--.github/devcontainers-action/lib/utils.js300
3 files changed, 75 insertions, 320 deletions
diff --git a/.github/devcontainers-action/lib/generateDocs.js b/.github/devcontainers-action/lib/generateDocs.js
index 5db6322..4bbeeed 100644
--- a/.github/devcontainers-action/lib/generateDocs.js
+++ b/.github/devcontainers-action/lib/generateDocs.js
@@ -56,9 +56,11 @@ const FEATURES_README_TEMPLATE = `
#{OptionsTable}
+#{Notes}
+
---
-_Note: This file was auto-generated from the [devcontainer-feature.json](#{RepoUrl})._
+_Note: This file was auto-generated from the [devcontainer-feature.json](#{RepoUrl}). Add additional notes to a \`NOTES.md\`._
`;
const TEMPLATE_README_TEMPLATE = `
# #{Name}
@@ -107,8 +109,6 @@ function _generateDocumentation(basePath, readmeTemplate, metadataFile, ociRegis
return;
}
const srcInfo = (0, utils_1.getGitHubMetadata)();
- const owner = srcInfo.owner;
- const repo = srcInfo.repo;
// Add version
let version = 'latest';
const parsedVersion = parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.version;
@@ -131,6 +131,10 @@ function _generateDocumentation(basePath, readmeTemplate, metadataFile, ociRegis
.join('\n');
return '| Options Id | Description | Type | Default Value |\n' + '|-----|-----|-----|-----|\n' + contents;
};
+ const generateNotesMarkdown = () => {
+ const notesPath = path.join(basePath, f, 'NOTES.md');
+ return fs.existsSync(notesPath) ? fs.readFileSync(path.join(notesPath), 'utf8') : '';
+ };
let urlToConfig = './devcontainer-feature.json';
const basePathTrimmed = basePath.startsWith('./') ? basePath.substring(2) : basePath;
if (srcInfo.owner && srcInfo.repo) {
@@ -142,9 +146,10 @@ function _generateDocumentation(basePath, readmeTemplate, metadataFile, ociRegis
.replace('#{Name}', parsedJson.name ? `${parsedJson.name} (${parsedJson.id})` : `${parsedJson.id}`)
.replace('#{Description}', (_a = parsedJson.description) !== null && _a !== void 0 ? _a : '')
.replace('#{OptionsTable}', generateOptionsMarkdown())
+ .replace('#{Notes}', generateNotesMarkdown())
// Features Only
.replace('#{Registry}', ociRegistry)
- .replace('#{Namespace}', namespace == '<owner>/<repo>' ? `${owner}/${repo}` : namespace)
+ .replace('#{Namespace}', namespace)
.replace('#{Version}', version)
// Templates Only
.replace('#{ManifestName}', (_c = (_b = parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.image) === null || _b === void 0 ? void 0 : _b.manifest) !== null && _c !== void 0 ? _c : '')
diff --git a/.github/devcontainers-action/lib/main.js b/.github/devcontainers-action/lib/main.js
index 8c20111..b00b44f 100644
--- a/.github/devcontainers-action/lib/main.js
+++ b/.github/devcontainers-action/lib/main.js
@@ -39,83 +39,55 @@ Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const generateDocs_1 = require("./generateDocs");
const utils_1 = require("./utils");
+const exec = __importStar(require("@actions/exec"));
function run() {
return __awaiter(this, void 0, void 0, function* () {
core.debug('Reading input parameters...');
// Read inputs
const shouldPublishFeatures = core.getInput('publish-features').toLowerCase() === 'true';
- const shouldPublishTemplates = core.getInput('publish-templates').toLowerCase() === 'true';
const shouldGenerateDocumentation = core.getInput('generate-docs').toLowerCase() === 'true';
- // Experimental
- const shouldTagIndividualFeatures = core.getInput('tag-individual-features').toLowerCase() === 'true';
- const shouldPublishToNPM = core.getInput('publish-to-npm').toLowerCase() === 'true';
- const shouldPublishReleaseArtifacts = core.getInput('publish-release-artifacts').toLowerCase() === 'true';
- const shouldPublishToOCI = core.getInput('publish-to-oci').toLowerCase() === 'true';
- const opts = {
- shouldTagIndividualFeatures,
- shouldPublishToNPM,
- shouldPublishReleaseArtifacts,
- shouldPublishToOCI
- };
const featuresBasePath = core.getInput('base-path-to-features');
- const templatesBasePath = core.getInput('base-path-to-templates');
- const ociRegistry = core.getInput('oci-registry');
- const namespace = core.getInput('features-namespace');
- let featuresMetadata = undefined;
- let templatesMetadata = undefined;
- // -- Package Release Artifacts
+ const sourceMetadata = (0, utils_1.getGitHubMetadata)();
+ const inputOciRegistry = core.getInput('oci-registry');
+ const ociRegistry = inputOciRegistry && inputOciRegistry !== '' ? inputOciRegistry : 'ghcr.io';
+ const inputNamespace = core.getInput('namespace');
+ const namespace = inputNamespace && inputNamespace !== '' ? inputNamespace : `${sourceMetadata.owner}/${sourceMetadata.repo}`;
+ const cliDebugMode = core.getInput('devcontainer-cli-debug-mode').toLowerCase() === 'true';
+ // -- Publish
if (shouldPublishFeatures) {
core.info('Publishing features...');
- featuresMetadata = yield packageFeatures(featuresBasePath, opts);
- }
- if (shouldPublishTemplates) {
- core.info('Publishing template...');
- templatesMetadata = yield packageTemplates(templatesBasePath);
+ yield publishFeatures(featuresBasePath, ociRegistry, namespace, cliDebugMode);
}
// -- Generate Documentation
if (shouldGenerateDocumentation && featuresBasePath) {
core.info('Generating documentation for features...');
yield (0, generateDocs_1.generateFeaturesDocumentation)(featuresBasePath, ociRegistry, namespace);
}
- if (shouldGenerateDocumentation && templatesBasePath) {
- core.info('Generating documentation for templates...');
- yield (0, generateDocs_1.generateTemplateDocumentation)(templatesBasePath);
- }
- // -- Programatically add feature/template metadata to collections file.
- core.info('Generating metadata file: devcontainer-collection.json');
- yield (0, utils_1.addCollectionsMetadataFile)(featuresMetadata, templatesMetadata, opts);
});
}
-function packageFeatures(basePath, opts) {
+function publishFeatures(basePath, ociRegistry, namespace, cliDebugMode = false) {
return __awaiter(this, void 0, void 0, function* () {
- try {
- core.info(`Archiving all features in ${basePath}`);
- const metadata = yield (0, utils_1.getFeaturesAndPackage)(basePath, opts);
- core.info('Packaging features has finished.');
- return metadata;
- }
- catch (error) {
- if (error instanceof Error) {
- core.setFailed(error.message);
- }
+ // Ensures we have the devcontainer CLI installed.
+ if (!(yield (0, utils_1.ensureDevcontainerCliPresent)(cliDebugMode))) {
+ core.setFailed('Failed to install devcontainer CLI');
+ return false;
}
- return;
- });
-}
-function packageTemplates(basePath) {
- return __awaiter(this, void 0, void 0, function* () {
try {
- core.info(`Archiving all templates in ${basePath}`);
- const metadata = yield (0, utils_1.getTemplatesAndPackage)(basePath);
- core.info('Packaging templates has finished.');
- return metadata;
- }
- catch (error) {
- if (error instanceof Error) {
- core.setFailed(error.message);
+ let cmd = 'devcontainer';
+ let args = ['features', 'publish', '-r', ociRegistry, '-n', namespace, basePath];
+ if (cliDebugMode) {
+ cmd = 'npx';
+ args = ['-y', './devcontainer.tgz', ...args];
}
+ const res = yield exec.getExecOutput(cmd, args, {
+ ignoreReturnCode: true
+ });
+ return res.exitCode === 0;
+ }
+ catch (err) {
+ core.setFailed(err === null || err === void 0 ? void 0 : err.message);
+ return false;
}
- return;
});
}
run();
diff --git a/.github/devcontainers-action/lib/utils.js b/.github/devcontainers-action/lib/utils.js
index 38f5604..f0885ce 100644
--- a/.github/devcontainers-action/lib/utils.js
+++ b/.github/devcontainers-action/lib/utils.js
@@ -31,42 +31,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
-var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
-};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.getTemplatesAndPackage = exports.getFeaturesAndPackage = exports.pushCollectionsMetadataToOCI = exports.addCollectionsMetadataFile = exports.getGitHubMetadata = exports.tarDirectory = exports.renameLocal = exports.mkdirLocal = exports.writeLocalFile = exports.readLocalFile = void 0;
+exports.ensureDevcontainerCliPresent = exports.isDevcontainerCliAvailable = exports.getGitHubMetadata = exports.renameLocal = exports.mkdirLocal = exports.writeLocalFile = exports.readLocalFile = void 0;
const github = __importStar(require("@actions/github"));
-const tar = __importStar(require("tar"));
const fs = __importStar(require("fs"));
-const core = __importStar(require("@actions/core"));
-const child_process = __importStar(require("child_process"));
const util_1 = require("util");
-const path_1 = __importDefault(require("path"));
+const core = __importStar(require("@actions/core"));
+const exec = __importStar(require("@actions/exec"));
exports.readLocalFile = (0, util_1.promisify)(fs.readFile);
exports.writeLocalFile = (0, util_1.promisify)(fs.writeFile);
exports.mkdirLocal = (0, util_1.promisify)(fs.mkdir);
exports.renameLocal = (0, util_1.promisify)(fs.rename);
-// Filter what gets included in the tar.c
-const filter = (file, _) => {
- // Don't include the archive itself.
- if (file === './devcontainer-features.tgz') {
- return false;
- }
- return true;
-};
-function tarDirectory(path, tgzName) {
- return __awaiter(this, void 0, void 0, function* () {
- return tar.create({ file: tgzName, C: path, filter }, ['.']).then(_ => {
- core.info(`Compressed ${path} directory to file ${tgzName}`);
- });
- });
-}
-exports.tarDirectory = tarDirectory;
function getGitHubMetadata() {
// Insert github repo metadata
const ref = github.context.ref;
- let sourceInformation = {
+ let metadata = {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
ref,
@@ -75,255 +54,54 @@ function getGitHubMetadata() {
// Add tag if parseable
if (ref.includes('refs/tags/')) {
const tag = ref.replace('refs/tags/', '');
- sourceInformation = Object.assign(Object.assign({}, sourceInformation), { tag });
+ metadata = Object.assign(Object.assign({}, metadata), { tag });
}
- return sourceInformation;
+ return metadata;
}
exports.getGitHubMetadata = getGitHubMetadata;
-function tagFeatureAtVersion(featureMetaData) {
- return __awaiter(this, void 0, void 0, function* () {
- const featureId = featureMetaData.id;
- const featureVersion = featureMetaData.version;
- const tagName = `${featureId}_v${featureVersion}`;
- // Get GITHUB_TOKEN from environment
- const githubToken = process.env.GITHUB_TOKEN;
- if (!githubToken) {
- core.setFailed('GITHUB_TOKEN environment variable is not set.');
- return;
- }
- // Setup Octokit client
- const octokit = github.getOctokit(githubToken);
- // Use octokit to get all tags for this repo
- const tags = yield octokit.rest.repos.listTags({
- owner: github.context.repo.owner,
- repo: github.context.repo.repo
- });
- // See if tags for this release was already created.
- const tagExists = tags.data.some(tag => tag.name === tagName);
- if (tagExists) {
- core.info(`Tag ${tagName} already exists. Skipping...`);
- return;
- }
- // Create tag
- const createdTag = yield octokit.rest.git.createTag({
- tag: tagName,
- message: `Feature ${featureId} version ${featureVersion}`,
- object: github.context.sha,
- type: 'commit',
- owner: github.context.repo.owner,
- repo: github.context.repo.repo
- });
- if (createdTag.status === 201) {
- core.info(`Tagged '${tagName}'`);
- }
- else {
- core.setFailed(`Failed to tag '${tagName}'`);
- return;
- }
- // Create reference to tag
- const createdRef = yield octokit.rest.git.createRef({
- owner: github.context.repo.owner,
- repo: github.context.repo.repo,
- ref: `refs/tags/${tagName}`,
- sha: createdTag.data.sha
- });
- if (createdRef.status === 201) {
- core.info(`Created reference for '${tagName}'`);
- }
- else {
- core.setFailed(`Failed to reference of tag '${tagName}'`);
- return;
- }
- });
-}
-function addCollectionsMetadataFile(featuresMetadata, templatesMetadata, opts) {
+function isDevcontainerCliAvailable(cliDebugMode = false) {
return __awaiter(this, void 0, void 0, function* () {
- const p = path_1.default.join('.', 'devcontainer-collection.json');
- const sourceInformation = getGitHubMetadata();
- const metadata = {
- sourceInformation,
- features: featuresMetadata || [],
- templates: templatesMetadata || []
- };
- // Write to the file
- yield (0, exports.writeLocalFile)(p, JSON.stringify(metadata, undefined, 4));
- if (opts.shouldPublishToOCI) {
- pushCollectionsMetadataToOCI(p);
- }
- });
-}
-exports.addCollectionsMetadataFile = addCollectionsMetadataFile;
-function pushArtifactToOCI(version, featureName, artifactPath) {
- return __awaiter(this, void 0, void 0, function* () {
- const exec = (0, util_1.promisify)(child_process.exec);
- const versions = [version, '1.0', '1', 'latest']; // TODO: Generate semantic versions from 'version'
- const sourceInfo = getGitHubMetadata();
- yield Promise.all(versions.map((v) => __awaiter(this, void 0, void 0, function* () {
- const ociRepo = `${sourceInfo.owner}/${sourceInfo.repo}/${featureName}:${v}`;
- try {
- const cmd = `oras push ghcr.io/${ociRepo} \
- --manifest-config /dev/null:application/vnd.devcontainers \
- ./${artifactPath}:application/vnd.devcontainers.layer.v1+tar`;
- yield exec(cmd);
- core.info(`Pushed artifact to '${ociRepo}'`);
- }
- catch (error) {
- if (error instanceof Error)
- core.setFailed(`Failed to push '${ociRepo}': ${error.message}`);
- }
- })));
- });
-}
-function pushCollectionsMetadataToOCI(collectionJsonPath) {
- return __awaiter(this, void 0, void 0, function* () {
- const exec = (0, util_1.promisify)(child_process.exec);
- const sourceInfo = getGitHubMetadata();
- const ociRepo = `${sourceInfo.owner}/${sourceInfo.repo}:latest`;
try {
- const cmd = `oras push ghcr.io/${ociRepo} \
- --manifest-config /dev/null:application/vnd.devcontainers \
- ./${collectionJsonPath}:application/vnd.devcontainers.collection.layer.v1+json`;
- yield exec(cmd);
- core.info(`Pushed collection metadata to '${ociRepo}'`);
+ let cmd = 'devcontainer';
+ let args = ['--version'];
+ if (cliDebugMode) {
+ cmd = 'npx';
+ args = ['-y', './devcontainer.tgz', ...args];
+ }
+ const res = yield exec.getExecOutput(cmd, args, {
+ ignoreReturnCode: true,
+ silent: true
+ });
+ core.info(`Devcontainer CLI version '${res.stdout}' is installed.`);
+ return res.exitCode === 0;
}
- catch (error) {
- if (error instanceof Error)
- core.setFailed(`Failed to push collection metadata '${ociRepo}': ${error.message}`);
+ catch (err) {
+ return false;
}
});
}
-exports.pushCollectionsMetadataToOCI = pushCollectionsMetadataToOCI;
-function loginToGHCR() {
+exports.isDevcontainerCliAvailable = isDevcontainerCliAvailable;
+function ensureDevcontainerCliPresent(cliDebugMode = false) {
return __awaiter(this, void 0, void 0, function* () {
- const exec = (0, util_1.promisify)(child_process.exec);
- // Get GITHUB_TOKEN from environment
- const githubToken = process.env.GITHUB_TOKEN;
- if (!githubToken) {
- core.setFailed('GITHUB_TOKEN environment variable is not set.');
- return;
+ if (yield isDevcontainerCliAvailable(cliDebugMode)) {
+ core.info('devcontainer CLI is already installed');
+ return true;
}
- try {
- yield exec(`oras login ghcr.io -u USERNAME -p ${githubToken}`);
- core.info('Oras logged in successfully!');
+ if (cliDebugMode) {
+ core.error('Cannot remotely fetch CLI in debug mode');
+ return false;
}
- catch (error) {
- if (error instanceof Error)
- core.setFailed(` Oras login failed!`);
- }
- });
-}
-function getFeaturesAndPackage(basePath, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- const { shouldPublishToNPM, shouldTagIndividualFeatures, shouldPublishReleaseArtifacts, shouldPublishToOCI } = opts;
- const featureDirs = fs.readdirSync(basePath);
- let metadatas = [];
- const exec = (0, util_1.promisify)(child_process.exec);
- if (shouldPublishToOCI) {
- yield loginToGHCR();
- }
- yield Promise.all(featureDirs.map((f) => __awaiter(this, void 0, void 0, function* () {
- var _a;
- core.info(`feature ==> ${f}`);
- if (!f.startsWith('.')) {
- const featureFolder = path_1.default.join(basePath, f);
- const featureJsonPath = path_1.default.join(featureFolder, 'devcontainer-feature.json');
- if (!fs.existsSync(featureJsonPath)) {
- core.error(`Feature '${f}' is missing a devcontainer-feature.json`);
- core.setFailed('All features must have a devcontainer-feature.json');
- return;
- }
- const featureMetadata = JSON.parse(fs.readFileSync(featureJsonPath, 'utf8'));
- if (!featureMetadata.id || !featureMetadata.version) {
- core.error(`Feature '${f}' is must defined an id and version`);
- core.setFailed('Incomplete devcontainer-feature.json');
- }
- metadatas.push(featureMetadata);
- const sourceInfo = getGitHubMetadata();
- if (!sourceInfo.owner) {
- core.setFailed('Could not determine repository owner.');
- return;
- }
- const archiveName = `${f}.tgz`;
- // ---- PUBLISH RELEASE ARTIFACTS (classic method) ----
- if (shouldPublishReleaseArtifacts || shouldPublishToOCI) {
- core.info(`** Tar'ing feature`);
- yield tarDirectory(featureFolder, archiveName);
- }
- // ---- PUBLISH TO NPM ----
- if (shouldPublishToOCI) {
- core.info(`** Publishing to OCI`);
- // TODO: CHECK IF THE FEATURE IS ALREADY PUBLISHED UNDER GIVEN TAG
- yield pushArtifactToOCI(featureMetadata.version, f, archiveName);
- }
- // ---- TAG INDIVIDUAL FEATURES ----
- if (shouldTagIndividualFeatures) {
- core.info(`** Tagging individual feature`);
- yield tagFeatureAtVersion(featureMetadata);
- }
- // ---- PUBLISH TO NPM ----
- if (shouldPublishToNPM) {
- core.info(`** Publishing to NPM`);
- // Adds a package.json file to the feature folder
- const packageJsonPath = path_1.default.join(featureFolder, 'package.json');
- // if (!sourceInfo.tag) {
- // core.error(`Feature ${f} is missing a tag! Cannot publish to NPM.`);
- // core.setFailed('All features published to NPM must be tagged with a version');
- // }
- const packageJsonObject = {
- name: `@${sourceInfo.owner}/${f}`,
- version: featureMetadata.version,
- description: `${(_a = featureMetadata.description) !== null && _a !== void 0 ? _a : 'My cool feature'}`,
- author: `${sourceInfo.owner}`,
- keywords: ['devcontainer-features']
- };
- yield (0, exports.writeLocalFile)(packageJsonPath, JSON.stringify(packageJsonObject, undefined, 4));
- core.info(`Feature Folder is: ${featureFolder}`);
- // Run npm pack, which 'tars' the folder
- const packageName = yield exec(`npm pack ./${featureFolder}`);
- if (packageName.stderr) {
- core.error(`${packageName.stderr.toString()}`);
- }
- const publishOutput = yield exec(`npm publish --access public "${packageName.stdout.trim()}"`);
- core.info(publishOutput.stdout);
- if (publishOutput.stderr) {
- core.error(`${publishOutput.stderr}`);
- }
- }
- }
- })));
- if (metadatas.length === 0) {
- core.setFailed('No features found');
- return;
+ try {
+ core.info('Fetching the latest @devcontainer/cli...');
+ const res = yield exec.getExecOutput('npm', ['install', '-g', '@devcontainers/cli'], {
+ ignoreReturnCode: true,
+ silent: true
+ });
+ return res.exitCode === 0;
}
- return metadatas;
- });
-}
-exports.getFeaturesAndPackage = getFeaturesAndPackage;
-function getTemplatesAndPackage(basePath) {
- return __awaiter(this, void 0, void 0, function* () {
- const templateDirs = fs.readdirSync(basePath);
- let metadatas = [];
- yield Promise.all(templateDirs.map((t) => __awaiter(this, void 0, void 0, function* () {
- core.info(`template ==> ${t}`);
- if (!t.startsWith('.')) {
- const templateFolder = path_1.default.join(basePath, t);
- const archiveName = `devcontainer-template-${t}.tgz`;
- // await tarDirectory(templateFolder, archiveName);
- const templateJsonPath = path_1.default.join(templateFolder, 'devcontainer-template.json');
- if (!fs.existsSync(templateJsonPath)) {
- core.error(`Template '${t}' is missing a devcontainer-template.json`);
- core.setFailed('All templates must have a devcontainer-template.json');
- return;
- }
- const templateMetadata = JSON.parse(fs.readFileSync(templateJsonPath, 'utf8'));
- metadatas.push(templateMetadata);
- }
- })));
- if (metadatas.length === 0) {
- core.setFailed('No templates found');
- return;
+ catch (err) {
+ return false;
}
- return metadatas;
});
}
-exports.getTemplatesAndPackage = getTemplatesAndPackage;
+exports.ensureDevcontainerCliPresent = ensureDevcontainerCliPresent;