fix: fix errors with biomejs
This commit is contained in:
parent
8e9b178f5e
commit
4d33e78454
@ -1,10 +1,6 @@
|
||||
{
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
],
|
||||
"watch-extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": ["ts-node/register"],
|
||||
"watch-extensions": ["ts"],
|
||||
"recursive": true,
|
||||
"reporter": "spec",
|
||||
"timeout": 60000,
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
|
||||
|
||||
// eslint-disable-next-line n/shebang
|
||||
import {execute} from '@oclif/core'
|
||||
import { execute } from "@oclif/core";
|
||||
|
||||
await execute({development: true, dir: import.meta.url})
|
||||
await execute({ development: true, dir: import.meta.url });
|
||||
|
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {execute} from '@oclif/core'
|
||||
import { execute } from "@oclif/core";
|
||||
|
||||
await execute({dir: import.meta.url})
|
||||
await execute({ dir: import.meta.url });
|
||||
|
@ -1,36 +1,43 @@
|
||||
import {Args, Command, Flags} from '@oclif/core'
|
||||
import {loadConfig, validateConfig} from '../helpers/config.js'
|
||||
import {existsSync} from 'node:fs'
|
||||
import {mkdir, writeFile} from 'node:fs/promises'
|
||||
import {join} from 'node:path'
|
||||
import {getComponentURL, getDirComponentURL, getRegistry} from '../helpers/registry.js'
|
||||
import ora from 'ora'
|
||||
import React, {ComponentPropsWithoutRef} from 'react'
|
||||
import {render, Box} from 'ink'
|
||||
import {SearchBox} from '../components/SearchBox.js'
|
||||
import {getDirComponentRequiredFiles, checkComponentInstalled} from '../helpers/path.js'
|
||||
import {Choice} from '../components/Choice.js'
|
||||
import {colorize} from '@oclif/core/ux'
|
||||
import {safeFetch} from '../helpers/safe-fetcher.js'
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { Args, Command, Flags } from "@oclif/core";
|
||||
import { colorize } from "@oclif/core/ux";
|
||||
import { Box, render } from "ink";
|
||||
import ora from "ora";
|
||||
import React, { type ComponentPropsWithoutRef } from "react";
|
||||
import { Choice } from "../components/Choice.js";
|
||||
import { SearchBox } from "../components/SearchBox.js";
|
||||
import { loadConfig, validateConfig } from "../helpers/config.js";
|
||||
import {
|
||||
checkComponentInstalled,
|
||||
getDirComponentRequiredFiles,
|
||||
} from "../helpers/path.js";
|
||||
import {
|
||||
getComponentURL,
|
||||
getDirComponentURL,
|
||||
getRegistry,
|
||||
} from "../helpers/registry.js";
|
||||
import { safeFetch } from "../helpers/safe-fetcher.js";
|
||||
|
||||
function Generator() {
|
||||
let complete: boolean = false
|
||||
let complete = false;
|
||||
|
||||
function ComponentSelector<T extends {displayName: string; key: string; installed: boolean}>(
|
||||
props: Omit<ComponentPropsWithoutRef<typeof SearchBox<T>>, 'helper'>,
|
||||
) {
|
||||
function ComponentSelector<
|
||||
T extends { displayName: string; key: string; installed: boolean },
|
||||
>(props: Omit<ComponentPropsWithoutRef<typeof SearchBox<T>>, "helper">) {
|
||||
return (
|
||||
<Box>
|
||||
<SearchBox
|
||||
helper={'Press Enter to select component.'}
|
||||
helper={"Press Enter to select component."}
|
||||
{...props}
|
||||
onSubmit={(value) => {
|
||||
complete = true
|
||||
props.onSubmit?.(value)
|
||||
complete = true;
|
||||
props.onSubmit?.(value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
@ -38,29 +45,31 @@ function Generator() {
|
||||
new Promise<void>((r) => {
|
||||
const i = setInterval(() => {
|
||||
if (complete) {
|
||||
r()
|
||||
clearInterval(i)
|
||||
r();
|
||||
clearInterval(i);
|
||||
}
|
||||
}, 100)
|
||||
}, 100);
|
||||
}),
|
||||
] as const
|
||||
] as const;
|
||||
}
|
||||
|
||||
function Generator2() {
|
||||
let complete = false
|
||||
let complete = false;
|
||||
|
||||
function ForceSelector({onComplete}: {onComplete: (value: 'yes' | 'no') => void}) {
|
||||
function ForceSelector({
|
||||
onComplete,
|
||||
}: { onComplete: (value: "yes" | "no") => void }) {
|
||||
return (
|
||||
<Choice
|
||||
question={'You already installed this component. Overwrite?'}
|
||||
yes={'Yes, overwrite existing file and install it.'}
|
||||
no={'No, cancel the action.'}
|
||||
question={"You already installed this component. Overwrite?"}
|
||||
yes={"Yes, overwrite existing file and install it."}
|
||||
no={"No, cancel the action."}
|
||||
onSubmit={(value) => {
|
||||
complete = true
|
||||
onComplete(value)
|
||||
complete = true;
|
||||
onComplete(value);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
@ -68,190 +77,237 @@ function Generator2() {
|
||||
new Promise<void>((r) => {
|
||||
const i = setInterval(() => {
|
||||
if (complete) {
|
||||
r()
|
||||
clearInterval(i)
|
||||
r();
|
||||
clearInterval(i);
|
||||
}
|
||||
}, 100)
|
||||
}, 100);
|
||||
}),
|
||||
] as const
|
||||
] as const;
|
||||
}
|
||||
|
||||
export default class Add extends Command {
|
||||
static override args = {
|
||||
name: Args.string({description: 'name of component to install'}),
|
||||
}
|
||||
name: Args.string({ description: "name of component to install" }),
|
||||
};
|
||||
|
||||
static override description = 'Add a component to the project.'
|
||||
static override description = "Add a component to the project.";
|
||||
|
||||
static override examples = ['<%= config.bin %> <%= command.id %>']
|
||||
static override examples = ["<%= config.bin %> <%= command.id %>"];
|
||||
|
||||
static override flags = {
|
||||
branch: Flags.string({char: 'r', description: 'use other branch instead of main'}),
|
||||
force: Flags.boolean({char: 'f', description: 'override the existing file'}),
|
||||
config: Flags.string({char: 'p', description: 'path to config'}),
|
||||
shared: Flags.string({char: 's', description: 'place for installation of shared.ts'}),
|
||||
components: Flags.string({char: 'c', description: 'place for installation of components'}),
|
||||
}
|
||||
branch: Flags.string({
|
||||
char: "r",
|
||||
description: "use other branch instead of main",
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
char: "f",
|
||||
description: "override the existing file",
|
||||
}),
|
||||
config: Flags.string({ char: "p", description: "path to config" }),
|
||||
shared: Flags.string({
|
||||
char: "s",
|
||||
description: "place for installation of shared.ts",
|
||||
}),
|
||||
components: Flags.string({
|
||||
char: "c",
|
||||
description: "place for installation of components",
|
||||
}),
|
||||
};
|
||||
|
||||
public async run(): Promise<void> {
|
||||
let {
|
||||
args,
|
||||
flags: {force, ...flags},
|
||||
} = await this.parse(Add)
|
||||
flags: { force, ...flags },
|
||||
} = await this.parse(Add);
|
||||
|
||||
const resolvedConfig = await validateConfig((message: string) => this.log(message), await loadConfig(flags.config))
|
||||
const componentFolder = join(process.cwd(), resolvedConfig.paths.components)
|
||||
const libFolder = join(process.cwd(), resolvedConfig.paths.lib)
|
||||
const resolvedConfig = await validateConfig(
|
||||
(message: string) => this.log(message),
|
||||
await loadConfig(flags.config),
|
||||
);
|
||||
const componentFolder = join(
|
||||
process.cwd(),
|
||||
resolvedConfig.paths.components,
|
||||
);
|
||||
const libFolder = join(process.cwd(), resolvedConfig.paths.lib);
|
||||
if (!existsSync(componentFolder)) {
|
||||
await mkdir(componentFolder, {recursive: true})
|
||||
await mkdir(componentFolder, { recursive: true });
|
||||
}
|
||||
if (!existsSync(libFolder)) {
|
||||
await mkdir(libFolder, {recursive: true})
|
||||
await mkdir(libFolder, { recursive: true });
|
||||
}
|
||||
|
||||
const loadRegistryOra = ora('Fetching registry...').start()
|
||||
const loadRegistryOra = ora("Fetching registry...").start();
|
||||
if (flags.registry) {
|
||||
this.log(`Using ${flags.branch} for branch.`)
|
||||
this.log(`Using ${flags.branch} for branch.`);
|
||||
}
|
||||
const unsafeRegistry = await getRegistry(flags.branch)
|
||||
const unsafeRegistry = await getRegistry(flags.branch);
|
||||
if (!unsafeRegistry.ok) {
|
||||
loadRegistryOra.fail(unsafeRegistry.message)
|
||||
return
|
||||
loadRegistryOra.fail(unsafeRegistry.message);
|
||||
return;
|
||||
}
|
||||
const registry = unsafeRegistry.registry
|
||||
const componentNames = Object.keys(registry.components)
|
||||
loadRegistryOra.succeed(`Successfully fetched registry! (${componentNames.length} components)`)
|
||||
const searchBoxComponent: {displayName: string; key: string; installed: boolean}[] = []
|
||||
const registry = unsafeRegistry.registry;
|
||||
const componentNames = Object.keys(registry.components);
|
||||
loadRegistryOra.succeed(
|
||||
`Successfully fetched registry! (${componentNames.length} components)`,
|
||||
);
|
||||
const searchBoxComponent: {
|
||||
displayName: string;
|
||||
key: string;
|
||||
installed: boolean;
|
||||
}[] = [];
|
||||
for await (const name of componentNames) {
|
||||
const installed = await checkComponentInstalled(registry.components[name], resolvedConfig)
|
||||
const installed = await checkComponentInstalled(
|
||||
registry.components[name],
|
||||
resolvedConfig,
|
||||
);
|
||||
searchBoxComponent.push({
|
||||
displayName: installed ? `${name} (installed)` : name,
|
||||
key: name,
|
||||
installed,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
let name: string | undefined = args.name?.toLowerCase?.()
|
||||
let name: string | undefined = args.name?.toLowerCase?.();
|
||||
let requireForce: boolean =
|
||||
!name || !componentNames.includes(name.toLowerCase())
|
||||
? false
|
||||
: searchBoxComponent.find(({key}) => key === name)?.installed
|
||||
: searchBoxComponent.find(({ key }) => key === name)?.installed
|
||||
? !force
|
||||
: false
|
||||
: false;
|
||||
|
||||
if (!name || !componentNames.includes(name.toLowerCase())) {
|
||||
const [ComponentSelector, waitForComplete] = Generator()
|
||||
const [ComponentSelector, waitForComplete] = Generator();
|
||||
|
||||
const inkInstance = render(
|
||||
<ComponentSelector
|
||||
components={searchBoxComponent}
|
||||
initialQuery={args.name}
|
||||
onSubmit={(comp) => {
|
||||
name = comp.key
|
||||
requireForce = comp.installed
|
||||
inkInstance.clear()
|
||||
name = comp.key;
|
||||
requireForce = comp.installed;
|
||||
inkInstance.clear();
|
||||
}}
|
||||
/>,
|
||||
)
|
||||
await waitForComplete
|
||||
inkInstance.unmount()
|
||||
);
|
||||
await waitForComplete;
|
||||
inkInstance.unmount();
|
||||
}
|
||||
|
||||
let quit = false
|
||||
let quit = false;
|
||||
|
||||
if (requireForce) {
|
||||
const [ForceSelector, waitForComplete] = Generator2()
|
||||
const [ForceSelector, waitForComplete] = Generator2();
|
||||
|
||||
const inkInstance = render(
|
||||
<ForceSelector
|
||||
onComplete={(value) => {
|
||||
force = value === 'yes'
|
||||
quit = value === 'no'
|
||||
inkInstance.clear()
|
||||
force = value === "yes";
|
||||
quit = value === "no";
|
||||
inkInstance.clear();
|
||||
}}
|
||||
/>,
|
||||
)
|
||||
await waitForComplete
|
||||
inkInstance.unmount()
|
||||
);
|
||||
await waitForComplete;
|
||||
inkInstance.unmount();
|
||||
if (quit) {
|
||||
this.log(colorize('redBright', 'Installation canceled by user.'))
|
||||
return
|
||||
this.log(colorize("redBright", "Installation canceled by user."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!name || !componentNames.includes(name.toLowerCase())) {
|
||||
this.error('Component name is not provided, or not selected.')
|
||||
this.error("Component name is not provided, or not selected.");
|
||||
}
|
||||
|
||||
const libFileOra = ora('Installing required library...').start()
|
||||
let successCount = 0
|
||||
const libFileOra = ora("Installing required library...").start();
|
||||
let successCount = 0;
|
||||
for await (const libFile of registry.lib) {
|
||||
const filePath = join(libFolder, libFile)
|
||||
const filePath = join(libFolder, libFile);
|
||||
if (!existsSync(filePath)) {
|
||||
const libFileContentResponse = await safeFetch(registry.base + registry.paths.lib.replace('{libName}', libFile))
|
||||
const libFileContentResponse = await safeFetch(
|
||||
registry.base + registry.paths.lib.replace("{libName}", libFile),
|
||||
);
|
||||
if (!libFileContentResponse.ok) {
|
||||
libFileOra.fail(libFileContentResponse.message)
|
||||
return
|
||||
libFileOra.fail(libFileContentResponse.message);
|
||||
return;
|
||||
}
|
||||
const libFileContent = await libFileContentResponse.response.text()
|
||||
await writeFile(filePath, libFileContent)
|
||||
successCount++
|
||||
const libFileContent = await libFileContentResponse.response.text();
|
||||
await writeFile(filePath, libFileContent);
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
if (successCount > 1) {
|
||||
libFileOra.succeed('Successfully installed library files!')
|
||||
libFileOra.succeed("Successfully installed library files!");
|
||||
} else {
|
||||
libFileOra.succeed('Library files are already installed!')
|
||||
libFileOra.succeed("Library files are already installed!");
|
||||
}
|
||||
|
||||
const componentFileOra = ora(`Installing ${name} component...`).start()
|
||||
const componentObject = registry.components[name]
|
||||
if (componentObject.type === 'file') {
|
||||
const componentFile = join(componentFolder, registry.components[name].name)
|
||||
const componentFileOra = ora(`Installing ${name} component...`).start();
|
||||
const componentObject = registry.components[name];
|
||||
if (componentObject.type === "file") {
|
||||
const componentFile = join(
|
||||
componentFolder,
|
||||
registry.components[name].name,
|
||||
);
|
||||
if (existsSync(componentFile) && !force) {
|
||||
componentFileOra.succeed(`Component is already installed! (${componentFile})`)
|
||||
componentFileOra.succeed(
|
||||
`Component is already installed! (${componentFile})`,
|
||||
);
|
||||
} else {
|
||||
const componentFileContentResponse = await safeFetch(await getComponentURL(registry, componentObject))
|
||||
const componentFileContentResponse = await safeFetch(
|
||||
await getComponentURL(registry, componentObject),
|
||||
);
|
||||
if (!componentFileContentResponse.ok) {
|
||||
componentFileOra.fail(componentFileContentResponse.message)
|
||||
return
|
||||
componentFileOra.fail(componentFileContentResponse.message);
|
||||
return;
|
||||
}
|
||||
const componentFileContent = (await componentFileContentResponse.response.text()).replaceAll(
|
||||
/import\s+{[^}]*}\s+from\s+"@pswui-lib"/g,
|
||||
(match) => match.replace(/@pswui-lib/, resolvedConfig.import.lib),
|
||||
)
|
||||
await writeFile(componentFile, componentFileContent)
|
||||
componentFileOra.succeed('Component is successfully installed!')
|
||||
const componentFileContent = (
|
||||
await componentFileContentResponse.response.text()
|
||||
).replaceAll(/import\s+{[^}]*}\s+from\s+"@pswui-lib"/g, (match) =>
|
||||
match.replace(/@pswui-lib/, resolvedConfig.import.lib),
|
||||
);
|
||||
await writeFile(componentFile, componentFileContent);
|
||||
componentFileOra.succeed("Component is successfully installed!");
|
||||
}
|
||||
} else if (componentObject.type === 'dir') {
|
||||
const componentDir = join(componentFolder, componentObject.name)
|
||||
} else if (componentObject.type === "dir") {
|
||||
const componentDir = join(componentFolder, componentObject.name);
|
||||
if (!existsSync(componentDir)) {
|
||||
await mkdir(componentDir, {recursive: true})
|
||||
await mkdir(componentDir, { recursive: true });
|
||||
}
|
||||
const requiredFiles = await getDirComponentRequiredFiles(componentObject, resolvedConfig)
|
||||
const requiredFiles = await getDirComponentRequiredFiles(
|
||||
componentObject,
|
||||
resolvedConfig,
|
||||
);
|
||||
if (requiredFiles.length === 0 && !force) {
|
||||
componentFileOra.succeed(`Component is already installed! (${componentDir})`)
|
||||
componentFileOra.succeed(
|
||||
`Component is already installed! (${componentDir})`,
|
||||
);
|
||||
} else {
|
||||
const requiredFilesURLs = await getDirComponentURL(registry, componentObject, requiredFiles)
|
||||
const requiredFilesURLs = await getDirComponentURL(
|
||||
registry,
|
||||
componentObject,
|
||||
requiredFiles,
|
||||
);
|
||||
for await (const [filename, url] of requiredFilesURLs) {
|
||||
const componentFile = join(componentDir, filename)
|
||||
const componentFile = join(componentDir, filename);
|
||||
if (!existsSync(componentFile) || force) {
|
||||
const componentFileContentResponse = await safeFetch(url)
|
||||
const componentFileContentResponse = await safeFetch(url);
|
||||
if (!componentFileContentResponse.ok) {
|
||||
componentFileOra.fail(componentFileContentResponse.message)
|
||||
return
|
||||
componentFileOra.fail(componentFileContentResponse.message);
|
||||
return;
|
||||
}
|
||||
const componentFileContent = (await componentFileContentResponse.response.text()).replaceAll(
|
||||
/import\s+{[^}]*}\s+from\s+"@pswui-lib"/g,
|
||||
(match) => match.replace(/@pswui-lib/, resolvedConfig.import.lib),
|
||||
)
|
||||
await writeFile(componentFile, componentFileContent)
|
||||
const componentFileContent = (
|
||||
await componentFileContentResponse.response.text()
|
||||
).replaceAll(/import\s+{[^}]*}\s+from\s+"@pswui-lib"/g, (match) =>
|
||||
match.replace(/@pswui-lib/, resolvedConfig.import.lib),
|
||||
);
|
||||
await writeFile(componentFile, componentFileContent);
|
||||
}
|
||||
}
|
||||
componentFileOra.succeed('Component is successfully installed!')
|
||||
componentFileOra.succeed("Component is successfully installed!");
|
||||
}
|
||||
}
|
||||
|
||||
this.log('Now you can import the component.')
|
||||
this.log("Now you can import the component.");
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +1,89 @@
|
||||
import {Command, Flags} from '@oclif/core'
|
||||
import ora from 'ora'
|
||||
import treeify from 'treeify'
|
||||
import { Command, Flags } from "@oclif/core";
|
||||
import ora from "ora";
|
||||
import treeify from "treeify";
|
||||
|
||||
import {loadConfig, validateConfig} from '../helpers/config.js'
|
||||
import {checkComponentInstalled} from '../helpers/path.js'
|
||||
import {getComponentURL, getDirComponentURL, getRegistry} from '../helpers/registry.js'
|
||||
import { loadConfig, validateConfig } from "../helpers/config.js";
|
||||
import { checkComponentInstalled } from "../helpers/path.js";
|
||||
import {
|
||||
getComponentURL,
|
||||
getDirComponentURL,
|
||||
getRegistry,
|
||||
} from "../helpers/registry.js";
|
||||
|
||||
export default class List extends Command {
|
||||
static override description = 'Prints all available components in registry and components installed in this project.'
|
||||
static override description =
|
||||
"Prints all available components in registry and components installed in this project.";
|
||||
|
||||
static override examples = ['<%= config.bin %> <%= command.id %>']
|
||||
static override examples = ["<%= config.bin %> <%= command.id %>"];
|
||||
|
||||
static override flags = {
|
||||
branch: Flags.string({char: 'r', description: 'use other branch instead of main'}),
|
||||
config: Flags.string({char: 'p', description: 'path to config'}),
|
||||
url: Flags.boolean({char: 'u', description: 'include component file URL'}),
|
||||
}
|
||||
branch: Flags.string({
|
||||
char: "r",
|
||||
description: "use other branch instead of main",
|
||||
}),
|
||||
config: Flags.string({ char: "p", description: "path to config" }),
|
||||
url: Flags.boolean({
|
||||
char: "u",
|
||||
description: "include component file URL",
|
||||
}),
|
||||
};
|
||||
|
||||
public async run(): Promise<void> {
|
||||
const {flags} = await this.parse(List)
|
||||
const { flags } = await this.parse(List);
|
||||
|
||||
const registrySpinner = ora('Fetching registry...')
|
||||
const registrySpinner = ora("Fetching registry...");
|
||||
|
||||
const loadedConfig = await validateConfig((message: string) => this.log(message), await loadConfig(flags.config))
|
||||
const loadedConfig = await validateConfig(
|
||||
(message: string) => this.log(message),
|
||||
await loadConfig(flags.config),
|
||||
);
|
||||
|
||||
registrySpinner.start()
|
||||
registrySpinner.start();
|
||||
if (flags.branch) {
|
||||
this.log(`Using ${flags.branch} for registry.`)
|
||||
this.log(`Using ${flags.branch} for registry.`);
|
||||
}
|
||||
|
||||
const unsafeRegistry = await getRegistry(flags.branch)
|
||||
const unsafeRegistry = await getRegistry(flags.branch);
|
||||
if (!unsafeRegistry.ok) {
|
||||
registrySpinner.fail(unsafeRegistry.message)
|
||||
return
|
||||
registrySpinner.fail(unsafeRegistry.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const {registry} = unsafeRegistry
|
||||
const names = Object.keys(registry.components)
|
||||
const { registry } = unsafeRegistry;
|
||||
const names = Object.keys(registry.components);
|
||||
|
||||
registrySpinner.succeed(`Fetched ${names.length} components.`)
|
||||
registrySpinner.succeed(`Fetched ${names.length} components.`);
|
||||
|
||||
let final: Record<string, {URL?: Record<string, string>; installed: 'no' | 'yes'}> = {}
|
||||
let final: Record<
|
||||
string,
|
||||
{ URL?: Record<string, string>; installed: "no" | "yes" }
|
||||
> = {};
|
||||
for await (const name of names) {
|
||||
const componentObject = registry.components[name]
|
||||
const installed = (await checkComponentInstalled(componentObject, loadedConfig)) ? 'yes' : 'no'
|
||||
const componentObject = registry.components[name];
|
||||
const installed = (await checkComponentInstalled(
|
||||
componentObject,
|
||||
loadedConfig,
|
||||
))
|
||||
? "yes"
|
||||
: "no";
|
||||
if (flags.url) {
|
||||
let url: Record<string, string> = {}
|
||||
let url: Record<string, string> = {};
|
||||
|
||||
if (componentObject.type === 'file') {
|
||||
url[name] = await getComponentURL(registry, componentObject)
|
||||
} else if (componentObject.type === 'dir') {
|
||||
url = Object.fromEntries(await getDirComponentURL(registry, componentObject))
|
||||
if (componentObject.type === "file") {
|
||||
url[name] = await getComponentURL(registry, componentObject);
|
||||
} else if (componentObject.type === "dir") {
|
||||
url = Object.fromEntries(
|
||||
await getDirComponentURL(registry, componentObject),
|
||||
);
|
||||
}
|
||||
|
||||
final = {...final, [name]: {URL: url, installed}}
|
||||
final = { ...final, [name]: { URL: url, installed } };
|
||||
} else {
|
||||
final = {...final, [name]: {installed}}
|
||||
final = { ...final, [name]: { installed } };
|
||||
}
|
||||
}
|
||||
|
||||
this.log('AVAILABLE COMPONENTS')
|
||||
this.log(treeify.asTree(final, true, true))
|
||||
this.log("AVAILABLE COMPONENTS");
|
||||
this.log(treeify.asTree(final, true, true));
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,45 @@
|
||||
import {Command, Args, Flags} from '@oclif/core'
|
||||
import {render} from 'ink'
|
||||
import {SearchBox} from '../components/SearchBox.js'
|
||||
import {getRegistry} from '../helpers/registry.js'
|
||||
import React from 'react'
|
||||
import { Args, Command, Flags } from "@oclif/core";
|
||||
import { render } from "ink";
|
||||
import React from "react";
|
||||
import { SearchBox } from "../components/SearchBox.js";
|
||||
import { getRegistry } from "../helpers/registry.js";
|
||||
|
||||
export default class Search extends Command {
|
||||
static override args = {
|
||||
query: Args.string({description: 'search query'}),
|
||||
}
|
||||
query: Args.string({ description: "search query" }),
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
branch: Flags.string({char: 'r', description: 'use other branch instead of main'}),
|
||||
}
|
||||
branch: Flags.string({
|
||||
char: "r",
|
||||
description: "use other branch instead of main",
|
||||
}),
|
||||
};
|
||||
|
||||
static override description = 'Search components.'
|
||||
static override description = "Search components.";
|
||||
|
||||
static override examples = ['<%= config.bin %> <%= command.id %>']
|
||||
static override examples = ["<%= config.bin %> <%= command.id %>"];
|
||||
|
||||
public async run(): Promise<void> {
|
||||
const {args, flags} = await this.parse(Search)
|
||||
const { args, flags } = await this.parse(Search);
|
||||
|
||||
if (flags.branch) {
|
||||
this.log(`Using ${flags.branch} for registry.`)
|
||||
this.log(`Using ${flags.branch} for registry.`);
|
||||
}
|
||||
const registryResult = await getRegistry(flags.branch)
|
||||
const registryResult = await getRegistry(flags.branch);
|
||||
if (!registryResult.ok) {
|
||||
this.error(registryResult.message)
|
||||
this.error(registryResult.message);
|
||||
}
|
||||
const registry = registryResult.registry
|
||||
const componentNames = Object.keys(registry.components)
|
||||
const registry = registryResult.registry;
|
||||
const componentNames = Object.keys(registry.components);
|
||||
|
||||
await render(
|
||||
<SearchBox
|
||||
components={componentNames.map((v) => ({key: v, displayName: v}))}
|
||||
components={componentNames.map((v) => ({ key: v, displayName: v }))}
|
||||
initialQuery={args.query}
|
||||
helper={'Press ESC to quit'}
|
||||
helper={"Press ESC to quit"}
|
||||
onKeyDown={(_, k, app) => k.escape && app.exit()}
|
||||
/>,
|
||||
).waitUntilExit()
|
||||
).waitUntilExit();
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
import React, {useState} from 'react'
|
||||
import {Box, Text, useInput} from 'ink'
|
||||
import { Box, Text, useInput } from "ink";
|
||||
import React, { useState } from "react";
|
||||
|
||||
function isUnicodeSupported() {
|
||||
if (process.platform !== 'win32') {
|
||||
return process.env['TERM'] !== 'linux' // Linux console (kernel)
|
||||
if (process.platform !== "win32") {
|
||||
return process.env.TERM !== "linux"; // Linux console (kernel)
|
||||
}
|
||||
|
||||
return (
|
||||
Boolean(process.env['WT_SESSION']) || // Windows Terminal
|
||||
Boolean(process.env['TERMINUS_SUBLIME']) || // Terminus (<0.2.27)
|
||||
process.env['ConEmuTask'] === '{cmd::Cmder}' || // ConEmu and cmder
|
||||
process.env['TERM_PROGRAM'] === 'Terminus-Sublime' ||
|
||||
process.env['TERM_PROGRAM'] === 'vscode' ||
|
||||
process.env['TERM'] === 'xterm-256color' ||
|
||||
process.env['TERM'] === 'alacritty' ||
|
||||
process.env['TERMINAL_EMULATOR'] === 'JetBrains-JediTerm'
|
||||
)
|
||||
Boolean(process.env.WT_SESSION) || // Windows Terminal
|
||||
Boolean(process.env.TERMINUS_SUBLIME) || // Terminus (<0.2.27)
|
||||
process.env.ConEmuTask === "{cmd::Cmder}" || // ConEmu and cmder
|
||||
process.env.TERM_PROGRAM === "Terminus-Sublime" ||
|
||||
process.env.TERM_PROGRAM === "vscode" ||
|
||||
process.env.TERM === "xterm-256color" ||
|
||||
process.env.TERM === "alacritty" ||
|
||||
process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm"
|
||||
);
|
||||
}
|
||||
|
||||
const shouldUseMain = isUnicodeSupported()
|
||||
const SELECTED: string = shouldUseMain ? '◉' : '(*)'
|
||||
const UNSELECTED: string = shouldUseMain ? '◯' : '( )'
|
||||
const shouldUseMain = isUnicodeSupported();
|
||||
const SELECTED: string = shouldUseMain ? "◉" : "(*)";
|
||||
const UNSELECTED: string = shouldUseMain ? "◯" : "( )";
|
||||
|
||||
export function Choice({
|
||||
question,
|
||||
@ -29,35 +29,38 @@ export function Choice({
|
||||
onSubmit,
|
||||
initial,
|
||||
}: {
|
||||
question: string
|
||||
yes: string
|
||||
no: string
|
||||
onSubmit?: (vaule: 'yes' | 'no') => void
|
||||
initial?: 'yes' | 'no'
|
||||
question: string;
|
||||
yes: string;
|
||||
no: string;
|
||||
onSubmit?: (vaule: "yes" | "no") => void;
|
||||
initial?: "yes" | "no";
|
||||
}) {
|
||||
const [state, setState] = useState<'yes' | 'no'>(initial ?? 'yes')
|
||||
const [state, setState] = useState<"yes" | "no">(initial ?? "yes");
|
||||
|
||||
useInput((_, k) => {
|
||||
if (k.upArrow) {
|
||||
setState('yes')
|
||||
setState("yes");
|
||||
} else if (k.downArrow) {
|
||||
setState('no')
|
||||
setState("no");
|
||||
}
|
||||
|
||||
if (k.return) {
|
||||
onSubmit?.(state)
|
||||
onSubmit?.(state);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<Box display={'flex'} flexDirection={'column'}>
|
||||
<Text color={'greenBright'}>{question}</Text>
|
||||
<Text color={state === 'yes' ? undefined : 'gray'}>
|
||||
{state === 'yes' ? SELECTED : UNSELECTED} {yes}
|
||||
<Box
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Text color={"greenBright"}>{question}</Text>
|
||||
<Text color={state === "yes" ? undefined : "gray"}>
|
||||
{state === "yes" ? SELECTED : UNSELECTED} {yes}
|
||||
</Text>
|
||||
<Text color={state === 'no' ? undefined : 'gray'}>
|
||||
{state === 'no' ? SELECTED : UNSELECTED} {no}
|
||||
<Text color={state === "no" ? undefined : "gray"}>
|
||||
{state === "no" ? SELECTED : UNSELECTED} {no}
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,24 +1,32 @@
|
||||
import React from 'react'
|
||||
import {Box, Text} from 'ink'
|
||||
import { Box, Text } from "ink";
|
||||
import React from "react";
|
||||
|
||||
export function Divider({width = 50, padding = 1, title}: {width?: number; padding?: number; title: string}) {
|
||||
const length = Math.floor((width - title.length - padding * 2) / 2)
|
||||
export function Divider({
|
||||
width = 50,
|
||||
padding = 1,
|
||||
title,
|
||||
}: { width?: number; padding?: number; title: string }) {
|
||||
const length = Math.floor((width - title.length - padding * 2) / 2);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{Array.from(Array(length)).map((_, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: there's nothing to be key except index
|
||||
<Text key={i}>─</Text>
|
||||
))}
|
||||
{Array.from(Array(padding)).map((_, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: there's nothing to be key except index
|
||||
<Text key={i}> </Text>
|
||||
))}
|
||||
<Text>{title}</Text>
|
||||
{Array.from(Array(padding)).map((_, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: there's nothing to be key except index
|
||||
<Text key={i}> </Text>
|
||||
))}
|
||||
{Array.from(Array(length)).map((_, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: there's nothing to be key except index
|
||||
<Text key={i}>─</Text>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {getSuggestion} from '../helpers/search.js'
|
||||
import Input from 'ink-text-input'
|
||||
import {Divider} from './Divider.js'
|
||||
import {Box, Text, useInput, useApp, type Key} from 'ink'
|
||||
import { Box, type Key, Text, useApp, useInput } from "ink";
|
||||
import Input from "ink-text-input";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getSuggestion } from "../helpers/search.js";
|
||||
import { Divider } from "./Divider.js";
|
||||
|
||||
export function SearchBox<T extends {key: string; displayName: string}>({
|
||||
export function SearchBox<T extends { key: string; displayName: string }>({
|
||||
components,
|
||||
helper,
|
||||
initialQuery,
|
||||
@ -12,92 +12,115 @@ export function SearchBox<T extends {key: string; displayName: string}>({
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
components: T[]
|
||||
helper: string
|
||||
initialQuery?: string
|
||||
onKeyDown?: (i: string, k: Key, app: ReturnType<typeof useApp>) => void
|
||||
onChange?: (item: T) => void
|
||||
onSubmit?: (item: T) => void
|
||||
components: T[];
|
||||
helper: string;
|
||||
initialQuery?: string;
|
||||
onKeyDown?: (i: string, k: Key, app: ReturnType<typeof useApp>) => void;
|
||||
onChange?: (item: T) => void;
|
||||
onSubmit?: (item: T) => void;
|
||||
}) {
|
||||
const [query, setQuery] = useState<string>(initialQuery ?? '')
|
||||
const [queryMode, setQueryMode] = useState<boolean>(true)
|
||||
const [isLoading, setLoading] = useState<boolean>(false)
|
||||
const [suggestions, setSuggestions] = useState<string[]>([])
|
||||
const [selected, setSelected] = useState<number>(-1)
|
||||
const [query, setQuery] = useState<string>(initialQuery ?? "");
|
||||
const [queryMode, setQueryMode] = useState<boolean>(true);
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
const [selected, setSelected] = useState<number>(-1);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryMode) {
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
getSuggestion(
|
||||
components.map(({key}) => key),
|
||||
components.map(({ key }) => key),
|
||||
query,
|
||||
).then((result) => {
|
||||
setSuggestions(result)
|
||||
setSelected(-1)
|
||||
})
|
||||
setSuggestions(result);
|
||||
setSelected(-1);
|
||||
});
|
||||
}
|
||||
}, [query, queryMode])
|
||||
}, [query, queryMode, components]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange) {
|
||||
const found = components.find(({key}) => key === suggestions[selected])
|
||||
found && onChange(found)
|
||||
const found = components.find(({ key }) => key === suggestions[selected]);
|
||||
found && onChange(found);
|
||||
}
|
||||
}, [selected, suggestions, onChange])
|
||||
}, [selected, suggestions, onChange, components]);
|
||||
|
||||
const app = useApp()
|
||||
const app = useApp();
|
||||
|
||||
useInput((i, k) => {
|
||||
if (k.downArrow) {
|
||||
setSelected((p) => (p >= suggestions.length - 1 ? 0 : p + 1))
|
||||
setQueryMode(false)
|
||||
setSelected((p) => (p >= suggestions.length - 1 ? 0 : p + 1));
|
||||
setQueryMode(false);
|
||||
}
|
||||
if (k.upArrow) {
|
||||
setSelected((p) => (p <= 0 ? suggestions.length - 1 : p - 1))
|
||||
setQueryMode(false)
|
||||
setSelected((p) => (p <= 0 ? suggestions.length - 1 : p - 1));
|
||||
setQueryMode(false);
|
||||
}
|
||||
onKeyDown?.(i, k, app)
|
||||
})
|
||||
onKeyDown?.(i, k, app);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!queryMode && suggestions[selected]) {
|
||||
setQuery(suggestions[selected])
|
||||
setQuery(suggestions[selected]);
|
||||
}
|
||||
}, [queryMode, selected])
|
||||
}, [queryMode, selected, suggestions]);
|
||||
|
||||
return (
|
||||
<Box width={50} display={'flex'} flexDirection={'column'}>
|
||||
<Text color={'gray'}>{helper}</Text>
|
||||
<Box display={'flex'} flexDirection={'row'}>
|
||||
<Box marginRight={1} display={'flex'} flexDirection={'row'}>
|
||||
<Text color={'greenBright'}>Search?</Text>
|
||||
<Box
|
||||
width={50}
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Text color={"gray"}>{helper}</Text>
|
||||
<Box
|
||||
display={"flex"}
|
||||
flexDirection={"row"}
|
||||
>
|
||||
<Box
|
||||
marginRight={1}
|
||||
display={"flex"}
|
||||
flexDirection={"row"}
|
||||
>
|
||||
<Text color={"greenBright"}>Search?</Text>
|
||||
</Box>
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(v) => {
|
||||
setQueryMode(true)
|
||||
setQuery(v)
|
||||
setQueryMode(true);
|
||||
setQuery(v);
|
||||
}}
|
||||
showCursor
|
||||
placeholder={' query'}
|
||||
placeholder={" query"}
|
||||
onSubmit={() => {
|
||||
const found = components.find(({key}) => key === suggestions[selected])
|
||||
found && onSubmit?.(found)
|
||||
const found = components.find(
|
||||
({ key }) => key === suggestions[selected],
|
||||
);
|
||||
found && onSubmit?.(found);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Divider title={isLoading ? 'Loading...' : `${suggestions.length} components found.`} />
|
||||
<Box display={'flex'} flexDirection={'column'}>
|
||||
<Divider
|
||||
title={
|
||||
isLoading ? "Loading..." : `${suggestions.length} components found.`
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
{suggestions.map((name, index) => {
|
||||
return (
|
||||
<Box key={name}>
|
||||
<Text color={selected === index ? undefined : 'gray'}>
|
||||
{components[components.findIndex(({key}) => key === name)].displayName}
|
||||
<Text color={selected === index ? undefined : "gray"}>
|
||||
{
|
||||
components[components.findIndex(({ key }) => key === name)]
|
||||
.displayName
|
||||
}
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,27 +1,28 @@
|
||||
import {z} from 'zod'
|
||||
import { z } from "zod";
|
||||
|
||||
export const registryURL = (branch: string) => `https://raw.githubusercontent.com/pswui/ui/${branch}/registry.json`
|
||||
export const CONFIG_DEFAULT_PATH = 'pswui.config.js'
|
||||
export const registryURL = (branch: string) =>
|
||||
`https://raw.githubusercontent.com/pswui/ui/${branch}/registry.json`;
|
||||
export const CONFIG_DEFAULT_PATH = "pswui.config.js";
|
||||
|
||||
export type RegistryComponent =
|
||||
| {
|
||||
files: string[]
|
||||
name: string
|
||||
type: 'dir'
|
||||
files: string[];
|
||||
name: string;
|
||||
type: "dir";
|
||||
}
|
||||
| {
|
||||
name: string
|
||||
type: 'file'
|
||||
}
|
||||
name: string;
|
||||
type: "file";
|
||||
};
|
||||
|
||||
export interface Registry {
|
||||
base: string
|
||||
components: Record<string, RegistryComponent>
|
||||
lib: string[]
|
||||
base: string;
|
||||
components: Record<string, RegistryComponent>;
|
||||
lib: string[];
|
||||
paths: {
|
||||
components: string
|
||||
lib: string
|
||||
}
|
||||
components: string;
|
||||
lib: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
@ -29,29 +30,31 @@ export interface Config {
|
||||
* Absolute path that will used for import in component
|
||||
*/
|
||||
import?: {
|
||||
lib?: '@pswui-lib' | string
|
||||
}
|
||||
lib?: "@pswui-lib" | string;
|
||||
};
|
||||
/**
|
||||
* Path that cli will create a file.
|
||||
*/
|
||||
paths?: {
|
||||
components?: 'src/pswui/components' | string
|
||||
lib?: 'src/pswui/lib' | string
|
||||
}
|
||||
components?: "src/pswui/components" | string;
|
||||
lib?: "src/pswui/lib" | string;
|
||||
};
|
||||
}
|
||||
export type ResolvedConfig<T = Config> = {
|
||||
[k in keyof T]-?: NonNullable<T[k]> extends object ? ResolvedConfig<NonNullable<T[k]>> : T[k]
|
||||
}
|
||||
[k in keyof T]-?: NonNullable<T[k]> extends object
|
||||
? ResolvedConfig<NonNullable<T[k]>>
|
||||
: T[k];
|
||||
};
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
import: {
|
||||
lib: '@pswui-lib',
|
||||
lib: "@pswui-lib",
|
||||
},
|
||||
paths: {
|
||||
components: 'src/pswui/components',
|
||||
lib: 'src/pswui/lib',
|
||||
components: "src/pswui/components",
|
||||
lib: "src/pswui/lib",
|
||||
},
|
||||
}
|
||||
};
|
||||
export const configZod = z.object({
|
||||
import: z
|
||||
.object({
|
||||
@ -61,9 +64,12 @@ export const configZod = z.object({
|
||||
.default(DEFAULT_CONFIG.import),
|
||||
paths: z
|
||||
.object({
|
||||
components: z.string().optional().default(DEFAULT_CONFIG.paths.components),
|
||||
components: z
|
||||
.string()
|
||||
.optional()
|
||||
.default(DEFAULT_CONFIG.paths.components),
|
||||
lib: z.string().optional().default(DEFAULT_CONFIG.paths.lib),
|
||||
})
|
||||
.optional()
|
||||
.default(DEFAULT_CONFIG.paths),
|
||||
})
|
||||
});
|
||||
|
@ -1,43 +1,67 @@
|
||||
import {colorize} from '@oclif/core/ux'
|
||||
import {existsSync} from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { colorize } from "@oclif/core/ux";
|
||||
|
||||
import {CONFIG_DEFAULT_PATH, DEFAULT_CONFIG, ResolvedConfig, configZod} from '../const.js'
|
||||
import {changeExtension} from './path.js'
|
||||
import {
|
||||
CONFIG_DEFAULT_PATH,
|
||||
DEFAULT_CONFIG,
|
||||
type ResolvedConfig,
|
||||
configZod,
|
||||
} from "../const.js";
|
||||
import { changeExtension } from "./path.js";
|
||||
|
||||
export async function loadConfig(config?: string): Promise<unknown> {
|
||||
const userConfigPath = config ? path.join(process.cwd(), config) : null
|
||||
const defaultConfigPath = path.join(process.cwd(), CONFIG_DEFAULT_PATH)
|
||||
const cjsConfigPath = path.join(process.cwd(), await changeExtension(CONFIG_DEFAULT_PATH, '.cjs'))
|
||||
const mjsConfigPath = path.join(process.cwd(), await changeExtension(CONFIG_DEFAULT_PATH, '.mjs'))
|
||||
const userConfigPath = config ? path.join(process.cwd(), config) : null;
|
||||
const defaultConfigPath = path.join(process.cwd(), CONFIG_DEFAULT_PATH);
|
||||
const cjsConfigPath = path.join(
|
||||
process.cwd(),
|
||||
await changeExtension(CONFIG_DEFAULT_PATH, ".cjs"),
|
||||
);
|
||||
const mjsConfigPath = path.join(
|
||||
process.cwd(),
|
||||
await changeExtension(CONFIG_DEFAULT_PATH, ".mjs"),
|
||||
);
|
||||
|
||||
if (userConfigPath) {
|
||||
if (existsSync(userConfigPath)) {
|
||||
return (await import(userConfigPath)).default
|
||||
return (await import(userConfigPath)).default;
|
||||
}
|
||||
|
||||
throw new Error(`Error: config ${userConfigPath} not found.`)
|
||||
throw new Error(`Error: config ${userConfigPath} not found.`);
|
||||
}
|
||||
|
||||
if (existsSync(defaultConfigPath)) {
|
||||
return (await import(defaultConfigPath)).default
|
||||
return (await import(defaultConfigPath)).default;
|
||||
}
|
||||
|
||||
if (existsSync(cjsConfigPath)) {
|
||||
return (await import(cjsConfigPath)).default
|
||||
return (await import(cjsConfigPath)).default;
|
||||
}
|
||||
|
||||
if (existsSync(mjsConfigPath)) {
|
||||
return (await import(mjsConfigPath)).default
|
||||
return (await import(mjsConfigPath)).default;
|
||||
}
|
||||
|
||||
return DEFAULT_CONFIG
|
||||
return DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
export async function validateConfig(log: (message: string) => void, config?: unknown): Promise<ResolvedConfig> {
|
||||
const parsedConfig: ResolvedConfig = await configZod.parseAsync(config)
|
||||
log(colorize('gray', `Install component to: ${path.join(process.cwd(), parsedConfig.paths.components)}`))
|
||||
log(colorize('gray', `Install shared module to: ${path.join(process.cwd(), parsedConfig.paths.lib)}`))
|
||||
log(colorize('gray', `Import shared with: ${parsedConfig.import.lib}`))
|
||||
return parsedConfig
|
||||
export async function validateConfig(
|
||||
log: (message: string) => void,
|
||||
config?: unknown,
|
||||
): Promise<ResolvedConfig> {
|
||||
const parsedConfig: ResolvedConfig = await configZod.parseAsync(config);
|
||||
log(
|
||||
colorize(
|
||||
"gray",
|
||||
`Install component to: ${path.join(process.cwd(), parsedConfig.paths.components)}`,
|
||||
),
|
||||
);
|
||||
log(
|
||||
colorize(
|
||||
"gray",
|
||||
`Install shared module to: ${path.join(process.cwd(), parsedConfig.paths.lib)}`,
|
||||
),
|
||||
);
|
||||
log(colorize("gray", `Import shared with: ${parsedConfig.import.lib}`));
|
||||
return parsedConfig;
|
||||
}
|
||||
|
@ -1,38 +1,52 @@
|
||||
import {existsSync} from 'node:fs'
|
||||
import {readdir} from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { existsSync } from "node:fs";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import {RegistryComponent, ResolvedConfig} from '../const.js'
|
||||
import type { RegistryComponent, ResolvedConfig } from "../const.js";
|
||||
|
||||
export async function getDirComponentRequiredFiles<T extends {type: 'dir'} & RegistryComponent>(
|
||||
componentObject: T,
|
||||
config: ResolvedConfig,
|
||||
) {
|
||||
const componentPath = path.join(process.cwd(), config.paths.components, componentObject.name)
|
||||
export async function getDirComponentRequiredFiles<
|
||||
T extends { type: "dir" } & RegistryComponent,
|
||||
>(componentObject: T, config: ResolvedConfig) {
|
||||
const componentPath = path.join(
|
||||
process.cwd(),
|
||||
config.paths.components,
|
||||
componentObject.name,
|
||||
);
|
||||
if (!existsSync(componentPath)) {
|
||||
return componentObject.files
|
||||
return componentObject.files;
|
||||
}
|
||||
|
||||
const dir = await readdir(componentPath)
|
||||
const dir = await readdir(componentPath);
|
||||
|
||||
return componentObject.files.filter((filename) => !dir.includes(filename))
|
||||
return componentObject.files.filter((filename) => !dir.includes(filename));
|
||||
}
|
||||
|
||||
export async function checkComponentInstalled(component: RegistryComponent, config: ResolvedConfig): Promise<boolean> {
|
||||
const componentDirRoot = path.join(process.cwd(), config.paths.components)
|
||||
if (!existsSync(componentDirRoot)) return false
|
||||
export async function checkComponentInstalled(
|
||||
component: RegistryComponent,
|
||||
config: ResolvedConfig,
|
||||
): Promise<boolean> {
|
||||
const componentDirRoot = path.join(process.cwd(), config.paths.components);
|
||||
if (!existsSync(componentDirRoot)) return false;
|
||||
|
||||
if (component.type === 'file') {
|
||||
const dir = await readdir(componentDirRoot)
|
||||
return dir.includes(component.name)
|
||||
if (component.type === "file") {
|
||||
const dir = await readdir(componentDirRoot);
|
||||
return dir.includes(component.name);
|
||||
}
|
||||
|
||||
const componentDir = path.join(componentDirRoot, component.name)
|
||||
if (!existsSync(componentDir)) return false
|
||||
const dir = await readdir(componentDir)
|
||||
return component.files.filter((filename) => !dir.includes(filename)).length === 0
|
||||
const componentDir = path.join(componentDirRoot, component.name);
|
||||
if (!existsSync(componentDir)) return false;
|
||||
const dir = await readdir(componentDir);
|
||||
return (
|
||||
component.files.filter((filename) => !dir.includes(filename)).length === 0
|
||||
);
|
||||
}
|
||||
|
||||
export async function changeExtension(_path: string, extension: string): Promise<string> {
|
||||
return path.join(path.dirname(_path), path.basename(_path, path.extname(_path)) + extension)
|
||||
export async function changeExtension(
|
||||
_path: string,
|
||||
extension: string,
|
||||
): Promise<string> {
|
||||
return path.join(
|
||||
path.dirname(_path),
|
||||
path.basename(_path, path.extname(_path)) + extension,
|
||||
);
|
||||
}
|
||||
|
@ -1,37 +1,49 @@
|
||||
import {Registry, RegistryComponent, registryURL} from '../const.js'
|
||||
import {safeFetch} from './safe-fetcher.js'
|
||||
import {
|
||||
type Registry,
|
||||
type RegistryComponent,
|
||||
registryURL,
|
||||
} from "../const.js";
|
||||
import { safeFetch } from "./safe-fetcher.js";
|
||||
|
||||
export async function getRegistry(
|
||||
branch?: string,
|
||||
): Promise<{message: string; ok: false} | {ok: true; registry: Registry}> {
|
||||
const registryResponse = await safeFetch(registryURL(branch ?? 'main'))
|
||||
): Promise<{ message: string; ok: false } | { ok: true; registry: Registry }> {
|
||||
const registryResponse = await safeFetch(registryURL(branch ?? "main"));
|
||||
|
||||
if (registryResponse.ok) {
|
||||
const registryJson = (await registryResponse.response.json()) as Registry
|
||||
registryJson.base = registryJson.base.replace('{branch}', branch ?? 'main')
|
||||
const registryJson = (await registryResponse.response.json()) as Registry;
|
||||
registryJson.base = registryJson.base.replace("{branch}", branch ?? "main");
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
registry: registryJson,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return registryResponse
|
||||
return registryResponse;
|
||||
}
|
||||
|
||||
export async function getComponentURL(
|
||||
registry: Registry,
|
||||
component: {type: 'file'} & RegistryComponent,
|
||||
component: { type: "file" } & RegistryComponent,
|
||||
): Promise<string> {
|
||||
return registry.base + registry.paths.components.replace('{componentName}', component.name)
|
||||
return (
|
||||
registry.base +
|
||||
registry.paths.components.replace("{componentName}", component.name)
|
||||
);
|
||||
}
|
||||
|
||||
export async function getDirComponentURL(
|
||||
registry: Registry,
|
||||
component: {type: 'dir'} & RegistryComponent,
|
||||
component: { type: "dir" } & RegistryComponent,
|
||||
files?: string[],
|
||||
): Promise<[string, string][]> {
|
||||
const base = registry.base + registry.paths.components.replace('{componentName}', component.name)
|
||||
const base =
|
||||
registry.base +
|
||||
registry.paths.components.replace("{componentName}", component.name);
|
||||
|
||||
return (files ?? component.files).map((filename) => [filename, base + '/' + filename])
|
||||
return (files ?? component.files).map((filename) => [
|
||||
filename,
|
||||
`${base}/${filename}`,
|
||||
]);
|
||||
}
|
||||
|
@ -1,19 +1,22 @@
|
||||
import fetch, {Response} from 'node-fetch'
|
||||
import fetch, { type Response } from "node-fetch";
|
||||
|
||||
export async function safeFetch(
|
||||
url: string,
|
||||
): Promise<{message: string; ok: false; response: Response} | {ok: true; response: Response}> {
|
||||
const response = await fetch(url)
|
||||
): Promise<
|
||||
| { message: string; ok: false; response: Response }
|
||||
| { ok: true; response: Response }
|
||||
> {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
return {
|
||||
ok: true,
|
||||
response,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
message: `Error while fetching from ${response.url}: ${response.status} ${response.statusText}`,
|
||||
ok: false,
|
||||
response,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,63 +1,69 @@
|
||||
export async function jaroWinkler(a: string, b: string): Promise<number> {
|
||||
const p = 0.1
|
||||
const p = 0.1;
|
||||
|
||||
if (a.length === 0 || b.length === 0) return 0
|
||||
if (a === b) return 1
|
||||
if (a.length === 0 || b.length === 0) return 0;
|
||||
if (a === b) return 1;
|
||||
|
||||
const range = Math.floor(Math.max(a.length, b.length) / 2) - 1
|
||||
let matches = 0
|
||||
const range = Math.floor(Math.max(a.length, b.length) / 2) - 1;
|
||||
let matches = 0;
|
||||
|
||||
const aMatches = Array.from({length: a.length})
|
||||
const bMatches = Array.from({length: b.length})
|
||||
const aMatches = Array.from({ length: a.length });
|
||||
const bMatches = Array.from({ length: b.length });
|
||||
|
||||
for (const [i, element] of Object.entries(a).map(
|
||||
([index, element]) => [Number.parseInt(index, 10), element] as const,
|
||||
)) {
|
||||
const start = i >= range ? i - range : 0
|
||||
const end = i + range <= b.length - 1 ? i + range : b.length - 1
|
||||
const start = i >= range ? i - range : 0;
|
||||
const end = i + range <= b.length - 1 ? i + range : b.length - 1;
|
||||
|
||||
for (let j = start; j <= end; j++) {
|
||||
if (bMatches[j] !== true && element === b[j]) {
|
||||
++matches
|
||||
aMatches[i] = true
|
||||
bMatches[j] = true
|
||||
break
|
||||
++matches;
|
||||
aMatches[i] = true;
|
||||
bMatches[j] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matches === 0) return 0
|
||||
if (matches === 0) return 0;
|
||||
|
||||
let t = 0
|
||||
let t = 0;
|
||||
|
||||
let point: number
|
||||
let point: number;
|
||||
|
||||
for (point = 0; point < a.length; point++) if (aMatches[point]) break
|
||||
for (point = 0; point < a.length; point++) if (aMatches[point]) break;
|
||||
|
||||
for (let i = point; i < a.length; i++)
|
||||
if (aMatches[i]) {
|
||||
let j
|
||||
let j: number;
|
||||
for (j = point; j < b.length; j++)
|
||||
if (bMatches[j]) {
|
||||
point = j + 1
|
||||
break
|
||||
point = j + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (a[i] !== b[j]) ++t
|
||||
if (a[i] !== b[j]) ++t;
|
||||
}
|
||||
|
||||
t /= 2
|
||||
t /= 2;
|
||||
|
||||
const J = (matches / a.length + matches / b.length + (matches - t) / matches) / 3
|
||||
return J + Math.min((p * t) / matches, 1) * (1 - J)
|
||||
const J =
|
||||
(matches / a.length + matches / b.length + (matches - t) / matches) / 3;
|
||||
return J + Math.min((p * t) / matches, 1) * (1 - J);
|
||||
}
|
||||
|
||||
export async function getSuggestion(componentNames: string[], input: string): Promise<string[]> {
|
||||
export async function getSuggestion(
|
||||
componentNames: string[],
|
||||
input: string,
|
||||
): Promise<string[]> {
|
||||
const componentJw = await Promise.all(
|
||||
componentNames.map(async (name) => [name, await jaroWinkler(name, input)] as const),
|
||||
)
|
||||
componentNames.map(
|
||||
async (name) => [name, await jaroWinkler(name, input)] as const,
|
||||
),
|
||||
);
|
||||
return componentJw
|
||||
.filter(([_, score]) => score > 0)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([name]) => name)
|
||||
.map(([name]) => name);
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from './public.js'
|
||||
export {run} from '@oclif/core'
|
||||
export * from "./public.js";
|
||||
export { run } from "@oclif/core";
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {Config} from './const.js'
|
||||
import type { Config } from "./const.js";
|
||||
|
||||
function buildConfig(config: Config): Config {
|
||||
return config
|
||||
return config;
|
||||
}
|
||||
|
||||
export {buildConfig}
|
||||
export { buildConfig };
|
||||
|
||||
export {Config} from './const.js'
|
||||
export { Config } from "./const.js";
|
||||
|
@ -1,7 +1,14 @@
|
||||
import "./tailwind.css";
|
||||
import App from "@/DialogOverflowTest.tsx";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode></React.StrictMode>,
|
||||
const ROOT = document.getElementById("root");
|
||||
|
||||
if (!ROOT) throw new Error("ROOT is not found");
|
||||
|
||||
ReactDOM.createRoot(ROOT).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user