Skip to content

Commit

Permalink
Add support for NPM
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinadam committed Dec 29, 2024
1 parent 86c17e1 commit f1abc9e
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 37 deletions.
44 changes: 26 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
name: CI

on:
push:
branches:
- main

jobs:
publish:
ci:
name: CI
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Deno
uses: denoland/setup-deno@v1
uses: denoland/setup-deno@v2
with:
deno-version: v1.x

deno-version: v2.x
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
registry-url: 'https://registry.npmjs.org'
- name: Format
run: deno fmt --check

- name: Lint
run: deno lint

- name: Check
run: deno task check

- name: Publish to JSR
run: deno publish
- name: Type-check
run: deno check --frozen **/*.ts
- name: Run tests
run: deno test --permit-no-files
- name: Generate jsr.json
run: deno run --allow-read=. --allow-run=deno scripts/generate-package-manifest.ts --type=jsr | tee jsr.json
- name: Dry run publish to JSR
run: deno publish --config=jsr.json --dry-run
- name: Generate package.json
run: deno run --allow-read=. --allow-run=deno scripts/generate-package-manifest.ts --type=npm | tee package.json
- name: Install dependencies
run: deno install
- name: Build
run: deno task --eval "tsc -b"
- name: Dry run publish to NPM
run: npm publish --dry-run
50 changes: 50 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Publish
on:
release:
types: [published]
jobs:
publish-jsr:
name: Publish to JSR
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Generate jsr.json
run: deno run --allow-read=. --allow-run=deno scripts/generate-package-manifest.ts --type=jsr | tee jsr.json
- name: Publish to JSR
run: deno publish --config=jsr.json
publish-npm:
name: Publish to NPM
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
registry-url: 'https://registry.npmjs.org'
- name: Generate package.json
run: deno run --allow-read=. --allow-run=deno scripts/generate-package-manifest.ts --type=npm | tee package.json
- name: Install dependencies
run: deno install
- name: Build
run: deno task --eval "tsc -b"
- name: Publish to NPM
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
deno.lock
dist
jsr.json
package.json
node_modules
tsconfig.tsbuildinfo
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# zip
# @quentinadam/zip

[![JSR](https://jsr.io/badges/@quentinadam/zip)](https://jsr.io/@quentinadam/zip)
[![CI](https://github.com/quentinadam/deno-zip/actions/workflows/ci.yml/badge.svg)](https://github.com/quentinadam/deno-zip/actions/workflows/ci.yml)
[![JSR][jsr-image]][jsr-url] [![NPM][npm-image]][npm-url] [![CI][ci-image]][ci-url]

A library for creating and extracting ZIP archives.

Expand All @@ -21,3 +20,10 @@ for (const { name, data } of await zip.extract(buffer)) {
console.log(name, new TextDecoder().decode(data));
}
```

[ci-image]: https://img.shields.io/github/actions/workflow/status/quentinadam/deno-zip/ci.yml?branch=main&logo=github&style=flat-square
[ci-url]: https://github.com/quentinadam/deno-zip/actions/workflows/ci.yml
[npm-image]: https://img.shields.io/npm/v/@quentinadam/zip.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@quentinadam/zip
[jsr-image]: https://jsr.io/badges/@quentinadam/zip?style=flat-square
[jsr-url]: https://jsr.io/@quentinadam/zip
20 changes: 9 additions & 11 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
{
"name": "@quentinadam/zip",
"version": "0.1.3",
"description": "A library for creating and extracting ZIP archives",
"license": "MIT",
"exports": "./zip.ts",
"exports": "./src/zip.ts",
"imports": {
"@quentinadam/assert": "jsr:@quentinadam/assert@^0.1.7",
"@quentinadam/uint8array-extension": "jsr:@quentinadam/uint8array-extension@^0.1.4"
},
"publish": {
"exclude": [
".github/",
".vscode/"
]
"@quentinadam/assert": "jsr:@quentinadam/assert@^0.1.10",
"@quentinadam/require": "jsr:@quentinadam/require@^0.1.4",
"@quentinadam/uint8array-extension": "jsr:@quentinadam/uint8array-extension@^0.1.5",
"typescript": "npm:typescript@^5.7.2"
},
"fmt": {
"singleQuote": true,
"lineWidth": 120
},
"tasks": {
"check": "deno check **/*.ts"
"compilerOptions": {
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true
}
}
39 changes: 39 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

154 changes: 154 additions & 0 deletions scripts/generate-package-manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
type Graph = {
version: number;
roots: string[];
modules: ({ kind: 'external'; specifier: string } | {
kind: 'esm';
dependencies?: { specifier: string; code?: { specifier: string } }[];
specifier: string;
})[];
};

function assert(value: boolean): asserts value {
if (value !== true) {
throw new Error('Assertion failed');
}
}

function require<T>(value: T | undefined | null): T {
assert(value !== undefined && value !== null);
return value;
}

function parsePackageSpecifier(specifier: string) {
// deno-fmt-ignore
const regex = /^(:?(?<registry>jsr|npm):\/?)?(?<name>(?:@[a-zA-Z0-9_\-]+\/)?[a-zA-Z0-9_\-]+)(?:@(?<version>(?:\*|(?:\^|~|[<>]=?)?\d+(?:\.\d+)*)))?(?<path>(\/[^\/]+)+)?$/;
const match = specifier.match(regex);
if (match !== null) {
const groups = require(match.groups);
const registry = groups.registry;
const name = require(groups.name);
const version = groups.version;
const path = groups.path;
return { registry, name, version, path };
}
return undefined;
}

class GraphAnalyzer {
readonly #graph: Graph;
readonly #specifiers: Set<string>;

constructor(graph: Graph, specifiers: Set<string>) {
this.#graph = graph;
this.#specifiers = specifiers;
}

analyze(specifier: string) {
const module = require(this.#graph.modules.find((module) => module.specifier === specifier));
if (module.kind === 'esm' && module.dependencies !== undefined) {
for (const dependency of module.dependencies) {
const parsedPackageSpecifier = parsePackageSpecifier(dependency.specifier);
if (parsedPackageSpecifier !== undefined) {
this.#specifiers.add(parsedPackageSpecifier.name);
} else if (dependency.code !== undefined) {
this.analyze(dependency.code.specifier);
}
}
}
}
}

export default async function getExportsDependencies() {
const exports = require(configurationFile.exports);
const exportedPaths = (typeof exports === 'string') ? [exports] : Object.values(exports);
const specifiers = new Set<string>();
for (const path of exportedPaths) {
const command = new Deno.Command('deno', { args: ['info', '--json', path] });
const { code, stdout, stderr } = await command.output();
if (code !== 0) {
throw new Error(new TextDecoder().decode(stderr));
}
const graph = JSON.parse(new TextDecoder().decode(stdout)) as Graph;
const analyzer = new GraphAnalyzer(graph, specifiers);
analyzer.analyze(require(graph.roots[0]));
}
if (specifiers.size > 0) {
const imports = configurationFile.imports as Record<string, string> | undefined;
assert(imports !== undefined);
return Array.from(specifiers).toSorted().map((specifier) => {
const parsedPackageSpecifier = require(parsePackageSpecifier(require(imports[specifier])));
const { registry, name, version } = parsedPackageSpecifier;
assert(registry !== undefined);
assert(version !== undefined);
return { registry, name, version };
});
}
return [];
}

type ConfigurationFile = {
name: string;
version: string;
description: string;
license: string;
exports?: string | Record<string, string>;
imports?: Record<string, string>;
};

const type = (() => {
try {
return require(require(require(Deno.args[0]).match(/^--type=(jsr|npm)$/))[1]);
} catch {
throw new Error('Missing or invalid type argument');
}
})();

const configurationFile: ConfigurationFile = JSON.parse(Deno.readTextFileSync('deno.json'));

const dependencies = await getExportsDependencies();

const scopedName = require(configurationFile.name);
const name = require(scopedName.split('/')[1]);

const manifest = (() => {
if (type === 'jsr') {
return {
name: scopedName,
version: require(configurationFile.version),
license: require(configurationFile.license),
exports: require(configurationFile.exports),
publish: { include: ['src', 'README.md'], exclude: ['**/*.test.ts'] },
imports: dependencies.length > 0
? Object.fromEntries(dependencies.map(({ name, registry, version }) => {
return [name, `${registry}:${name}@${version}`];
}))
: undefined,
};
}
if (type === 'npm') {
return {
name: scopedName,
version: require(configurationFile.version),
description: require(configurationFile.description),
license: require(configurationFile.license),
author: 'Quentin Adam',
repository: { type: 'git', url: `git+https://github.com/quentinadam/deno-${name}.git` },
type: 'module',
exports: ((exports) => {
const replaceFn = (path: string) => path.replace(/^\.\/src\//, './dist/').replace(/\.ts$/, '.js');
if (typeof exports === 'string') {
return replaceFn(exports);
} else {
return Object.fromEntries(Object.entries(exports).map(([key, value]) => [key, replaceFn(value)]));
}
})(require(configurationFile.exports)),
files: ['dist', 'README.md'],
dependencies: dependencies.length > 0
? Object.fromEntries(dependencies.map(({ name, version }) => [name, version]))
: undefined,
};
}
throw new Error(`Invalid type ${type}`);
})();

console.log(JSON.stringify(manifest, null, 2));
6 changes: 4 additions & 2 deletions crc32.ts → src/crc32.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import require from '@quentinadam/require';

const POLYNOMIAL = -306674912;

const TABLE = /* @__PURE__ */ (() => {
Expand All @@ -13,8 +15,8 @@ const TABLE = /* @__PURE__ */ (() => {
})();

export default function crc32(buffer: Uint8Array, crc = 0xFFFFFFFF) {
for (let i = 0; i < buffer.length; ++i) {
crc = TABLE[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8);
for (const byte of buffer) {
crc = require(TABLE[(crc ^ byte) & 0xff]) ^ (crc >>> 8);
}
return (crc ^ -1) >>> 0;
}
2 changes: 1 addition & 1 deletion deflate.ts → src/deflate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Uint8ArrayExtension from '@quentinadam/uint8array-extension';
import * as Uint8ArrayExtension from '@quentinadam/uint8array-extension';

async function transform(stream: TransformStream<Uint8Array, Uint8Array>, data: Uint8Array) {
const writer = stream.writable.getWriter();
Expand Down
2 changes: 1 addition & 1 deletion zip.ts → src/zip.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Uint8ArrayExtension from '@quentinadam/uint8array-extension';
import assert from '@quentinadam/assert';
import Uint8ArrayExtension from '@quentinadam/uint8array-extension';
import crc32 from './crc32.ts';
import { compress, decompress } from './deflate.ts';

Expand Down
Loading

0 comments on commit f1abc9e

Please sign in to comment.