reorganize sources

This commit is contained in:
joshuaboud 2022-08-19 17:05:09 -03:00
parent 192ff817f9
commit 1de5081d11
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
26 changed files with 937 additions and 3188 deletions

View File

@ -10,6 +10,11 @@
}
],
"settings": {
"jest.disabledWorkspaceFolders": ["Git Repo Root"]
"vitest.disabledWorkspaceFolders": [
"Git Repo Root"
],
"vitest.enable": true,
"vitest.showFailMessages": false,
"vitest.commandLine": "npx vitest"
}
}

View File

@ -1,5 +0,0 @@
module.exports = {
transform: {
"\\.[jt]sx?$": "babel-jest",
},
};

View File

@ -1,10 +0,0 @@
{
"typeAcquisition": {
"include": [
"jest"
]
},
"compilerOptions": {
"jsx": "preserve",
}
}

View File

@ -4,32 +4,33 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"test": "jest"
"test": "vitest run",
"testui": "vitest --ui",
"coverage": "vitest run --coverage"
},
"dependencies": {
"@45drives/regex": "^0.1.0",
"vue": "^3.2.25"
"vue": "^3.2.37"
},
"devDependencies": {
"@45drives/cockpit-css": "^0.1.5",
"@45drives/cockpit-helpers": "^0.1.8",
"@45drives/cockpit-syntaxes": "^0.1.4",
"@babel/preset-env": "^7.18.2",
"@fontsource/red-hat-text": "^4.5.4",
"@headlessui/vue": "^1.5.0",
"@heroicons/vue": "^1.0.6",
"@tailwindcss/forms": "^0.5.0",
"@types/jest": "^27.5.1",
"@vitejs/plugin-vue": "^2.2.0",
"@vitejs/plugin-vue": "^3.0.3",
"@vitest/ui": "^0.22.0",
"autoprefixer": "^10.4.2",
"babel-jest": "^28.1.0",
"jest": "^28.1.0",
"postcss": "^8.4.8",
"source-sans-pro": "^3.6.0",
"tailwindcss": "^3.0.23",
"vite": "^2.8.0",
"vue-router": "^4.0.15"
"typescript": "^4.7.4",
"vite": "^3.0.8",
"vitest": "^0.22.0",
"vue-router": "^4.0.15",
"vue-tsc": "^0.40.1"
}
}

View File

@ -0,0 +1,11 @@
import { Source } from "../types/Source";
import { Location } from "../types/Location";
import { INotifications } from "../types/Notifications";
export interface SourceContext {
notifications: INotifications;
}
export function defineSource<SourceType extends Source<Location>>(factory: (ctx: SourceContext) => SourceType) {
return factory;
}

View File

@ -0,0 +1,50 @@
import Progress from './Progress';
describe('classProgress', () => {
describe('defaultconstructorvalues', () => {
const prog = new Progress();
it('startsat0', () => {
expect(prog.start).to.equal(0);
})
it('endsat100', () => {
expect(prog.end).to.equal(100);
})
it('initializesto0', () => {
expect(prog.raw).to.equal(0);
})
})
it('can maintain value state and report accurate percent and fractions throughout range', () => {
function testProg(start: number, stop: number, step: number) {
const prog = new Progress(start, stop);
expect(prog.fraction).to.equal(0);
expect(prog.percent).to.equal(0);
for (let i = start; i <= stop; i += step) {
prog.update(i);
expect(prog.raw).to.equal(i);
const expectedFrac = (i - start) / (stop - start);
expect(prog.fraction).to.equal(expectedFrac);
expect(prog.percent).to.equal(expectedFrac * 100);
}
expect(prog.fraction).to.equal(1);
expect(prog.percent).to.equal(100);
}
testProg(0, 100, 1);
testProg(0, 50, 1);
testProg(0, 200, 1);
testProg(50, 100, 2);
testProg(3, 4, 1);
describe('edge cases', () => {
it('reports as finished if start === end', () => {
function testProg(value: number) {
const prog = new Progress(value, value);
expect(prog.fraction).to.equal(1);
expect(prog.percent).to.equal(100);
}
testProg(0);
testProg(1);
testProg(100);
testProg(1/3);
})
})
})
})

View File

@ -0,0 +1,32 @@
export default class Progress {
private readonly span: number;
private value: number;
/**
* Represent progress of a task with arbitrary start and end values
* @param start - value at 0%
* @param end - value at 100%
* @param initial - starting value
* @param cap - restrict value to [0%, 100%] for {@link fraction}, {@link percent}, {@link raw}, and {@link update}
*/
constructor(public readonly start = 0, public readonly end = 100, initial = start, private readonly cap = false) {
this.span = this.end - this.start;
this.value = initial;
}
get fraction() {
if (this.span === 0)
return 1;
return (this.raw - this.start) / this.span;
}
get percent() {
return this.fraction * 100;
}
get raw() {
return this.cap ? Math.max(this.start, Math.min(this.value, this.end)) : this.value;
}
update(value: number) {
this.value = this.cap ? Math.max(this.start, Math.min(value, this.end)) : value;
}
toString() {
return `${this.percent}%`;
}
}

View File

@ -0,0 +1,8 @@
/**
* ASCII field delimiter
*/
export const UNIT_SEPARATOR = '\x1F';
/**
* ASCII record delimiter
*/
export const RECORD_SEPARATOR = '\x1E';

View File

@ -0,0 +1,151 @@
import { Source } from "../../../types/Source";
import { ItemPosix, ItemPosixLink, ItemPosixNonLink, LsType } from "./types";
import { UNIT_SEPARATOR, RECORD_SEPARATOR } from "../../constants";
const findPrintfDirectives = [
'%p', // full path
'%f', // name
'%D', // dev id
'%i', // inode
'%m', // mode (octal)
'%U', // uid
'%u', // user
'%G', // gid
'%g', // group
'%s', // size
'%A@', // atime
'%T@', // mtime
'%C@', // ctime
'%B@', // btime
'%y', // type
'%Y', // symlink target type or type if not symlink
'%l', // symlink target name or '' if not symlink
];
type FindRecord = [
string, // full path
string, // name
string, // dev id
string, // inode
string, // mode (octal)
string, // uid
string, // user
string, // gid
string, // group
string, // size
string, // atime
string, // mtime
string, // ctime
string, // btime
ItemPosixNonLink["type"], // type
ItemPosixNonLink["type"], // symlink target type or type if not symlink
'', // symlink target name or '' if not symlink
] | [
string, // full path
string, // name
string, // dev id
string, // inode
string, // mode (octal)
string, // uid
string, // user
string, // gid
string, // group
string, // size
string, // atime
string, // mtime
string, // ctime
string, // btime
ItemPosixLink["type"], // type
string, // symlink target type or type if not symlink
ItemPosixLink["targetType"], // symlink target name or '' if not symlink
]
const findPrintfArg = `${findPrintfDirectives.join(UNIT_SEPARATOR)}${RECORD_SEPARATOR}`;
const parseIntFunctor = (radix?: number) => (str: string) => {
const num = parseInt(str, radix);
return isNaN(num) ? undefined : num;
}
function makeItem(record: string): Omit<ItemPosixLink, 'source'> | Omit<ItemPosixNonLink, 'source'> {
const [
path,
name,
st_devStr,
st_inoStr,
st_modeOctStr,
st_uidStr,
user,
st_gidStr,
group,
st_sizeStr,
st_atimeStr,
st_mtimeStr,
st_ctimeStr,
st_btimeStr,
type,
target,
targetType
] = record.split(UNIT_SEPARATOR) as FindRecord;
const [
st_dev,
st_ino,
st_uid,
st_gid,
st_size,
st_atime,
st_mtime,
st_ctime,
st_btime
] = [
st_devStr,
st_inoStr,
st_uidStr,
st_gidStr,
st_sizeStr,
st_atimeStr,
st_mtimeStr,
st_ctimeStr,
st_btimeStr,
].map(parseIntFunctor(10));
const [st_mode] = [st_modeOctStr].map(parseIntFunctor(8));
if (type === LsType.LINK) {
return {
path,
name,
st_dev,
st_ino,
st_mode,
st_uid,
user,
st_gid,
group,
st_size,
st_atime,
st_mtime,
st_ctime,
st_btime,
type,
target,
targetType,
}
} else {
return {
path,
name,
st_dev,
st_ino,
st_mode,
st_uid,
user,
st_gid,
group,
st_size,
st_atime,
st_mtime,
st_ctime,
st_btime,
type,
}
}
}

View File

@ -0,0 +1,20 @@
import { defineSource } from "../../../composables/SourceAdapter";
import { Source } from "../../../types/Source";
import { ItemPosix } from "./types";
export default defineSource(({ notifications }) => {
return class SourcePosix implements Source<ItemPosix> {
constructor(private root: string, private host: string) { }
private async getRecords(location: string): Promise<string[]> {
return [
""
]
}
async list(path: string = ""): Promise<(ItemPosix)[]> {
return (await this.getRecords(this.root + path)).map(record => this.makeItem(record));
}
async lookup(path: string): Promise<ItemPosix> {
return this.makeItem(this.getRecords(this.root + location)[0])
}
}
})

View File

@ -0,0 +1,109 @@
import { Location } from "../../../types/Location";
export enum LsType {
REGULAR_FILE = 'f',
DIRECTORY = 'd',
LINK = 'l',
CHARACTER = 'c',
BLOCK = 'b',
FIFO = 'p',
SOCKET = 's',
UNKNOWN = 'U',
LINK_LOOP = 'L',
LINK_BROKEN = 'N',
DOOR = 'D',
}
export namespace LsType {
export const humanReadableLut: { [key in LsType]: string } = {
[LsType.REGULAR_FILE]: 'regular file',
[LsType.DIRECTORY]: 'directory',
[LsType.LINK]: 'symbolic link',
[LsType.CHARACTER]: 'character device',
[LsType.BLOCK]: 'block device',
[LsType.FIFO]: 'FIFO (named pipe)',
[LsType.SOCKET]: 'socket',
[LsType.UNKNOWN]: 'unknown file type',
[LsType.LINK_LOOP]: 'broken link (loop)',
[LsType.LINK_BROKEN]: 'broken link (non-existent)',
[LsType.DOOR]: 'door (Solaris)', // probably don't need this, but why not right?
}
}
/**
* POSIX-spec of stat output, minus st_nlink, st_rdev, st_blksz, st_blocks
*/
export interface ItemPosixBase extends Location {
/**
* Name of file
*/
name: string;
/**
* ID of device containing file
*/
st_dev?: number;
/**
* inode number
*/
st_ino?: number;
/**
* protection
*/
st_mode?: number;
/**
* user ID of owner
*/
st_uid?: number;
/**
* user name of owner
*/
user: string;
/**
* group ID of owner
*/
st_gid?: number;
/**
* group name of owner
*/
group: string;
/**
* total size, in bytes
*/
st_size?: number;
/**
* time of last access
*/
st_atime?: number;
/**
* time of last modification
*/
st_mtime?: number;
/**
* time of last status change
*/
st_ctime?: number;
/**
* Birth time (not supported on all filesystems)
*/
st_btime?: number;
/**
* File's type (like in ls -l), U=unknown type (shouldn't happen)
*/
type: LsType;
}
export interface ItemPosixNonLink extends ItemPosixBase {
type: Exclude<LsType, LsType.LINK | LsType.LINK_BROKEN | LsType.LINK_LOOP>;
}
export interface ItemPosixLink extends ItemPosixBase {
type: LsType.LINK;
/**
* Only if symbolic link:
* File's type (like %y), plus follow symbolic links: `L'=loop, `N'=nonexistent, `?' for any other error
*/
targetType: LsType;
target: string;
}
export type ItemPosix = ItemPosixNonLink | ItemPosixLink;

13
navigator/src/types/FrontEnd.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { Location } from "./Location";
export namespace ItemDisplay {
export type mimetype = `text/${string}` | `image/${string}` | `video/${string}`;
}
export interface ItemDisplay<SourceItemType extends Location> {
sourceItem: SourceItemType;
name: string;
displayType: 'file' | 'directory';
isLink: true;
mimetype?: ItemDisplay.mimetype;
}

39
navigator/src/types/Item.d.ts vendored Normal file
View File

@ -0,0 +1,39 @@
import { Location } from "./Location";
import { Source } from "./Source";
export namespace Item {
export type Type = "file" | "directory" | "link";
}
export interface ItemBase extends Location {
type: Item.Type;
}
export interface ItemFile extends ItemBase {
type: "file";
size?: number;
}
export interface ItemDirectory extends ItemBase {
type: "directory";
}
export interface ItemLinkBase extends ItemBase {
type: "link";
link: ItemBase["path"];
resolvedType: Omit<Item.Type, "link">
}
export interface ItemLinkFile extends ItemLinkBase, ItemFile {
resolvedType: "file";
}
export interface ItemLinkDirectory extends ItemLinkBase, ItemDirectory {
resolvedType: "directory";
}
export interface ItemLinkBroken extends ItemLinkBase {
broken: true;
}
export type Item = ItemFile | ItemDirectory | ItemLinkFile | ItemLinkDirectory;

12
navigator/src/types/Location.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import { Source } from "./Source";
export interface Location {
/**
* Reference to the source that contains the item
*/
source: Source;
/**
* Path to item relative to source's base path
*/
path: string;
}

32
navigator/src/types/Notifications.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
export interface INotifications {
info(header: string, body: string, actions?: INotificationAction[]): INotification;
warn(header: string, body: string, actions?: INotificationAction[]): INotification;
error(header: string, body: string, actions?: INotificationAction[]): INotification;
success(header: string, body: string, actions?: INotificationAction[]): INotification;
denied(header: string, body: string, actions?: INotificationAction[]): INotification;
}
/**
* An action related to a notification
*/
export interface INotificationAction {
/**
* Button text for action
*/
text: string;
/**
* The implementation of the action
* @param removeNotif - Call this to remove the notification, equivalent to {@link INotification.remove}
*/
callback(removeNotif: () => void): void;
}
/**
* Notification handle returned by methods in INotifications
*/
export interface INotification {
/**
* Remove the notification
*/
remove(): void;
}

64
navigator/src/types/Source.d.ts vendored Normal file
View File

@ -0,0 +1,64 @@
import { ItemDisplay } from "./FrontEnd";
import { Location } from "./Location";
export interface ItemPermissions {
owner: string | number;
group: string | number;
mode: number;
}
export namespace Source {
export interface CreateOptions {
/**
* If a file/link already exists at the specified path, and is the same type as what is being created, overwrite it.
* Cannot overwrite directories.
*/
overwrite?: boolean;
/**
* If a file/link already exists at the specified path, overwrite it regardless of original type.
* Cannot overwrite directories.
*/
forceOverwrite?: boolean;
/**
* If the path does not exist, create parent directories.
* Analogous to `mkdir -p`
*/
parents?: boolean;
}
export interface DeleteOptions {
/**
* For directories, delete all children. If not specified for non-empty directory, deletion will fail.
*/
recursive?: boolean;
}
export interface DownloadOptions {
/**
* Compress files/directories into archive before downloading
*/
zip?: boolean;
}
}
export interface Source<ItemType extends Location, FileType extends Location = ItemType, DirectoryType extends Location = ItemType, LinkType extends Location = ItemType> {
displayTransform(item: ItemType): ItemDisplay<ItemType>;
async list(path?: string): Promise<ItemType[]>;
async lookup(path: string): Promise<ItemType>;
async createFile?(path: string, opts?: Source.CreateOptions): Promise<FileType>;
async createDirectory?(path: string, opts?: Source.CreateOptions): Promise<DirectoryType>;
async createLink?(path: string, targetLocation: string, opts?: Source.CreateOptions): Promise<LinkType>;
async delete?(item: ItemType, opts?: Source.DeleteOptions): Promise<ItemType>;
async moveToTrash?(item: ItemType, opts?: Source.DeleteOptions): Promise<ItemType>;
async read?(item: FileType): Promise<Uint8Array>;
async write?(item: FileType, data: Uint8Array): Promise<FileType>;
async getPermissions?(item: Location): Promise<Location & ItemPermissions>;
async setPermissions?(item: Location & Partial<ItemPermissions>, newPermissions?: Partial<ItemPermissions>): Promise<Location & ItemPermissions>;
async download?(item: Location, options: Source.DownloadOptions): Promise<Location>;
async download?(items: Location[], options: Source.DownloadOptions): Promise<Location>;
async upload?(dataTransfer: DataTransfer): Promise<Location | Location[]>;
}

3
navigator/src/types/cockpit.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare global {
var cockpit = {};
}

7
navigator/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

33
navigator/tsconfig.json Normal file
View File

@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"ESNext",
"DOM"
],
"skipLibCheck": true,
"types": [
"vitest/globals",
]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts"
]
}

View File

@ -1,16 +0,0 @@
export type ProblemCode =
'access-denied' |
'authentication-failed' |
'internal-error' |
'no-cockpit' |
'no-session' |
'not-found' |
'terminated' |
'timeout' |
'unknown-hostkey' |
'no-forwarding';
export interface Problem {
message: string;
problem: ProblemCode | null;
}

View File

@ -1,55 +0,0 @@
import { Problem } from "./Problem";
export interface SpawnProblem extends Problem {
exit_status: number | null;
exit_signal: string | null;
}
export type SpawnOptionsErr = "out" | "ignore" | "message"
export interface SpawnOptions {
/**
* If set to `true` then handle the input and output of the process as
* arrays of binary bytes.
*/
binary?: boolean;
/**
* The directory to spawn the process in.
*/
directory?: string;
/**
* Controls where the standard error is sent. By default it is logged to the
* journal.
* If set to `"out"` it is included in with the output data.
* If set to `"ignore"` then the error output is discarded.
* If set to `"message"`, then it will be returned as the error message.
* When the {@link SpawnOptions.pty} `"pty"` field is set, this field has no effect.
*/
err?: "out" | "ignore" | "message";
/**
* The remote host to spawn the process on. If an alternate user or port is
* required it can be specified as `"user@myhost:port"`. If no host is
* specified then the correct one will be automatically selected based on
* the page calling this function.
*/
host?: string;
/**
* An optional array that contains strings to be used as additional
* environment variables for the new process. These are "NAME=VALUE"
* strings.
*/
environ?: string[];
/**
* Launch the process in its own PTY terminal, and send/receive terminal
* input and output.
*/
pty?: boolean;
/**
* Batch data coming from the process in blocks of at least this size. This
* is not a guarantee. After a short timeout the data will be sent even if
* the data doesn't match the batch size. Defaults to zero.
*/
batch?: number;
}
export type Spawn = () => void;

View File

@ -1,167 +0,0 @@
export import { Problem, ProblemCode } from './Problem';
export import { Spawn, SpawnOptions, SpawnOptionsErr, SpawnProblem } from './Spawn';
export interface Cockpit {
spawn: Spawn;
}
declare global {
declare var cockpit: Cockpit;
}
// interface Func1<T, R = void> {
// (arg: T): R;
// }
// interface Func2<T, K, R = void> {
// (arg1: T, arg2: K): R;
// }
// interface Func3<T, K, V, R = void> {
// (arg1: T, arg2: K, arg3: V): R;
// }
// type GUID = string;
// type Fail = {
// message: string;
// problem?: string;
// };
// type SpawnFail = Fail & {
// exit_status?: number;
// exit_signal?: number;
// };
// type ErrorConfig = 'message' | 'out' | 'ignore' | 'pty';
// type Superuser = 'require' | 'try';
// type ProblemCodes = 'access-denied' | 'authentication-failed' | 'internal-error' | 'no-cockpit' | 'no-session' | 'not-found' | 'terminated' | 'timeout' | 'unknown-hostkey' | 'no-forwarding';
// type SpawnConfig = {
// err?: ErrorConfig;
// binary?: boolean;
// directory?: string;
// host?: string;
// environ?: string[];
// pty?: boolean;
// batch?: boolean;
// latency?: number;
// superuser?: Superuser;
// };
// interface SyntaxParser<K> {
// parse: Func1<string, K>;
// stringify: Func1<K, string>;
// }
// type FileConfig<K extends object = {}> = {
// syntax?: SyntaxParser<K>;
// binary?: boolean;
// max_read_size?: number;
// superuser?: Superuser;
// host?: string;
// };
// interface FileOperationsPromise extends JQuery.Promise<string> {}
// interface ClosableWithProblem { close(problem?: ProblemCodes): void; }
// interface FileOperations extends Closable {
// read(): FileOperationsPromise;
// replace(content: string | null, tag?: string): FileOperationsPromise;
// modify(): FileOperationsPromise;
// watch(callback: Func3<string, string, string> | Func2<string, string>): void;
// /**
// * A string containing the path that was passed to the `cockpit.file()` method.
// */
// path: string;
// }
// interface SpawnPromise extends JQuery.Promise<string>, ClosableWithProblem {
// stream(callback: Func1<string>): SpawnPromise;
// input(data?: string | Uint8Array, stream?: boolean): SpawnPromise;
// }
// interface Closable { close(): void; }
// function CacheProvider(provide: Func1<any>, key: any): Closable | null;
// interface UserInfo {
// id: number;
// name: string;
// full_name: string;
// groups: string[];
// home: string;
// shell: string;
// }
// interface EventHandler<V, T = string> {
// addEventListener(type: T, handler: Func1<CustomEvent<V>>);
// removeEventListener(type: T, handler: Func1<CustomEvent<V>>);
// dispatchEvent(event: Event);
// }
// interface UserInfoPromise extends JQuery.Promise<UserInfo> {}
// type PermissionOptions = { group: string };
// type PermissionEvents = 'changed';
// interface PermissionInfo extends EventHandler<PermissionInfoPromise, PermissionEvents>, Closable {
// allowed: boolean;
// user: UserInfo;
// };
// interface PermissionInfoPromise extends JQuery.Promise<PermissionInfo> {};
// type HttpHeaders = any;
// interface HttpOptions {
// address: string;
// connection: string;
// superuser: Superuser;
// }
// type HttpData = string | Uint8Array;
// declare const enum HttpMethod {
// Get = 'GET',
// Post = 'POST',
// Head = 'HEAD'
// }
// interface HttpRequestOptions {
// body?: HttpData;
// headers?: HttpHeaders;
// method?: HttpMethod;
// params?: any;
// path?: string;
// }
// interface HttpOperations extends ClosableWithProblem {
// get(path: string, params: any, headers: HttpHeaders): HttpOperationsPromise;
// post(path: string, body: string | any, headers: HttpHeaders): HttpOperationsPromise;
// request(options: HttpRequestOptions): HttpOperationsPromise;
// }
// interface HttpOperationsPromise extends JQuery.Promise<HttpData>, ClosableWithProblem {
// response(handler: Func2<number, HttpHeaders>): HttpOperationsPromise;
// stream(handler: Func1<HttpData>): HttpOperationsPromise;
// input(handler: HttpData, stream?: boolean): HttpOperationsPromise;
// }
// interface CockpitAPI {
// spawn(path: string[], config?: SpawnConfig): SpawnPromise;
// script(path: string, args?: string[], config?: SpawnConfig): SpawnPromise;
// file(path: string): FileOperations;
// cache(key: GUID, provider: CacheProvider, consumer: Func2<any, any>): Closable;
// logout(reload: boolean): void;
// user(): UserInfoPromise;
// permission(options?: PermissionOptions): PermissionInfoPromise;
// http(endpoint: string | number, options: HttpOptions): HttpOperations;
// }
// declare var cockpit : CockpitAPI;
// declare module 'cockpit' {
// export default cockpit;
// export { Superuser, ErrorConfig, ProblemCodes };
// }

View File

@ -1,3 +1,4 @@
/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
@ -9,5 +10,8 @@ export default defineConfig({
target: [
"chrome87", "edge88", "firefox78", "safari14"
]
},
test: {
globals: true,
}
})

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
module.exports = {
projects: [
'./navigator'
]
}