diff --git a/.config/eslint.cjs b/.config/eslint.cjs index 7320893e..2a1ea066 100644 --- a/.config/eslint.cjs +++ b/.config/eslint.cjs @@ -285,6 +285,12 @@ module.exports = { 'no-restricted-imports': 'off', 'import/no-relative-packages': 'off', // common-internals is symlinked from node } + }, + { + files: ['e2e/deno/**'], + rules: { + 'import/no-unresolved': 'off', + } } ], settings: { diff --git a/.dockerignore b/.dockerignore index 7224b67d..83410924 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ **/node_modules **/private -**/dist \ No newline at end of file +**/dist +/e2e \ No newline at end of file diff --git a/e2e/deno/.dockerignore b/e2e/deno/.dockerignore new file mode 100644 index 00000000..a1de89c1 --- /dev/null +++ b/e2e/deno/.dockerignore @@ -0,0 +1,3 @@ +/.jsr-data +/dist +/deno.lock \ No newline at end of file diff --git a/e2e/deno/.env.example b/e2e/deno/.env.example new file mode 100644 index 00000000..20a2ee7d --- /dev/null +++ b/e2e/deno/.env.example @@ -0,0 +1,5 @@ +# obtain these values from my.telegram.org +API_ID= +API_HASH= + +GITHUB_TOKEN= \ No newline at end of file diff --git a/e2e/deno/.gitignore b/e2e/deno/.gitignore new file mode 100644 index 00000000..75a15d22 --- /dev/null +++ b/e2e/deno/.gitignore @@ -0,0 +1,3 @@ +/.jsr-data +.env +/deno.lock \ No newline at end of file diff --git a/e2e/deno/Dockerfile.build b/e2e/deno/Dockerfile.build new file mode 100644 index 00000000..6ebd1978 --- /dev/null +++ b/e2e/deno/Dockerfile.build @@ -0,0 +1,26 @@ +FROM denoland/deno:bin-1.42.4 as deno-bin + +FROM node:20 +WORKDIR /app + +COPY --from=deno-bin /deno /bin/deno +# todo: remove once 1.42.5 is out +RUN deno upgrade --canary --version=2f5a6a8514ad8eadce1a0a9f1a7a419692e337ef + +RUN corepack enable && \ + corepack prepare pnpm@8.7.1 --activate + +COPY ../.. /app/ + +RUN pnpm install --frozen-lockfile && \ + pnpm -C packages/tl run gen-code + +RUN apt update && apt install -y socat + +ENV REGISTRY="http://jsr/" +ENV E2E="1" +ENV JSR="1" +ENV JSR_TOKEN="token" + +ENTRYPOINT [ "node", "/app/scripts/publish.js" ] +CMD [ "all" ] \ No newline at end of file diff --git a/e2e/deno/Dockerfile.jsr b/e2e/deno/Dockerfile.jsr new file mode 100644 index 00000000..170237fb --- /dev/null +++ b/e2e/deno/Dockerfile.jsr @@ -0,0 +1,3 @@ +FROM ghcr.io/teidesu/jsr-api:latest + +RUN apt update && apt install -y curl \ No newline at end of file diff --git a/e2e/deno/Dockerfile.test b/e2e/deno/Dockerfile.test new file mode 100644 index 00000000..f5a3e594 --- /dev/null +++ b/e2e/deno/Dockerfile.test @@ -0,0 +1,10 @@ +FROM denoland/deno:1.42.4 +WORKDIR /app + +RUN apt update && apt install -y socat + +COPY ./ /app/ + +ENV DOCKER="1" + +ENTRYPOINT [ "./cli.sh", "run" ] \ No newline at end of file diff --git a/e2e/deno/README.md b/e2e/deno/README.md new file mode 100644 index 00000000..5b579dc9 --- /dev/null +++ b/e2e/deno/README.md @@ -0,0 +1,30 @@ +# mtcute e2e tests (Deno edition) + +This directory contains end-to-end tests for mtcute under Deno. + +They are made for 2 purposes: + - Ensure published packages work as expected and can properly be imported + - Ensure that the library works with the actual Telegram API + +To achieve the first goal, we use a local JSR instance container where we publish the package, +and then install it from there in another container + +## Setting up + +Before running the tests, you need to copy `.env.example` to `.env` and fill in the values + +## Running tests + +```bash +# first start a local jsr instance +./cli.sh start + +# push all packages to the local registry +./cli.sh update +# pushing a particular package is not supported due to jsr limitations + +# run the tests +./cli.sh run +# or in docker +./cli.sh run-docker +``` \ No newline at end of file diff --git a/e2e/deno/cli.sh b/e2e/deno/cli.sh new file mode 100755 index 00000000..7a9d1414 --- /dev/null +++ b/e2e/deno/cli.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +set -eau + +method=$1 +shift + +case "$method" in + "start") + docker compose up -d --wait jsr + node ./init-server.js + ;; + "update") + # unpublish all packages + rm -rf .jsr-data/gcs/modules/@mtcute/* + docker compose exec jsr-db psql registry -U user -c "delete from publishing_tasks;" + docker compose exec jsr-db psql registry -U user -c "delete from package_files;" + docker compose exec jsr-db psql registry -U user -c "delete from npm_tarballs;" + docker compose exec jsr-db psql registry -U user -c "delete from package_version_dependencies;" + docker compose exec jsr-db psql registry -U user -c "delete from package_versions;" + docker compose exec jsr-db psql registry -U user -c "delete from packages;" + + # publish all packages + docker compose run --rm --build build all + + # clear cache + rm -rf $(deno info --json | jq .denoDir -r)/deps + rm deno.lock + ;; + "clean") + docker compose down + rm -rf .jsr-data + ;; + "stop") + docker compose down + ;; + "run") + source .env + + if [ -n "$DOCKER" ]; then + # running behind a socat proxy seems to fix some of the docker networking issues (thx kamillaova) + socat TCP-LISTEN:4873,fork,reuseaddr TCP4:jsr:80 & + socat_pid=$! + + trap "kill $socat_pid" EXIT + fi + + export JSR_URL=http://localhost:4873 + if [ -z "$@" ]; then + deno test -A tests/**/*.ts + else + deno test -A $@ + fi + ;; + "run-docker") + source .env + docker compose run --rm --build test $@ + ;; + *) + echo "Unknown command" + ;; +esac \ No newline at end of file diff --git a/e2e/deno/deno.json b/e2e/deno/deno.json new file mode 100644 index 00000000..f1651f62 --- /dev/null +++ b/e2e/deno/deno.json @@ -0,0 +1,9 @@ +{ + "imports": { + "@mtcute/web": "jsr:@mtcute/web@*", + "@mtcute/wasm": "jsr:@mtcute/wasm@*", + "@mtcute/tl": "jsr:@mtcute/tl@*", + "@mtcute/tl-runtime": "jsr:@mtcute/tl-runtime@*", + "@mtcute/core": "jsr:@mtcute/core@*" + } +} \ No newline at end of file diff --git a/e2e/deno/docker-compose.yaml b/e2e/deno/docker-compose.yaml new file mode 100644 index 00000000..80cc9395 --- /dev/null +++ b/e2e/deno/docker-compose.yaml @@ -0,0 +1,78 @@ +version: "3" +services: + # jsr (based on https://github.com/teidesu/docker-images/blob/main/jsr/docker-compose.yaml) + jsr-db: + image: postgres:15 + command: postgres -c 'max_connections=1000' + restart: always + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: registry + healthcheck: + test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" + interval: 5s + retries: 20 + start_period: 5s + volumes: + - ./.jsr-data/db:/var/lib/postgresql/data + jsr-gcs: + image: fsouza/fake-gcs-server:latest + command: -scheme http -filesystem-root=/gcs-data -port 4080 + volumes: + - ./.jsr-data/gcs:/gcs-data + jsr-api: + depends_on: + jsr-db: + condition: service_healthy + jsr-gcs: + condition: service_started + healthcheck: + test: "curl --fail http://localhost:8001/sitemap.xml || exit 1" + interval: 5s + retries: 20 + start_period: 5s + build: + context: . + dockerfile: Dockerfile.jsr + environment: + - "DATABASE_URL=postgres://user:password@jsr-db/registry" + - "GITHUB_CLIENT_ID=fake" + - "GITHUB_CLIENT_SECRET=fake" + - "GCS_ENDPOINT=http://jsr-gcs:4080" + - "MODULES_BUCKET=modules" + - "PUBLISHING_BUCKET=publishing" + - "DOCS_BUCKET=docs" + - "NPM_BUCKET=npm" + - "REGISTRY_URL=http://localhost:4873" + - "NPM_URL=http://example.com/unused" + jsr: + depends_on: + jsr-api: + condition: service_healthy + image: nginx:1.21 + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + ports: + - "4873:80" + + # our stuff + build: + build: + context: ../.. + dockerfile: e2e/deno/Dockerfile.build + environment: + - GITHUB_TOKEN=${GITHUB_TOKEN} + depends_on: + - jsr + test: + build: + context: . + dockerfile: Dockerfile.test + environment: + - API_ID=${API_ID} + - API_HASH=${API_HASH} + depends_on: + - jsr +networks: + mtcute-e2e: {} \ No newline at end of file diff --git a/e2e/deno/init-server.js b/e2e/deno/init-server.js new file mode 100644 index 00000000..429a2e9e --- /dev/null +++ b/e2e/deno/init-server.js @@ -0,0 +1,67 @@ +/* eslint-disable no-console */ +const { execSync } = require('child_process') + +function getDockerContainerIp(name) { + const containerId = execSync(`docker compose ps -q ${name}`).toString().trim() + const ip = execSync(`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${containerId}`) + .toString() + .trim() + + return ip +} + +for (const stmt of [ + "delete from tokens where user_id = '00000000-0000-0000-0000-000000000000';", + "insert into tokens (hash, user_id, type, expires_at) values ('3c469e9d6c5875d37a43f353d4f88e61fcf812c66eee3457465a40b0da4153e0', '00000000-0000-0000-0000-000000000000', 'web', current_date + interval '100' year);", + "update users set is_staff = true, scope_limit = 99999 where id = '00000000-0000-0000-0000-000000000000';", +]) { + execSync(`docker compose exec jsr-db psql registry -U user -c "${stmt}"`) +} + +console.log('[i] Initialized database') + +const GCS_URL = `http://${getDockerContainerIp('jsr-gcs')}:4080/` +const API_URL = `http://${getDockerContainerIp('jsr-api')}:8001/` + +async function createBucket(name) { + try { + const resp = await fetch(`${GCS_URL}storage/v1/b`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }), + }) + + await resp.text() + + return resp.ok || resp.status === 409 + } catch (e) { + console.log(e) + + return false + } +} + +(async () => { + for (const bucket of ['modules', 'docs', 'publishing', 'npm']) { + const ok = await createBucket(bucket) + console.log(`[i] Created bucket ${bucket}: ${ok}`) + } + + // create @mtcute scope if it doesn't exist + const resp = await fetch(`${API_URL}api/scopes`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Cookie: 'token=token', + }, + body: JSON.stringify({ scope: 'mtcute' }), + }) + + if (resp.status !== 200 && resp.status !== 409) { + throw new Error(`Failed to create scope: ${resp.statusText} ${await resp.text()}`) + } + + if (resp.status === 200) { + console.log('[i] Created scope mtcute') + } +})() diff --git a/e2e/deno/nginx.conf b/e2e/deno/nginx.conf new file mode 100644 index 00000000..846ef740 --- /dev/null +++ b/e2e/deno/nginx.conf @@ -0,0 +1,29 @@ +events {} + +http { + upstream gcs { + server jsr-gcs:4080; + } + + upstream api { + server jsr-api:8001; + } + + error_log /error.log debug; + + server { + listen 80; + + location ~ ^/(@.*)$ { + proxy_pass http://gcs/storage/v1/b/modules/o/$1?alt=media; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location / { + proxy_pass http://api; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } +} \ No newline at end of file diff --git a/e2e/deno/tests/packaging/base-client.ts b/e2e/deno/tests/packaging/base-client.ts new file mode 100644 index 00000000..b752892a --- /dev/null +++ b/e2e/deno/tests/packaging/base-client.ts @@ -0,0 +1,21 @@ +import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts' + +import { BaseTelegramClient } from '@mtcute/core/client.js' + +import { getApiParams } from '../../utils.ts' + +Deno.test('@mtcute/core', async (t) => { + await t.step('connects to test DC and makes help.getNearestDc', async () => { + const tg = new BaseTelegramClient({ + ...getApiParams(), + }) + + await tg.connect() + const config = await tg.call({ _: 'help.getNearestDc' }) + await tg.close() + + assertEquals(typeof config, 'object') + assertEquals(config._, 'nearestDc') + assertEquals(config.thisDc, 2) + }) +}) diff --git a/e2e/deno/tests/packaging/tl-runtime.ts b/e2e/deno/tests/packaging/tl-runtime.ts new file mode 100644 index 00000000..b00dbeb0 --- /dev/null +++ b/e2e/deno/tests/packaging/tl-runtime.ts @@ -0,0 +1,73 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts' + +import { Long } from '@mtcute/core' +import { setPlatform } from '@mtcute/core/platform.js' +import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime' +import { WebPlatform } from '@mtcute/web' + +// here we primarily want to check that everything imports properly, +// and that the code is actually executable. The actual correctness +// of the implementation is covered tested by unit tests + +const p = new WebPlatform() +setPlatform(p) + +Deno.test('encodings', () => { + assertEquals(p.hexEncode(new Uint8Array([1, 2, 3, 4, 5])), '0102030405') +}) + +Deno.test('TlBinaryReader', () => { + const map = { + '85337187': function (r: any) { + const ret: any = {} + ret._ = 'mt_resPQ' + ret.nonce = r.int128() + ret.serverNonce = r.int128() + ret.pq = r.bytes() + ret.serverPublicKeyFingerprints = r.vector(r.long) + + return ret + }, + } + const data = + '000000000000000001c8831ec97ae55140000000632416053e0549828cca27e966b301a48fece2fca5cf4d33f4a11ea877ba4aa5739073300817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3' + const buf = p.hexDecode(data) + + const r = new TlBinaryReader(map, buf, 8) + + assertEquals(r.long().toString(16), '51e57ac91e83c801') + assertEquals(r.uint(), 64) + + const obj: any = r.object() + assertEquals(obj._, 'mt_resPQ') +}) + +Deno.test('TlBinaryWriter', () => { + const map = { + mt_resPQ: function (w: any, obj: any) { + w.uint(85337187) + w.bytes(obj.pq) + w.vector(w.long, obj.serverPublicKeyFingerprints) + }, + _staticSize: {} as any, + } + + const obj = { + _: 'mt_resPQ', + pq: p.hexDecode('17ED48941A08F981'), + serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)], + } + + assertEquals(TlSerializationCounter.countNeededBytes(map, obj), 32) + + const w = TlBinaryWriter.alloc(map, 48) + w.long(Long.ZERO) + w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId + w.object(obj) + + assertEquals( + p.hexEncode(w.result()), + '000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3', + ) +}) diff --git a/e2e/deno/tests/packaging/tl-schema.ts b/e2e/deno/tests/packaging/tl-schema.ts new file mode 100644 index 00000000..5514729e --- /dev/null +++ b/e2e/deno/tests/packaging/tl-schema.ts @@ -0,0 +1,43 @@ +import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts' + +import { Long } from '@mtcute/core' +import { setPlatform } from '@mtcute/core/platform.js' +import { tl } from '@mtcute/tl' +import { __tlReaderMap } from '@mtcute/tl/binary/reader.js' +import { __tlWriterMap } from '@mtcute/tl/binary/writer.js' +import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime' +import { WebPlatform } from '@mtcute/web' + +// here we primarily want to check that @mtcute/tl correctly works with @mtcute/tl-runtime + +const p = new WebPlatform() +setPlatform(p) + +Deno.test('@mtcute/tl', async (t) => { + await t.step('writers map works with TlBinaryWriter', () => { + const obj = { + _: 'inputPeerUser', + userId: 123, + accessHash: Long.fromNumber(456), + } + + assertEquals( + p.hexEncode(TlBinaryWriter.serializeObject(__tlWriterMap, obj)), + '4ca5e8dd7b00000000000000c801000000000000', + ) + }) + + await t.step('readers map works with TlBinaryReader', () => { + const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000') + // eslint-disable-next-line + const obj = TlBinaryReader.deserializeObject(__tlReaderMap, buf) + + assertEquals(obj._, 'inputPeerUser') + assertEquals(obj.userId, 123) + assertEquals(obj.accessHash.toString(), '456') + }) + + await t.step('correctly checks for combinator types', () => { + assertEquals(tl.isAnyInputUser({ _: 'inputUserEmpty' }), true) + }) +}) diff --git a/e2e/deno/tests/packaging/wasm.ts b/e2e/deno/tests/packaging/wasm.ts new file mode 100644 index 00000000..865f670e --- /dev/null +++ b/e2e/deno/tests/packaging/wasm.ts @@ -0,0 +1,25 @@ +import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts' + +import { ige256Decrypt, ige256Encrypt } from '@mtcute/wasm' +import { WebCryptoProvider, WebPlatform } from '@mtcute/web' + +await new WebCryptoProvider().initialize() +const platform = new WebPlatform() + +Deno.test('@mtcute/wasm', async (t) => { + const key = platform.hexDecode('5468697320697320616E20696D706C655468697320697320616E20696D706C65') + const iv = platform.hexDecode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353') + + const data = platform.hexDecode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b') + const dataEnc = platform.hexDecode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69') + + await t.step('should work with Buffers', () => { + assertEquals(ige256Encrypt(data, key, iv), dataEnc) + assertEquals(ige256Decrypt(dataEnc, key, iv), data) + }) + + await t.step('should work with Uint8Arrays', () => { + assertEquals(ige256Encrypt(data, key, iv), dataEnc) + assertEquals(ige256Decrypt(dataEnc, key, iv), data) + }) +}) diff --git a/e2e/deno/utils.ts b/e2e/deno/utils.ts new file mode 100644 index 00000000..25711aa8 --- /dev/null +++ b/e2e/deno/utils.ts @@ -0,0 +1,42 @@ +import { MaybePromise, MemoryStorage } from '@mtcute/core' +import { setPlatform } from '@mtcute/core/platform.js' +import { LogManager, sleep } from '@mtcute/core/utils.js' +import { WebCryptoProvider, WebPlatform, WebSocketTransport } from '@mtcute/web' + +export const getApiParams = (storage?: string) => { + if (storage) throw new Error('unsupported yet') + + if (!Deno.env.has('API_ID') || !Deno.env.has('API_HASH')) { + throw new Error('API_ID and API_HASH env variables must be set') + } + + setPlatform(new WebPlatform()) + + return { + apiId: parseInt(Deno.env.get('API_ID')!), + apiHash: Deno.env.get('API_HASH')!, + testMode: true, + storage: new MemoryStorage(), + logLevel: LogManager.VERBOSE, + transport: () => new WebSocketTransport(), + crypto: new WebCryptoProvider(), + } +} + +export async function waitFor(condition: () => MaybePromise, timeout = 5000): Promise { + const start = Date.now() + let lastError + + while (Date.now() - start < timeout) { + try { + await condition() + + return + } catch (e) { + lastError = e + await sleep(100) + } + } + + throw lastError +} diff --git a/e2e/node/.env.example b/e2e/node/.env.example index 2a7641ed..20a2ee7d 100644 --- a/e2e/node/.env.example +++ b/e2e/node/.env.example @@ -1,3 +1,5 @@ # obtain these values from my.telegram.org API_ID= API_HASH= + +GITHUB_TOKEN= \ No newline at end of file diff --git a/packages/wasm/build.config.cjs b/packages/wasm/build.config.cjs index 0117d0e2..9f85a6bc 100644 --- a/packages/wasm/build.config.cjs +++ b/packages/wasm/build.config.cjs @@ -1,6 +1,10 @@ -module.exports = ({ path: { join }, fs, outDir, packageDir, jsr }) => ({ +module.exports = ({ path: { join }, fs, outDir, packageDir, jsr, transformFile }) => ({ esmOnlyDirectives: true, final() { fs.cpSync(join(packageDir, 'mtcute.wasm'), join(outDir, 'mtcute.wasm')) + + if (jsr) { + transformFile(join(outDir, 'index.ts'), (code) => code.replace("'../mtcute.wasm'", "'./mtcute.wasm'")) + } }, }) diff --git a/scripts/publish.js b/scripts/publish.js index 5736e76a..e07c75c2 100644 --- a/scripts/publish.js +++ b/scripts/publish.js @@ -4,8 +4,17 @@ const cp = require('child_process') const IS_JSR = process.env.JSR === '1' const MAIN_REGISTRY = IS_JSR ? 'http://jsr.test/' : 'https://registry.npmjs.org' -const REGISTRY = process.env.REGISTRY || MAIN_REGISTRY +let REGISTRY = process.env.REGISTRY || MAIN_REGISTRY exports.REGISTRY = REGISTRY +if (!REGISTRY.endsWith('/')) REGISTRY += '/' + +if (process.env.E2E && IS_JSR) { + // running behind a socat proxy seems to fix some of the docker networking issues (thx kamillaova) + const hostname = new URL(REGISTRY).hostname + const port = new URL(REGISTRY).port || { 'http:': 80, 'https:': 443 }[new URL(REGISTRY).protocol] + cp.spawn('bash', ['-c', `socat TCP-LISTEN:1234,fork,reuseaddr TCP4:${hostname}:${port}`], { stdio: 'ignore' }) + REGISTRY = 'http://localhost:1234/' +} if (IS_JSR) { // for the underlying tools that expect JSR_URL env var @@ -23,26 +32,49 @@ const JSR_EXCEPTIONS = { test: 'never', } -async function checkVersion(name, version, retry = 0) { +function fetchRetry(url, init, retry = 0) { + return fetch(url, init).catch((err) => { + if (retry >= 5) throw err + + // for whatever reason this request sometimes fails with ECONNRESET + // no idea why, probably some issue in docker networking + console.log('[i] Error fetching %s:', url) + console.log(err) + + return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => fetchRetry(url, init, retry + 1)) + }) +} + +async function checkVersion(name, version) { let registry = REGISTRY - if (!registry.endsWith('/')) registry += '/' const url = IS_JSR ? `${registry}@mtcute/${name}/${version}_meta.json` : `${registry}@mtcute/${name}/${version}` - return fetch(url) - .then((r) => r.status === 200) - .catch((err) => { - if (retry >= 5) throw err + return fetchRetry(url).then((r) => r.status === 200) +} - // for whatever reason this request sometimes fails with ECONNRESET - // no idea why, probably some issue in docker networking - console.log('[i] Error checking version:') - console.log(err) +async function jsrMaybeCreatePackage(name) { + // check if the package even exists + const packageMeta = await fetchRetry(`${REGISTRY}api/scopes/mtcute/packages/${name}`) - return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => - checkVersion(name, version, retry + 1), - ) + if (packageMeta.status === 404) { + console.error('[i] %s does not exist, creating..', name) + + const create = await fetchRetry(`${REGISTRY}api/scopes/mtcute/packages`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Cookie: `token=${process.env.JSR_TOKEN}`, + }, + body: JSON.stringify({ package: name }), }) + + if (create.status !== 200) { + throw new Error(`Failed to create package: ${create.statusText} ${await create.text()}`) + } + } else if (packageMeta.status !== 200) { + throw new Error(`Failed to check package: ${packageMeta.statusText} ${await packageMeta.text()}`) + } } async function publishSinglePackage(name) { @@ -76,11 +108,14 @@ async function publishSinglePackage(name) { return } + } else if (IS_JSR && process.env.JSR_TOKEN) { + await jsrMaybeCreatePackage(name) } if (IS_JSR) { // publish to jsr - cp.execSync('deno publish --allow-dirty', { + const params = process.env.JSR_TOKEN ? `--token ${process.env.JSR_TOKEN}` : '' + cp.execSync(`deno publish --allow-dirty ${params}`, { cwd: path.join(packageDir, 'dist/jsr'), stdio: 'inherit', }) @@ -158,6 +193,7 @@ async function main(arg = process.argv[2]) { } catch (e) { console.error('[!] Failed to publish %s:', pkg) console.error(e) + if (IS_JSR || process.env.E2E) throw e failedPkgs.push(pkg) } } @@ -169,6 +205,8 @@ async function main(arg = process.argv[2]) { } catch (e) { console.error('[!] Failed to publish %s:', pkg) console.error(e) + if (IS_JSR || process.env.E2E) throw e + failedPkgs.push(pkg) } }