diff options
author | Samruddhi Khandale <skhandale@microsoft.com> | 2022-08-05 22:22:30 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-05 22:22:30 +0300 |
commit | 542963cf04cbe62d54a0e287fa314b05c612671f (patch) | |
tree | 2c420b0f821de24032985af51ba081a4e617b6a5 /.github/devcontainers-action/lib | |
parent | 69d3df5f945adc4f7ab5d1198167a1dbc8412fc9 (diff) |
Prep: republish features to ghcr.io (#78)
* syncing action
* modify release.yaml
* version update ; remove "install"
* add 'latest'
* add workflow condition
Diffstat (limited to '.github/devcontainers-action/lib')
-rw-r--r-- | .github/devcontainers-action/lib/contracts/templates.js | 2 | ||||
-rw-r--r-- | .github/devcontainers-action/lib/generateDocs.js | 186 | ||||
-rw-r--r-- | .github/devcontainers-action/lib/main.js | 62 | ||||
-rw-r--r-- | .github/devcontainers-action/lib/utils.js | 258 |
4 files changed, 366 insertions, 142 deletions
diff --git a/.github/devcontainers-action/lib/contracts/templates.js b/.github/devcontainers-action/lib/contracts/templates.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/.github/devcontainers-action/lib/contracts/templates.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/.github/devcontainers-action/lib/generateDocs.js b/.github/devcontainers-action/lib/generateDocs.js index b82517f..1250ffc 100644 --- a/.github/devcontainers-action/lib/generateDocs.js +++ b/.github/devcontainers-action/lib/generateDocs.js @@ -32,95 +32,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.generateFeaturesDocumentation = void 0; +exports.generateTemplateDocumentation = exports.generateFeaturesDocumentation = void 0; const fs = __importStar(require("fs")); -const github = __importStar(require("@actions/github")); const core = __importStar(require("@actions/core")); const path = __importStar(require("path")); -function generateFeaturesDocumentation(basePath) { - return __awaiter(this, void 0, void 0, function* () { - fs.readdir(basePath, (err, files) => { - if (err) { - core.error(err.message); - core.setFailed(`failed to generate 'features' documentation ${err.message}`); - return; - } - files.forEach(f => { - core.info(`Generating docs for feature '${f}'`); - if (f !== '.' && f !== '..') { - const readmePath = path.join(basePath, f, 'README.md'); - // Reads in feature.json - const featureJsonPath = path.join(basePath, f, 'devcontainer-feature.json'); - if (!fs.existsSync(featureJsonPath)) { - core.error(`devcontainer-feature.json not found at path '${featureJsonPath}'`); - return; - } - let featureJson = undefined; - try { - featureJson = JSON.parse(fs.readFileSync(featureJsonPath, 'utf8')); - } - catch (err) { - core.error(`Failed to parse ${featureJsonPath}: ${err}`); - return; - } - if (!featureJson || !(featureJson === null || featureJson === void 0 ? void 0 : featureJson.id)) { - core.error(`devcontainer-feature.json for feature '${f}' does not contain an 'id'`); - return; - } - const ref = github.context.ref; - const owner = github.context.repo.owner; - const repo = github.context.repo.repo; - // Add tag if parseable - let versionTag = 'latest'; - if (ref.includes('refs/tags/')) { - versionTag = ref.replace('refs/tags/', ''); - } - const generateOptionsMarkdown = () => { - const options = featureJson === null || featureJson === void 0 ? void 0 : featureJson.options; - if (!options) { - return ''; - } - const keys = Object.keys(options); - const contents = keys - .map(k => { - const val = options[k]; - return `| ${k} | ${val.description || '-'} | ${val.type || '-'} | ${val.default || '-'} |`; - }) - .join('\n'); - return ('| Options Id | Description | Type | Default Value |\n' + - '|-----|-----|-----|-----|\n' + - contents); - }; - const newReadme = README_TEMPLATE.replace('#{nwo}', `${owner}/${repo}`) - .replace('#{versionTag}', versionTag) - .replace('#{featureId}', featureJson.id) - .replace('#{featureName}', featureJson.name - ? `${featureJson.name} (${featureJson.id})` - : `${featureJson.id}`) - .replace('#{featureDescription}', featureJson.description ? featureJson.description : '') - .replace('#{optionsTable}', generateOptionsMarkdown()); - // Remove previous readme - if (fs.existsSync(readmePath)) { - fs.unlinkSync(readmePath); - } - // Write new readme - fs.writeFileSync(readmePath, newReadme); - } - }); - }); - }); -} -exports.generateFeaturesDocumentation = generateFeaturesDocumentation; -const README_TEMPLATE = ` -# #{featureName} +const utils_1 = require("./utils"); +const FEATURES_README_TEMPLATE = ` +# #{Name} -#{featureDescription} +#{Description} ## Example Usage \`\`\`json "features": { - "#{nwo}/#{featureId}@#{versionTag}": { + "#{Nwo}/#{Id}@#{VersionTag}": { "version": "latest" } } @@ -128,9 +54,105 @@ const README_TEMPLATE = ` ## Options -#{optionsTable} +#{OptionsTable} --- -_Note: This file was auto-generated from the [devcontainer-feature.json](./devcontainer-feature.json)._ +_Note: This file was auto-generated from the [devcontainer-feature.json](#{RepoUrl})._ `; +const TEMPLATE_README_TEMPLATE = ` +# #{Name} + +#{Description} + +## Options + +#{OptionsTable} +`; +function generateFeaturesDocumentation(basePath) { + return __awaiter(this, void 0, void 0, function* () { + yield _generateDocumentation(basePath, FEATURES_README_TEMPLATE, 'devcontainer-feature.json'); + }); +} +exports.generateFeaturesDocumentation = generateFeaturesDocumentation; +function generateTemplateDocumentation(basePath) { + return __awaiter(this, void 0, void 0, function* () { + yield _generateDocumentation(basePath, TEMPLATE_README_TEMPLATE, 'devcontainer-template.json'); + }); +} +exports.generateTemplateDocumentation = generateTemplateDocumentation; +function _generateDocumentation(basePath, readmeTemplate, metadataFile) { + return __awaiter(this, void 0, void 0, function* () { + const directories = fs.readdirSync(basePath); + yield Promise.all(directories.map((f) => __awaiter(this, void 0, void 0, function* () { + var _a, _b, _c; + if (!f.startsWith('.')) { + const readmePath = path.join(basePath, f, 'README.md'); + // Reads in feature.json + const jsonPath = path.join(basePath, f, metadataFile); + if (!fs.existsSync(jsonPath)) { + core.error(`${metadataFile} not found at path '${jsonPath}'`); + return; + } + let parsedJson = undefined; + try { + parsedJson = JSON.parse(fs.readFileSync(jsonPath, 'utf8')); + } + catch (err) { + core.error(`Failed to parse ${jsonPath}: ${err}`); + return; + } + if (!parsedJson || !(parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.id)) { + core.error(`${metadataFile} for '${f}' does not contain an 'id'`); + return; + } + const srcInfo = (0, utils_1.getGitHubMetadata)(); + const ref = srcInfo.ref; + const owner = srcInfo.owner; + const repo = srcInfo.repo; + // Add tag if parseable + let versionTag = 'latest'; + if (ref && ref.includes('refs/tags/')) { + versionTag = ref.replace('refs/tags/', ''); + } + const generateOptionsMarkdown = () => { + const options = parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.options; + if (!options) { + return ''; + } + const keys = Object.keys(options); + const contents = keys + .map(k => { + const val = options[k]; + return `| ${k} | ${val.description || '-'} | ${val.type || '-'} | ${val.default || '-'} |`; + }) + .join('\n'); + return '| Options Id | Description | Type | Default Value |\n' + '|-----|-----|-----|-----|\n' + contents; + }; + let urlToConfig = './devcontainer-feature.json'; + const basePathTrimmed = basePath.startsWith('./') ? basePath.substring(2) : basePath; + if (srcInfo.owner && srcInfo.repo) { + urlToConfig = `https://github.com/${srcInfo.owner}/${srcInfo.repo}/blob/main/${basePathTrimmed}/${f}/devcontainer-feature.json`; + } + const newReadme = readmeTemplate + // Templates & Features + .replace('#{Id}', parsedJson.id) + .replace('#{Name}', parsedJson.name ? `${parsedJson.name} (${parsedJson.id})` : `${parsedJson.id}`) + .replace('#{Description}', (_a = parsedJson.description) !== null && _a !== void 0 ? _a : '') + .replace('#{OptionsTable}', generateOptionsMarkdown()) + // Features Only + .replace('#{Nwo}', `${owner}/${repo}`) + .replace('#{VersionTag}', versionTag) + // 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 : '') + .replace('#{RepoUrl}', urlToConfig); + // Remove previous readme + if (fs.existsSync(readmePath)) { + fs.unlinkSync(readmePath); + } + // Write new readme + fs.writeFileSync(readmePath, newReadme); + } + }))); + }); +} diff --git a/.github/devcontainers-action/lib/main.js b/.github/devcontainers-action/lib/main.js index fca18f2..69506b7 100644 --- a/.github/devcontainers-action/lib/main.js +++ b/.github/devcontainers-action/lib/main.js @@ -44,42 +44,51 @@ function run() { core.debug('Reading input parameters...'); // Read inputs const shouldPublishFeatures = core.getInput('publish-features').toLowerCase() === 'true'; - const shouldPublishTemplate = core.getInput('publish-templates').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'); let featuresMetadata = undefined; let templatesMetadata = undefined; + // -- Package Release Artifacts if (shouldPublishFeatures) { core.info('Publishing features...'); - const featuresBasePath = core.getInput('base-path-to-features'); - featuresMetadata = yield packageFeatures(featuresBasePath); + featuresMetadata = yield packageFeatures(featuresBasePath, opts); } - if (shouldPublishTemplate) { + if (shouldPublishTemplates) { core.info('Publishing template...'); - const basePathToDefinitions = core.getInput('base-path-to-templates'); - templatesMetadata = undefined; // TODO - yield packageTemplates(basePathToDefinitions); + templatesMetadata = yield packageTemplates(templatesBasePath); } - if (shouldGenerateDocumentation) { - core.info('Generating documentation...'); - const featuresBasePath = core.getInput('base-path-to-features'); - if (featuresBasePath) { - yield (0, generateDocs_1.generateFeaturesDocumentation)(featuresBasePath); - } - else { - core.error("'base-path-to-features' input is required to generate documentation"); - } - // TODO: base-path-to-templates + // -- Generate Documentation + if (shouldGenerateDocumentation && featuresBasePath) { + core.info('Generating documentation for features...'); + yield (0, generateDocs_1.generateFeaturesDocumentation)(featuresBasePath); } - // TODO: Programatically add feature/template fino with relevant metadata for UX clients. - core.info('Generation metadata file: devcontainer-collection.json'); - yield (0, utils_1.addCollectionsMetadataFile)(featuresMetadata, templatesMetadata); + 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) { +function packageFeatures(basePath, opts) { return __awaiter(this, void 0, void 0, function* () { try { core.info(`Archiving all features in ${basePath}`); - const metadata = yield (0, utils_1.getFeaturesAndPackage)(basePath); + const metadata = yield (0, utils_1.getFeaturesAndPackage)(basePath, opts); core.info('Packaging features has finished.'); return metadata; } @@ -94,14 +103,17 @@ function packageFeatures(basePath) { function packageTemplates(basePath) { return __awaiter(this, void 0, void 0, function* () { try { - core.info(`Archiving all templated in ${basePath}`); - yield (0, utils_1.getTemplatesAndPackage)(basePath); + 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) + if (error instanceof Error) { core.setFailed(error.message); + } } + return; }); } run(); 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; |