aboutsummaryrefslogtreecommitdiff
path: root/.github/devcontainers-action/lib/utils.js
diff options
context:
space:
mode:
Diffstat (limited to '.github/devcontainers-action/lib/utils.js')
-rw-r--r--.github/devcontainers-action/lib/utils.js258
1 files changed, 223 insertions, 35 deletions
diff --git a/.github/devcontainers-action/lib/utils.js b/.github/devcontainers-action/lib/utils.js
index ba206ae..38f5604 100644
--- a/.github/devcontainers-action/lib/utils.js
+++ b/.github/devcontainers-action/lib/utils.js
@@ -35,11 +35,12 @@ 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.addCollectionsMetadataFile = exports.tarDirectory = exports.renameLocal = exports.mkdirLocal = exports.writeLocalFile = exports.readLocalFile = void 0;
+exports.getTemplatesAndPackage = exports.getFeaturesAndPackage = exports.pushCollectionsMetadataToOCI = exports.addCollectionsMetadataFile = exports.getGitHubMetadata = exports.tarDirectory = 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"));
exports.readLocalFile = (0, util_1.promisify)(fs.readFile);
@@ -62,23 +63,83 @@ function tarDirectory(path, tgzName) {
});
}
exports.tarDirectory = tarDirectory;
-function addCollectionsMetadataFile(featuresMetadata, templatesMetadata) {
+function getGitHubMetadata() {
+ // Insert github repo metadata
+ const ref = github.context.ref;
+ let sourceInformation = {
+ owner: github.context.repo.owner,
+ repo: github.context.repo.repo,
+ ref,
+ sha: github.context.sha
+ };
+ // Add tag if parseable
+ if (ref.includes('refs/tags/')) {
+ const tag = ref.replace('refs/tags/', '');
+ sourceInformation = Object.assign(Object.assign({}, sourceInformation), { tag });
+ }
+ return sourceInformation;
+}
+exports.getGitHubMetadata = getGitHubMetadata;
+function tagFeatureAtVersion(featureMetaData) {
return __awaiter(this, void 0, void 0, function* () {
- const p = path_1.default.join('.', 'devcontainer-collection.json');
- // Insert github repo metadata
- const ref = github.context.ref;
- let sourceInformation = {
- source: 'github',
+ 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,
- sha: github.context.sha
- };
- // Add tag if parseable
- if (ref.includes('refs/tags/')) {
- const tag = ref.replace('refs/tags/', '');
- sourceInformation = Object.assign(Object.assign({}, sourceInformation), { tag });
+ 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) {
+ 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 || [],
@@ -86,27 +147,148 @@ function addCollectionsMetadataFile(featuresMetadata, templatesMetadata) {
};
// Write to the file
yield (0, exports.writeLocalFile)(p, JSON.stringify(metadata, undefined, 4));
+ if (opts.shouldPublishToOCI) {
+ pushCollectionsMetadataToOCI(p);
+ }
});
}
exports.addCollectionsMetadataFile = addCollectionsMetadataFile;
-function getFeaturesAndPackage(basePath) {
+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}'`);
+ }
+ catch (error) {
+ if (error instanceof Error)
+ core.setFailed(`Failed to push collection metadata '${ociRepo}': ${error.message}`);
+ }
+ });
+}
+exports.pushCollectionsMetadataToOCI = pushCollectionsMetadataToOCI;
+function loginToGHCR() {
+ 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;
+ }
+ try {
+ yield exec(`oras login ghcr.io -u USERNAME -p ${githubToken}`);
+ core.info('Oras logged in successfully!');
+ }
+ 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 !== '.' && f !== '..') {
+ if (!f.startsWith('.')) {
const featureFolder = path_1.default.join(basePath, f);
- const archiveName = `${f}.tgz`;
- yield tarDirectory(`${basePath}/${f}`, archiveName);
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.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) {
@@ -119,23 +301,29 @@ function getFeaturesAndPackage(basePath) {
exports.getFeaturesAndPackage = getFeaturesAndPackage;
function getTemplatesAndPackage(basePath) {
return __awaiter(this, void 0, void 0, function* () {
- let archives = [];
- fs.readdir(basePath, (err, files) => {
- if (err) {
- core.error(err.message);
- core.setFailed(`failed to get list of templates: ${err.message}`);
- return;
- }
- files.forEach(file => {
- core.info(`template ==> ${file}`);
- if (file !== '.' && file !== '..') {
- const archiveName = `devcontainer-definition-${file}.tgz`;
- tarDirectory(`${basePath}/${file}`, archiveName);
- archives.push(archiveName);
+ 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;
}
- });
- });
- return archives;
+ const templateMetadata = JSON.parse(fs.readFileSync(templateJsonPath, 'utf8'));
+ metadatas.push(templateMetadata);
+ }
+ })));
+ if (metadatas.length === 0) {
+ core.setFailed('No templates found');
+ return;
+ }
+ return metadatas;
});
}
exports.getTemplatesAndPackage = getTemplatesAndPackage;