Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"js-yaml": "^5.4.1",
"jwt-decode": "^4.0.0",
"semver": "^7.8.5",
"tar-stream": "^3.2.0",
"tar-stream": "^3.2.1",
"tmp": "^0.2.7"
},
"devDependencies": {
Expand All @@ -71,7 +71,6 @@
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.11.0",
"@types/semver": "^7.7.1",
"@types/tar-stream": "^3.1.4",
"@types/tmp": "^0.2.6",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
Expand Down
107 changes: 51 additions & 56 deletions src/oci/oci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,66 +75,61 @@ export class OCI {
};
}

public static loadArchive(opts: LoadArchiveOpts): Promise<Archive> {
return new Promise<Archive>((resolve, reject) => {
const tarex: tar.Extract = tar.extract();

let rootIndex: Index;
let rootLayout: ImageLayout;
const indexes: Record<string, Index> = {};
const manifests: Record<string, Manifest> = {};
const images: Record<string, Image> = {};
const blobs: Record<string, unknown> = {};

tarex.on('entry', async (header, stream, next) => {
if (header.type === 'file') {
const filename = path.normalize(header.name);
if (filename === IMAGE_INDEX_FILE_V1) {
rootIndex = await OCI.streamToJson<Index>(stream);
} else if (filename === IMAGE_LAYOUT_FILE_V1) {
rootLayout = await OCI.streamToJson<ImageLayout>(stream);
} else if (filename.startsWith(path.join(IMAGE_BLOBS_DIR_V1, path.sep))) {
const blob = await OCI.extractBlob(stream);
const digest = `${filename.split(path.sep)[1]}:${filename.split(path.sep)[filename.split(path.sep).length - 1]}`;
if (OCI.isIndex(blob)) {
indexes[digest] = <Index>JSON.parse(blob);
} else if (OCI.isManifest(blob)) {
manifests[digest] = <Manifest>JSON.parse(blob);
} else if (OCI.isImage(blob)) {
images[digest] = <Image>JSON.parse(blob);
} else {
blobs[digest] = blob;
}
public static async loadArchive(opts: LoadArchiveOpts): Promise<Archive> {
const tarex = tar.extract();

let rootIndex: Index | undefined;
let rootLayout: ImageLayout | undefined;
const indexes: Record<string, Index> = {};
const manifests: Record<string, Manifest> = {};
const images: Record<string, Image> = {};
const blobs: Record<string, unknown> = {};

fs.createReadStream(opts.file)
.pipe(gunzip())
.pipe(tarex as unknown as NodeJS.WritableStream);

for await (const entry of tarex) {
const header = entry.header;
const stream = Readable.from(entry as unknown as AsyncIterable<Buffer>);
if (header.type === 'file') {
const filename = path.normalize(header.name);
if (filename === IMAGE_INDEX_FILE_V1) {
rootIndex = await OCI.streamToJson<Index>(stream);
} else if (filename === IMAGE_LAYOUT_FILE_V1) {
rootLayout = await OCI.streamToJson<ImageLayout>(stream);
} else if (filename.startsWith(path.join(IMAGE_BLOBS_DIR_V1, path.sep))) {
const blob = await OCI.extractBlob(stream);
const digest = `${filename.split(path.sep)[1]}:${filename.split(path.sep)[filename.split(path.sep).length - 1]}`;
if (OCI.isIndex(blob)) {
indexes[digest] = <Index>JSON.parse(blob);
} else if (OCI.isManifest(blob)) {
manifests[digest] = <Manifest>JSON.parse(blob);
} else if (OCI.isImage(blob)) {
images[digest] = <Image>JSON.parse(blob);
} else {
reject(new Error(`Invalid OCI archive: unexpected file ${filename}`));
blobs[digest] = blob;
}
} else {
throw new Error(`Invalid OCI archive: unexpected file ${filename}`);
}
stream.resume();
next();
});

tarex.on('finish', () => {
if (!rootIndex || !rootLayout) {
reject(new Error('Invalid OCI archive: missing index or layout'));
}
resolve({
root: {
index: rootIndex,
layout: rootLayout
},
indexes: indexes,
manifests: manifests,
images: images,
blobs: blobs
} as Archive);
});

tarex.on('error', error => {
reject(error);
});
}
stream.resume();
}
if (!rootIndex || !rootLayout) {
throw new Error('Invalid OCI archive: missing index or layout');
}

fs.createReadStream(opts.file).pipe(gunzip()).pipe(tarex);
});
return {
root: {
index: rootIndex,
layout: rootLayout
},
indexes: indexes,
manifests: manifests,
images: images,
blobs: blobs
} as Archive;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
20 changes: 5 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,6 @@ __metadata:
"@types/js-yaml": "npm:^4.0.9"
"@types/node": "npm:^24.11.0"
"@types/semver": "npm:^7.7.1"
"@types/tar-stream": "npm:^3.1.4"
"@types/tmp": "npm:^0.2.6"
"@typescript-eslint/eslint-plugin": "npm:^8.56.1"
"@typescript-eslint/parser": "npm:^8.56.1"
Expand All @@ -496,7 +495,7 @@ __metadata:
prettier: "npm:^3.8.1"
rimraf: "npm:^6.1.3"
semver: "npm:^7.8.5"
tar-stream: "npm:^3.2.0"
tar-stream: "npm:^3.2.1"
tmp: "npm:^0.2.7"
typescript: "npm:^5.9.3"
vitest: "npm:^4.0.18"
Expand Down Expand Up @@ -1434,15 +1433,6 @@ __metadata:
languageName: node
linkType: hard

"@types/tar-stream@npm:^3.1.4":
version: 3.1.4
resolution: "@types/tar-stream@npm:3.1.4"
dependencies:
"@types/node": "npm:*"
checksum: 10/9d1e182315c2189e18293b5a884edb9803c70772d37914870fbdb0bd659970e0d8effe002b19415ee251c422e3f169013668f3acf41feb9371040d0f0867d7b7
languageName: node
linkType: hard

"@types/tmp@npm:^0.2.6":
version: 0.2.6
resolution: "@types/tmp@npm:0.2.6"
Expand Down Expand Up @@ -4587,15 +4577,15 @@ __metadata:
languageName: node
linkType: hard

"tar-stream@npm:^3.2.0":
version: 3.2.0
resolution: "tar-stream@npm:3.2.0"
"tar-stream@npm:^3.2.1":
version: 3.2.1
resolution: "tar-stream@npm:3.2.1"
dependencies:
b4a: "npm:^1.6.4"
bare-fs: "npm:^4.5.5"
fast-fifo: "npm:^1.2.0"
streamx: "npm:^2.15.0"
checksum: 10/ce57a81521de73ae7a3b7d55a08da50d6771427c249bfa89a208518e48faf5254c8fa7201a8f5419ab8bde9601a74e6dd512b31a13ec89774aec96178f99a8d3
checksum: 10/1a09f857e0789489476d99fb716fa6d209e7d19aa73b7a4b23f40eeba46792d5007ca62fde4fca8f643ed71ace1c526a293c655718a0409275a45a8aa86b562a
languageName: node
linkType: hard

Expand Down
Loading