Skip to content

Commit

Permalink
Merge branch 'main' into prisma
Browse files Browse the repository at this point in the history
  • Loading branch information
SferaDev authored Nov 28, 2023
2 parents 93d6c31 + 6c23d36 commit 841feab
Show file tree
Hide file tree
Showing 42 changed files with 610 additions and 342 deletions.
6 changes: 0 additions & 6 deletions .changeset/three-ears-arrive.md

This file was deleted.

1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
with:
title: Release tracking
publish: npx changeset publish
version: node ./scripts/changeset-version.mjs
env:
GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
15 changes: 15 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# @xata.io/cli

## 0.15.0

### Minor Changes

- [#1251](https://github.com/xataio/client-ts/pull/1251) [`e97d1999`](https://github.com/xataio/client-ts/commit/e97d1999f3c25f149213ceca81958e1674624e05) Thanks [@SferaDev](https://github.com/SferaDev)! - Remove Xata Workers support

### Patch Changes

- [#1212](https://github.com/xataio/client-ts/pull/1212) [`1348a7fa`](https://github.com/xataio/client-ts/commit/1348a7fa973d8ad008f925297a479f26d231efec) Thanks [@eemmiillyy](https://github.com/eemmiillyy)! - feat: compatibility endpoint

- Updated dependencies [[`e97d1999`](https://github.com/xataio/client-ts/commit/e97d1999f3c25f149213ceca81958e1674624e05)]:
- @xata.io/[email protected]
- @xata.io/[email protected]
- @xata.io/[email protected]

## 0.14.5

### Patch Changes
Expand Down
7 changes: 6 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xata.io/cli",
"version": "0.14.5",
"version": "0.15.0",
"description": "Xata.io CLI",
"author": "Xata Inc.",
"bin": {
Expand All @@ -25,6 +25,7 @@
"@oclif/plugin-plugins": "^4.1.8",
"@types/ini": "^1.3.33",
"@types/prompts": "^2.4.9",
"@types/semver": "^7.5.6",
"@xata.io/client": "workspace:*",
"@xata.io/codegen": "workspace:*",
"@xata.io/importer": "workspace:*",
Expand All @@ -45,6 +46,7 @@
"open": "^9.1.0",
"prompts": "^2.4.2",
"relaxed-json": "^1.0.3",
"semver": "^7.5.4",
"text-table": "^0.2.0",
"tslib": "^2.6.2",
"tmp": "^0.2.1",
Expand All @@ -71,6 +73,9 @@
},
"oclif": {
"bin": "xata",
"hooks": {
"init": "./dist/hooks/init/compatibility"
},
"dirname": "xata",
"commands": "./dist/commands",
"plugins": [
Expand Down
8 changes: 4 additions & 4 deletions cli/src/commands/init/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('xata init', () => {
}",
"package.json": "{\\"name\\":\\"test\\",\\"version\\":\\"1.0.0\\"}",
"readme.md": "",
"xataCustom.ts": "// Generated by Xata Codegen 0.27.0. Please do not edit.
"xataCustom.ts": "// Generated by Xata Codegen 0.28.0. Please do not edit.
import { buildClient } from \\"@xata.io/client\\";
import type {
BaseClientOptions,
Expand Down Expand Up @@ -242,7 +242,7 @@ describe('xata init', () => {
}
}",
"readme.md": "",
"xataCustom.ts": "// Generated by Xata Codegen 0.27.0. Please do not edit.
"xataCustom.ts": "// Generated by Xata Codegen 0.28.0. Please do not edit.
import { buildClient } from \\"@xata.io/client\\";
import type {
BaseClientOptions,
Expand Down Expand Up @@ -319,7 +319,7 @@ describe('xata init', () => {
}
}",
"readme.md": "",
"xataCustom.ts": "// Generated by Xata Codegen 0.27.0. Please do not edit.
"xataCustom.ts": "// Generated by Xata Codegen 0.28.0. Please do not edit.
import { buildClient } from \\"npm:@xata.io/client@latest\\";
import type {
BaseClientOptions,
Expand Down Expand Up @@ -400,7 +400,7 @@ describe('xata init', () => {
"package.json": "{\\"name\\":\\"test\\",\\"version\\":\\"1.0.0\\"}",
"pnpm-lock.yaml": "lockfileVersion: '6.0'",
"readme.md": "",
"xataCustom.ts": "// Generated by Xata Codegen 0.27.0. Please do not edit.
"xataCustom.ts": "// Generated by Xata Codegen 0.28.0. Please do not edit.
import { buildClient } from \\"@xata.io/client\\";
import type {
BaseClientOptions,
Expand Down
187 changes: 187 additions & 0 deletions cli/src/hooks/init/compatibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { writeFile, readFile, stat } from 'fs/promises';
import fetch from 'node-fetch';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { ONE_DAY, check, fetchInfo, getSdkVersion } from './compatibility.js';
import semver from 'semver';

vi.mock('node-fetch');
vi.mock('fs/promises');

afterEach(() => {
vi.clearAllMocks();
});

const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
const writeFileMock = writeFile as unknown as ReturnType<typeof vi.fn>;
const readFileMock = readFile as unknown as ReturnType<typeof vi.fn>;
const statMock = stat as unknown as ReturnType<typeof vi.fn>;

const latestAvailableVersionCLI = '1.0.0';
const latestAvailableVersionSDK = '2.0.0';
const specificVersionCLI = '0.0.8';

const userVersionCLI = '~0.0.1';
const userVersionSDK = '^0.0.2';
const userVersionAlpha = `${latestAvailableVersionCLI}-alpha.v927d47c`;

const cliUpdateAvailable = `"✨ A newer version of @xata.io/cli is now available: ${latestAvailableVersionCLI}. You are currently using version: ${semver.coerce(
userVersionCLI
)}"`;
const sdkUpdateAvailable = `"✨ A newer version of @xata.io/client is now available: ${latestAvailableVersionSDK}. You are currently using version: ${semver.coerce(
userVersionSDK
)}"`;

const cliError = `"Incompatible version of @xata.io/cli: ${semver.coerce(
userVersionCLI
)}. Please upgrade to a version that satisfies: >=${latestAvailableVersionCLI}||${specificVersionCLI}."`;
const sdkError = `"Incompatible version of @xata.io/client: ${semver.coerce(
userVersionSDK
)}. Please upgrade to a version that satisfies: ${latestAvailableVersionSDK}."`;

const compatibilityFile = './compatibility.json';

const compat = {
'@xata.io/cli': {
latest: latestAvailableVersionCLI,
compatibility: [
{
range: `>=${latestAvailableVersionCLI}`
},
{
range: `${specificVersionCLI}`
}
]
},
'@xata.io/client': {
latest: latestAvailableVersionSDK,
compatibility: [
{
range: latestAvailableVersionSDK
}
]
}
};

const packageJsonObj = (withPackage: boolean) => {
return {
name: 'client-ts',
dependencies: withPackage
? {
'@xata.io/client': userVersionSDK
}
: {}
};
};

fetchMock.mockReturnValue({ ok: true, json: async () => compat });

describe('getSdkVersion', () => {
test('returns version when @xata package', async () => {
readFileMock.mockReturnValue(JSON.stringify(packageJsonObj(true)));
expect(await getSdkVersion()).toEqual(userVersionSDK);
});

test('returns null when no @xata package', async () => {
readFileMock.mockReturnValue(JSON.stringify(packageJsonObj(false)));
expect(await getSdkVersion()).toEqual(null);
});
});

describe('fetchInfo', () => {
const fetchInfoParams = { file: compatibilityFile, url: '' };

describe('refreshes', () => {
beforeEach(() => {
readFileMock.mockReturnValue(JSON.stringify(compat));
writeFileMock.mockReturnValue(undefined);
});

test('when no files exist', async () => {
statMock.mockRejectedValue(undefined);

await fetchInfo(fetchInfoParams);
expect(writeFileMock).toHaveBeenCalledTimes(1);
});

test('when file is stale', async () => {
const yesterday = new Date().getDate() - ONE_DAY + 1000;
statMock.mockReturnValue({ mtime: new Date(yesterday) });

await fetchInfo(fetchInfoParams);
expect(writeFileMock).toHaveBeenCalledTimes(1);
});

test('when problem fetching, no file writes', async () => {
fetchMock.mockReturnValue({
ok: false
});

const yesterday = new Date().getDate() - ONE_DAY + 1000;
statMock.mockReturnValue({ mtime: new Date(yesterday) });

await fetchInfo(fetchInfoParams);
expect(writeFileMock).not.toHaveBeenCalled();
});
});

describe('does not refresh', () => {
test('if file is not stale', async () => {
statMock.mockReturnValue({ mtime: new Date() });

await fetchInfo(fetchInfoParams);
expect(fetchMock).not.toHaveBeenCalled();
expect(writeFileMock).not.toHaveBeenCalled();
});
});
});

describe('checks', () => {
describe('latest', () => {
beforeEach(() => {
readFileMock.mockReturnValue(JSON.stringify(compat));
});

test('returns warn if newer package available', async () => {
const cliResponse = await check({ compat, pkg: '@xata.io/cli', version: userVersionCLI });
expect(cliResponse.warn).toMatchInlineSnapshot(cliUpdateAvailable);

const sdkResponse = await check({ compat, pkg: '@xata.io/client', version: userVersionSDK });
expect(sdkResponse.warn).toMatchInlineSnapshot(sdkUpdateAvailable);
});

test('returns null if no newer package available', async () => {
const cliResponse = await check({ compat, pkg: '@xata.io/cli', version: latestAvailableVersionCLI });
expect(cliResponse.warn).toBeNull();

const sdkResponse = await check({ compat, pkg: '@xata.io/client', version: latestAvailableVersionSDK });
expect(sdkResponse.warn).toBeNull();
});
});

describe('compatibility', () => {
beforeEach(() => {
readFileMock.mockReturnValue(JSON.stringify(compat));
});

test('returns error if not compatible', async () => {
const cliResponse = await check({ compat, pkg: '@xata.io/cli', version: userVersionCLI });
expect(cliResponse.error).toMatchInlineSnapshot(cliError);

const sdkResponse = await check({ compat, pkg: '@xata.io/client', version: userVersionSDK });
expect(sdkResponse.error).toMatchInlineSnapshot(sdkError);
});

test('returns null if compatible', async () => {
const cliResponse = await check({ compat, pkg: '@xata.io/cli', version: latestAvailableVersionCLI });
expect(cliResponse.error).toBeNull();

const sdkResponse = await check({ compat, pkg: '@xata.io/client', version: latestAvailableVersionSDK });
expect(sdkResponse.error).toBeNull();

// Alpha versions
const cliResponseAlpha = await check({ compat, pkg: '@xata.io/cli', version: userVersionAlpha });
expect(cliResponseAlpha.error).toBeNull();
expect(cliResponseAlpha.warn).toBeNull();
});
});
});
95 changes: 95 additions & 0 deletions cli/src/hooks/init/compatibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Hook } from '@oclif/core';
import { readFile, stat, writeFile, mkdir } from 'fs/promises';
import semver from 'semver';
import path from 'path';
import fetch from 'node-fetch';

export const ONE_DAY = 1000 * 60 * 60 * 24 * 1;

export type Package = '@xata.io/cli' | '@xata.io/client';
export type Compatibility = Record<Package, { latest: string; compatibility: { range: string }[] }>;
export type PackageJson = { dependencies: Record<string, string> };

export const check = async ({ pkg, version, compat }: { pkg: Package; version: string; compat: Compatibility }) => {
const currentVersion = semver.coerce(version)?.version as string;
const updateAvailable = semver.lt(currentVersion, compat[pkg].latest);
const compatibleRange = compat[pkg].compatibility.map((v) => v.range).join('||');
const semverCompatible = semver.satisfies(currentVersion, compatibleRange);

return {
warn: updateAvailable
? `✨ A newer version of ${pkg} is now available: ${compat[pkg].latest}. You are currently using version: ${currentVersion}`
: null,
error: !semverCompatible
? `Incompatible version of ${pkg}: ${currentVersion}. Please upgrade to a version that satisfies: ${compatibleRange}.`
: null
};
};

export const getSdkVersion = async (): Promise<null | string> => {
const packageJson: PackageJson = JSON.parse(await readFile(`${path.join(process.cwd())}/package.json`, 'utf-8'));
return packageJson?.dependencies?.['@xata.io/client'] ? packageJson.dependencies['@xata.io/client'] : null;
};

export const fetchInfo = async ({ url, file }: { url: string; file: string }) => {
let shouldRefresh = true;

try {
// Latest time of one of the files should be enough
const statResult = await stat(file);
const lastModified = new Date(statResult.mtime);
// Last param is the number of days - we fetch new package info if the file is older than 1 day
const staleAt = new Date(lastModified.valueOf() + ONE_DAY);
shouldRefresh = new Date() > staleAt;
} catch (error) {
// Do nothing
}

if (shouldRefresh) {
try {
const latestCompatibilityResponse = await fetch(url);
if (!latestCompatibilityResponse.ok) return;
const body = await latestCompatibilityResponse.json();
if (!(body as Compatibility)['@xata.io/cli']) return;

try {
await writeFile(file, JSON.stringify(body));
} catch (error: any) {
if (error.code === 'ENOENT') {
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, JSON.stringify(body));
}
}
} catch (error) {
// Do nothing
}
}
};

const hook: Hook<'init'> = async function (_options) {
const dir = path.join(process.cwd(), '.xata', 'version');
const compatibilityFile = `${dir}/compatibility.json`;
const compatibilityUri = 'https://raw.githubusercontent.com/xataio/client-ts/main/compatibility.json';

const displayWarning = async () => {
const compat: Compatibility = JSON.parse(await readFile(compatibilityFile, 'utf-8'));

const checks = [
{ pkg: '@xata.io/cli', version: this.config.version },
{ pkg: '@xata.io/client', version: await getSdkVersion() }
] as const;

for (const { pkg, version } of checks) {
if (!version) continue;

const { warn, error } = await check({ pkg, version, compat });
if (warn) this.log(warn);
if (error) this.error(error);
}
};

await fetchInfo({ file: compatibilityFile, url: compatibilityUri });
await displayWarning();
};

export default hook;
Loading

0 comments on commit 841feab

Please sign in to comment.