mirror of
https://github.com/docker/actions-toolkit.git
synced 2024-11-23 11:36:10 +08:00
Merge pull request #324 from crazy-max/build-summary
github: write build summary
This commit is contained in:
commit
0903e498a4
@ -86,6 +86,7 @@ maybe('exportBuild', () => {
|
|||||||
expect(exportRes?.dockerbuildFilename).toBeDefined();
|
expect(exportRes?.dockerbuildFilename).toBeDefined();
|
||||||
expect(exportRes?.dockerbuildSize).toBeDefined();
|
expect(exportRes?.dockerbuildSize).toBeDefined();
|
||||||
expect(fs.existsSync(exportRes?.dockerbuildFilename)).toBe(true);
|
expect(fs.existsSync(exportRes?.dockerbuildFilename)).toBe(true);
|
||||||
|
expect(exportRes?.summaries).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@ -147,5 +148,6 @@ maybe('exportBuild', () => {
|
|||||||
expect(exportRes?.dockerbuildFilename).toBeDefined();
|
expect(exportRes?.dockerbuildFilename).toBeDefined();
|
||||||
expect(exportRes?.dockerbuildSize).toBeDefined();
|
expect(exportRes?.dockerbuildSize).toBeDefined();
|
||||||
expect(fs.existsSync(exportRes?.dockerbuildFilename)).toBe(true);
|
expect(fs.existsSync(exportRes?.dockerbuildFilename)).toBe(true);
|
||||||
|
expect(exportRes?.summaries).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,13 +14,22 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {beforeEach, describe, expect, it, jest} from '@jest/globals';
|
import {beforeEach, describe, expect, it, jest, test} from '@jest/globals';
|
||||||
|
import fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import {Buildx} from '../src/buildx/buildx';
|
||||||
|
import {Bake} from '../src/buildx/bake';
|
||||||
|
import {Build} from '../src/buildx/build';
|
||||||
|
import {Exec} from '../src/exec';
|
||||||
import {GitHub} from '../src/github';
|
import {GitHub} from '../src/github';
|
||||||
|
import {History} from '../src/buildx/history';
|
||||||
|
|
||||||
const fixturesDir = path.join(__dirname, 'fixtures');
|
const fixturesDir = path.join(__dirname, 'fixtures');
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const tmpDir = path.join(process.env.TEMP || '/tmp', 'github-jest');
|
||||||
|
|
||||||
const maybe = !process.env.GITHUB_ACTIONS || (process.env.GITHUB_ACTIONS === 'true' && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu')) ? describe : describe.skip;
|
const maybe = !process.env.GITHUB_ACTIONS || (process.env.GITHUB_ACTIONS === 'true' && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu')) ? describe : describe.skip;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -39,3 +48,149 @@ maybe('uploadArtifact', () => {
|
|||||||
expect(res?.url).toBeDefined();
|
expect(res?.url).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
maybe('writeBuildSummary', () => {
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
"single",
|
||||||
|
[
|
||||||
|
'build',
|
||||||
|
'-f', path.join(fixturesDir, 'hello.Dockerfile'),
|
||||||
|
fixturesDir
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"multiplatform",
|
||||||
|
[
|
||||||
|
'build',
|
||||||
|
'-f', path.join(fixturesDir, 'hello.Dockerfile'),
|
||||||
|
'--platform', 'linux/amd64,linux/arm64',
|
||||||
|
fixturesDir
|
||||||
|
],
|
||||||
|
]
|
||||||
|
])('write build summary %p', async (_, bargs) => {
|
||||||
|
const buildx = new Buildx();
|
||||||
|
const build = new Build({buildx: buildx});
|
||||||
|
|
||||||
|
fs.mkdirSync(tmpDir, {recursive: true});
|
||||||
|
await expect(
|
||||||
|
(async () => {
|
||||||
|
// prettier-ignore
|
||||||
|
const buildCmd = await buildx.getCommand([
|
||||||
|
'--builder', process.env.CTN_BUILDER_NAME ?? 'default',
|
||||||
|
...bargs,
|
||||||
|
'--metadata-file', build.getMetadataFilePath()
|
||||||
|
]);
|
||||||
|
await Exec.exec(buildCmd.command, buildCmd.args);
|
||||||
|
})()
|
||||||
|
).resolves.not.toThrow();
|
||||||
|
|
||||||
|
const metadata = build.resolveMetadata();
|
||||||
|
expect(metadata).toBeDefined();
|
||||||
|
const buildRef = build.resolveRef(metadata);
|
||||||
|
expect(buildRef).toBeDefined();
|
||||||
|
|
||||||
|
const history = new History({buildx: buildx});
|
||||||
|
const exportRes = await history.export({
|
||||||
|
refs: [buildRef ?? '']
|
||||||
|
});
|
||||||
|
expect(exportRes).toBeDefined();
|
||||||
|
expect(exportRes?.dockerbuildFilename).toBeDefined();
|
||||||
|
expect(exportRes?.dockerbuildSize).toBeDefined();
|
||||||
|
expect(exportRes?.summaries).toBeDefined();
|
||||||
|
|
||||||
|
const uploadRes = await GitHub.uploadArtifact({
|
||||||
|
filename: exportRes?.dockerbuildFilename,
|
||||||
|
mimeType: 'application/gzip',
|
||||||
|
retentionDays: 1
|
||||||
|
});
|
||||||
|
expect(uploadRes).toBeDefined();
|
||||||
|
expect(uploadRes?.url).toBeDefined();
|
||||||
|
|
||||||
|
await GitHub.writeBuildSummary({
|
||||||
|
exportRes: exportRes,
|
||||||
|
uploadRes: uploadRes,
|
||||||
|
inputs: {
|
||||||
|
context: fixturesDir,
|
||||||
|
file: path.join(fixturesDir, 'hello.Dockerfile')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
'single',
|
||||||
|
[
|
||||||
|
'bake',
|
||||||
|
'-f', path.join(fixturesDir, 'hello-bake.hcl'),
|
||||||
|
'hello'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'group',
|
||||||
|
[
|
||||||
|
'bake',
|
||||||
|
'-f', path.join(fixturesDir, 'hello-bake.hcl'),
|
||||||
|
'hello-all'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'matrix',
|
||||||
|
[
|
||||||
|
'bake',
|
||||||
|
'-f', path.join(fixturesDir, 'hello-bake.hcl'),
|
||||||
|
'hello-matrix'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
])('write bake summary %p', async (_, bargs) => {
|
||||||
|
const buildx = new Buildx();
|
||||||
|
const bake = new Bake({buildx: buildx});
|
||||||
|
|
||||||
|
fs.mkdirSync(tmpDir, {recursive: true});
|
||||||
|
await expect(
|
||||||
|
(async () => {
|
||||||
|
// prettier-ignore
|
||||||
|
const buildCmd = await buildx.getCommand([
|
||||||
|
'--builder', process.env.CTN_BUILDER_NAME ?? 'default',
|
||||||
|
...bargs,
|
||||||
|
'--metadata-file', bake.getMetadataFilePath()
|
||||||
|
]);
|
||||||
|
await Exec.exec(buildCmd.command, buildCmd.args, {
|
||||||
|
cwd: fixturesDir
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
).resolves.not.toThrow();
|
||||||
|
|
||||||
|
const metadata = bake.resolveMetadata();
|
||||||
|
expect(metadata).toBeDefined();
|
||||||
|
const buildRefs = bake.resolveRefs(metadata);
|
||||||
|
expect(buildRefs).toBeDefined();
|
||||||
|
|
||||||
|
const history = new History({buildx: buildx});
|
||||||
|
const exportRes = await history.export({
|
||||||
|
refs: buildRefs ?? []
|
||||||
|
});
|
||||||
|
expect(exportRes).toBeDefined();
|
||||||
|
expect(exportRes?.dockerbuildFilename).toBeDefined();
|
||||||
|
expect(exportRes?.dockerbuildSize).toBeDefined();
|
||||||
|
expect(exportRes?.summaries).toBeDefined();
|
||||||
|
|
||||||
|
const uploadRes = await GitHub.uploadArtifact({
|
||||||
|
filename: exportRes?.dockerbuildFilename,
|
||||||
|
mimeType: 'application/gzip',
|
||||||
|
retentionDays: 1
|
||||||
|
});
|
||||||
|
expect(uploadRes).toBeDefined();
|
||||||
|
expect(uploadRes?.url).toBeDefined();
|
||||||
|
|
||||||
|
await GitHub.writeBuildSummary({
|
||||||
|
exportRes: exportRes,
|
||||||
|
uploadRes: uploadRes,
|
||||||
|
inputs: {
|
||||||
|
files: path.join(fixturesDir, 'hello-bake.hcl')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -59,12 +59,14 @@
|
|||||||
"async-retry": "^1.3.3",
|
"async-retry": "^1.3.3",
|
||||||
"csv-parse": "^5.5.6",
|
"csv-parse": "^5.5.6",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"semver": "^7.6.2",
|
"semver": "^7.6.2",
|
||||||
"tmp": "^0.2.3"
|
"tmp": "^0.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/csv-parse": "^1.2.2",
|
"@types/csv-parse": "^1.2.2",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^20.12.10",
|
"@types/node": "^20.12.10",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/tmp": "^0.2.6",
|
"@types/tmp": "^0.2.6",
|
||||||
|
@ -27,7 +27,7 @@ import {Docker} from '../docker/docker';
|
|||||||
import {Exec} from '../exec';
|
import {Exec} from '../exec';
|
||||||
import {GitHub} from '../github';
|
import {GitHub} from '../github';
|
||||||
|
|
||||||
import {ExportRecordOpts, ExportRecordResponse} from '../types/history';
|
import {ExportRecordOpts, ExportRecordResponse, Summaries} from '../types/history';
|
||||||
|
|
||||||
export interface HistoryOpts {
|
export interface HistoryOpts {
|
||||||
buildx?: Buildx;
|
buildx?: Buildx;
|
||||||
@ -95,6 +95,7 @@ export class History {
|
|||||||
buildxDialStdioProc.stdout.pipe(fs.createWriteStream(buildxOutFifoPath));
|
buildxDialStdioProc.stdout.pipe(fs.createWriteStream(buildxOutFifoPath));
|
||||||
|
|
||||||
const tmpDockerbuildFilename = path.join(outDir, 'rec.dockerbuild');
|
const tmpDockerbuildFilename = path.join(outDir, 'rec.dockerbuild');
|
||||||
|
const summaryFilename = path.join(outDir, 'summary.json');
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const ebargs: Array<string> = ['--ref-state-dir=/buildx-refs', `--node=${builderName}/${nodeName}`];
|
const ebargs: Array<string> = ['--ref-state-dir=/buildx-refs', `--node=${builderName}/${nodeName}`];
|
||||||
@ -145,9 +146,14 @@ export class History {
|
|||||||
fs.renameSync(tmpDockerbuildFilename, dockerbuildPath);
|
fs.renameSync(tmpDockerbuildFilename, dockerbuildPath);
|
||||||
const dockerbuildStats = fs.statSync(dockerbuildPath);
|
const dockerbuildStats = fs.statSync(dockerbuildPath);
|
||||||
|
|
||||||
|
core.info(`Parsing ${summaryFilename}`);
|
||||||
|
fs.statSync(summaryFilename);
|
||||||
|
const summaries = <Summaries>JSON.parse(fs.readFileSync(summaryFilename, {encoding: 'utf-8'}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dockerbuildFilename: dockerbuildPath,
|
dockerbuildFilename: dockerbuildPath,
|
||||||
dockerbuildSize: dockerbuildStats.size,
|
dockerbuildSize: dockerbuildStats.size,
|
||||||
|
summaries: summaries,
|
||||||
builderName: builderName,
|
builderName: builderName,
|
||||||
nodeName: nodeName,
|
nodeName: nodeName,
|
||||||
refs: refs
|
refs: refs
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import jsyaml from 'js-yaml';
|
||||||
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {CreateArtifactRequest, FinalizeArtifactRequest, StringValue} from '@actions/artifact/lib/generated';
|
import {CreateArtifactRequest, FinalizeArtifactRequest, StringValue} from '@actions/artifact/lib/generated';
|
||||||
import {internalArtifactTwirpClient} from '@actions/artifact/lib/internal/shared/artifact-twirp-client';
|
import {internalArtifactTwirpClient} from '@actions/artifact/lib/internal/shared/artifact-twirp-client';
|
||||||
@ -23,6 +25,7 @@ import {getBackendIdsFromToken} from '@actions/artifact/lib/internal/shared/util
|
|||||||
import {getExpiration} from '@actions/artifact/lib/internal/upload/retention';
|
import {getExpiration} from '@actions/artifact/lib/internal/upload/retention';
|
||||||
import {InvalidResponseError, NetworkError} from '@actions/artifact';
|
import {InvalidResponseError, NetworkError} from '@actions/artifact';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
import {SummaryTableCell} from '@actions/core/lib/summary';
|
||||||
import * as github from '@actions/github';
|
import * as github from '@actions/github';
|
||||||
import {GitHub as Octokit} from '@actions/github/lib/utils';
|
import {GitHub as Octokit} from '@actions/github/lib/utils';
|
||||||
import {Context} from '@actions/github/lib/context';
|
import {Context} from '@actions/github/lib/context';
|
||||||
@ -30,7 +33,9 @@ import {TransferProgressEvent} from '@azure/core-http';
|
|||||||
import {BlobClient, BlobHTTPHeaders} from '@azure/storage-blob';
|
import {BlobClient, BlobHTTPHeaders} from '@azure/storage-blob';
|
||||||
import {jwtDecode, JwtPayload} from 'jwt-decode';
|
import {jwtDecode, JwtPayload} from 'jwt-decode';
|
||||||
|
|
||||||
import {GitHubActionsRuntimeToken, GitHubActionsRuntimeTokenAC, GitHubRepo, UploadArtifactOpts, UploadArtifactResponse} from './types/github';
|
import {Util} from './util';
|
||||||
|
|
||||||
|
import {BuildSummaryOpts, GitHubActionsRuntimeToken, GitHubActionsRuntimeTokenAC, GitHubRepo, UploadArtifactOpts, UploadArtifactResponse} from './types/github';
|
||||||
|
|
||||||
export interface GitHubOpts {
|
export interface GitHubOpts {
|
||||||
token?: string;
|
token?: string;
|
||||||
@ -190,4 +195,84 @@ export class GitHub {
|
|||||||
url: artifactURL
|
url: artifactURL
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async writeBuildSummary(opts: BuildSummaryOpts): Promise<void> {
|
||||||
|
// can't use original core.summary.addLink due to the need to make
|
||||||
|
// EOL optional
|
||||||
|
const addLink = function (text: string, url: string, addEOL = false): string {
|
||||||
|
return `<a href="${url}">${text}</a>` + (addEOL ? os.EOL : '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const refsSize = Object.keys(opts.exportRes.refs).length;
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const sum = core.summary
|
||||||
|
.addHeading('Docker Build summary', 1)
|
||||||
|
.addRaw(`<p>`)
|
||||||
|
.addRaw(`For a detailed look at the build, download the following build record archive and import it into Docker Desktop's Builds view. `)
|
||||||
|
.addBreak()
|
||||||
|
.addRaw(`Build records include details such as timing, dependencies, results, logs, traces, and other information about a build. `)
|
||||||
|
.addRaw(addLink('Learn more', 'https://docs.docker.com/go/build-summary/'))
|
||||||
|
.addRaw('</p>')
|
||||||
|
.addRaw(`<p>`)
|
||||||
|
.addRaw(`:arrow_down: ${addLink(`<strong>${opts.uploadRes.filename}</strong>`, opts.uploadRes.url)} (${Util.formatFileSize(opts.uploadRes.size)})`)
|
||||||
|
.addBreak()
|
||||||
|
.addRaw(`This file includes <strong>${refsSize} build record${refsSize > 1 ? 's' : ''}</strong>.`)
|
||||||
|
.addRaw(`</p>`)
|
||||||
|
.addRaw(`<p>`)
|
||||||
|
.addRaw(`Find this useful? `)
|
||||||
|
.addRaw(addLink('Let us know', 'https://docs.docker.com/feedback/gha-build-summary'))
|
||||||
|
.addRaw('</p>');
|
||||||
|
|
||||||
|
sum.addHeading('Preview', 2);
|
||||||
|
|
||||||
|
const summaryTableData: Array<Array<SummaryTableCell>> = [
|
||||||
|
[
|
||||||
|
{header: true, data: 'ID'},
|
||||||
|
{header: true, data: 'Name'},
|
||||||
|
{header: true, data: 'Status'},
|
||||||
|
{header: true, data: 'Cached'},
|
||||||
|
{header: true, data: 'Duration'}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
let summaryError: string | undefined;
|
||||||
|
for (const ref in opts.exportRes.summaries) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(opts.exportRes.summaries, ref)) {
|
||||||
|
const summary = opts.exportRes.summaries[ref];
|
||||||
|
// prettier-ignore
|
||||||
|
summaryTableData.push([
|
||||||
|
{data: `<code>${ref.substring(0, 6).toUpperCase()}</code>`},
|
||||||
|
{data: `<strong>${summary.name}</strong>`},
|
||||||
|
{data: `${summary.status === 'completed' ? ':white_check_mark:' : summary.status === 'canceled' ? ':no_entry_sign:' : ':x:'} ${summary.status}`},
|
||||||
|
{data: `${summary.numCachedSteps > 0 ? Math.round((summary.numCachedSteps / summary.numTotalSteps) * 100) : 0}%`},
|
||||||
|
{data: summary.duration}
|
||||||
|
]);
|
||||||
|
if (summary.error) {
|
||||||
|
summaryError = summary.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum.addTable([...summaryTableData]);
|
||||||
|
if (summaryError) {
|
||||||
|
sum.addHeading('Error', 4);
|
||||||
|
sum.addCodeBlock(summaryError, 'text');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.inputs) {
|
||||||
|
sum.addHeading('Build inputs', 2).addCodeBlock(
|
||||||
|
jsyaml.dump(opts.inputs, {
|
||||||
|
indent: 2,
|
||||||
|
lineWidth: -1
|
||||||
|
}),
|
||||||
|
'yaml'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.bakeDefinition) {
|
||||||
|
sum.addHeading('Bake definition', 2).addCodeBlock(JSON.stringify(opts.bakeDefinition, null, 2), 'json');
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Writing summary`);
|
||||||
|
await sum.addSeparator().write();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
import {components as OctoOpenApiTypes} from '@octokit/openapi-types';
|
import {components as OctoOpenApiTypes} from '@octokit/openapi-types';
|
||||||
import {JwtPayload} from 'jwt-decode';
|
import {JwtPayload} from 'jwt-decode';
|
||||||
|
|
||||||
|
import {BakeDefinition} from './bake';
|
||||||
|
import {ExportRecordResponse} from './history';
|
||||||
|
|
||||||
export interface GitHubRelease {
|
export interface GitHubRelease {
|
||||||
id: number;
|
id: number;
|
||||||
tag_name: string;
|
tag_name: string;
|
||||||
@ -47,3 +50,11 @@ export interface UploadArtifactResponse {
|
|||||||
size: number;
|
size: number;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BuildSummaryOpts {
|
||||||
|
exportRes: ExportRecordResponse;
|
||||||
|
uploadRes: UploadArtifactResponse;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
inputs?: any;
|
||||||
|
bakeDefinition?: BakeDefinition;
|
||||||
|
}
|
||||||
|
@ -22,7 +22,23 @@ export interface ExportRecordOpts {
|
|||||||
export interface ExportRecordResponse {
|
export interface ExportRecordResponse {
|
||||||
dockerbuildFilename: string;
|
dockerbuildFilename: string;
|
||||||
dockerbuildSize: number;
|
dockerbuildSize: number;
|
||||||
|
summaries: Summaries;
|
||||||
builderName: string;
|
builderName: string;
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
refs: Array<string>;
|
refs: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Summaries {
|
||||||
|
[ref: string]: RecordSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecordSummary {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
duration: string;
|
||||||
|
numCachedSteps: number;
|
||||||
|
numTotalSteps: number;
|
||||||
|
numCompletedSteps: number;
|
||||||
|
frontendAttrs: Record<string, string>;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
@ -1111,6 +1111,7 @@ __metadata:
|
|||||||
"@octokit/core": ^5.1.0
|
"@octokit/core": ^5.1.0
|
||||||
"@octokit/plugin-rest-endpoint-methods": ^10.4.0
|
"@octokit/plugin-rest-endpoint-methods": ^10.4.0
|
||||||
"@types/csv-parse": ^1.2.2
|
"@types/csv-parse": ^1.2.2
|
||||||
|
"@types/js-yaml": ^4.0.9
|
||||||
"@types/node": ^20.12.10
|
"@types/node": ^20.12.10
|
||||||
"@types/semver": ^7.5.8
|
"@types/semver": ^7.5.8
|
||||||
"@types/tmp": ^0.2.6
|
"@types/tmp": ^0.2.6
|
||||||
@ -1126,6 +1127,7 @@ __metadata:
|
|||||||
eslint-plugin-prettier: ^5.1.3
|
eslint-plugin-prettier: ^5.1.3
|
||||||
handlebars: ^4.7.8
|
handlebars: ^4.7.8
|
||||||
jest: ^29.7.0
|
jest: ^29.7.0
|
||||||
|
js-yaml: ^4.1.0
|
||||||
jwt-decode: ^4.0.0
|
jwt-decode: ^4.0.0
|
||||||
prettier: ^3.2.5
|
prettier: ^3.2.5
|
||||||
rimraf: ^5.0.5
|
rimraf: ^5.0.5
|
||||||
@ -2185,6 +2187,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/js-yaml@npm:^4.0.9":
|
||||||
|
version: 4.0.9
|
||||||
|
resolution: "@types/js-yaml@npm:4.0.9"
|
||||||
|
checksum: e5e5e49b5789a29fdb1f7d204f82de11cb9e8f6cb24ab064c616da5d6e1b3ccfbf95aa5d1498a9fbd3b9e745564e69b4a20b6c530b5a8bbb2d4eb830cda9bc69
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/json-schema@npm:^7.0.15":
|
"@types/json-schema@npm:^7.0.15":
|
||||||
version: 7.0.15
|
version: 7.0.15
|
||||||
resolution: "@types/json-schema@npm:7.0.15"
|
resolution: "@types/json-schema@npm:7.0.15"
|
||||||
|
Loading…
Reference in New Issue
Block a user