Skip to content

Commit

Permalink
Merge pull request #422 from ieedan/general-improvements
Browse files Browse the repository at this point in the history
feat(cli): ux improvements
  • Loading branch information
ieedan authored Feb 7, 2025
2 parents 3044d13 + 012fed6 commit 1c652c0
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 96 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-comics-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": patch
---

feat: Autofix incorrect extension on (.ts|js|mjs|cjs) config files.
5 changes: 5 additions & 0 deletions .changeset/famous-hotels-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": patch
---

fix: Ensure registries coming from args are always configured first.
5 changes: 5 additions & 0 deletions .changeset/great-bobcats-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": patch
---

feat: Improve error messages when paths are incorrectly named or do not resolve.
5 changes: 5 additions & 0 deletions .changeset/many-geese-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": patch
---

fix: Do not accept invalid path default blocks path on init.
16 changes: 2 additions & 14 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,7 @@
"bugs": {
"url": "https://github.com/ieedan/jsrepo/issues"
},
"keywords": [
"repo",
"cli",
"svelte",
"vue",
"typescript",
"javascript",
"shadcn",
"registry"
],
"keywords": ["repo", "cli", "svelte", "vue", "typescript", "javascript", "shadcn", "registry"],
"type": "module",
"exports": {
".": {
Expand All @@ -34,10 +25,7 @@
},
"bin": "./dist/index.js",
"main": "./dist/index.js",
"files": [
"./schemas/**/*",
"dist/**/*"
],
"files": ["./schemas/**/*", "dist/**/*"],
"scripts": {
"start": "tsup --silent && node ./dist/index.js",
"build": "tsup",
Expand Down
51 changes: 47 additions & 4 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import color from 'chalk';
import { Command, Option, program } from 'commander';
import { diffLines } from 'diff';
import { createPathsMatcher } from 'get-tsconfig';
import { detect, resolveCommand } from 'package-manager-detector';
import path from 'pathe';
import * as v from 'valibot';
Expand All @@ -32,7 +33,7 @@ import {
} from '../utils/config';
import { installDependencies } from '../utils/dependencies';
import { formatDiff } from '../utils/diff';
import { formatFile } from '../utils/files';
import { formatFile, matchJSDescendant, tryGetTsconfig } from '../utils/files';
import { loadFormatterConfig } from '../utils/format';
import { json } from '../utils/language-support';
import * as persisted from '../utils/persisted';
Expand Down Expand Up @@ -144,10 +145,29 @@ const _initProject = async (registries: string[], options: Options) => {
let paths: Paths;
let configFiles: Record<string, string> = {};

const tsconfigResult = tryGetTsconfig(options.cwd).unwrapOr(null);

const defaultPathResult = await text({
message: 'Please enter a default path to install the blocks',
validate(value) {
if (value.trim() === '') return 'Please provide a value';

if (!value.startsWith('./')) {
const error =
'Invalid path alias! If you are intending to use a relative path make sure it starts with `./`';

if (tsconfigResult === null) {
return error;
}

const matcher = createPathsMatcher(tsconfigResult);

if (matcher) {
const found = matcher(value);

if (found.length === 0) return error;
}
}
},
placeholder: './src/blocks',
initialValue: initialConfig.isOk() ? initialConfig.unwrap().paths['*'] : undefined,
Expand Down Expand Up @@ -200,9 +220,9 @@ const _initProject = async (registries: string[], options: Options) => {

const repos = Array.from(
new Set([
...(initialConfig.isOk() ? initialConfig.unwrap().repos : []),
...registries,
...(options.repos ?? []),
...(initialConfig.isOk() ? initialConfig.unwrap().repos : []),
])
);

Expand Down Expand Up @@ -444,12 +464,33 @@ const promptForProviderConfig = async ({
configFiles[file.name] = result;
}

const fullFilePath = path.join(options.cwd, configFiles[file.name]);
let fullFilePath = path.join(options.cwd, configFiles[file.name]);

let originalFileContents: string | undefined;

if (fs.existsSync(fullFilePath)) {
originalFileContents = fs.readFileSync(fullFilePath).toString();
} else {
const dir = path.dirname(fullFilePath);

if (fs.existsSync(dir)) {
const matchedPath = matchJSDescendant(fullFilePath);

if (matchedPath) {
originalFileContents = fs.readFileSync(matchedPath).toString();

const newPath = path.relative(options.cwd, matchedPath);

log.warn(
`Located ${color.bold(configFiles[file.name])} at ${color.bold(newPath)}`
);

// update path
configFiles[file.name] = newPath;

fullFilePath = path.join(options.cwd, newPath);
}
}
}

loading.start(`Fetching the ${color.cyan(file.name)} from ${color.cyan(repo)}`);
Expand Down Expand Up @@ -618,7 +659,9 @@ const promptForProviderConfig = async ({
} else {
const dir = path.dirname(fullFilePath);

fs.mkdirSync(dir, { recursive: true });
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}

if (acceptedChanges) {
Expand Down
26 changes: 22 additions & 4 deletions packages/cli/src/utils/blocks/ts/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,38 @@
*
* ## Usage
* ```ts
* startsWithOneOf('a', 'a', 'b'); // true
* startsWithOneOf('c', 'a', 'b'); // false
* startsWithOneOf('ab', 'a', 'c'); // true
* startsWithOneOf('cc', 'a', 'b'); // false
* ```
*
* @param str
* @param strings
* @returns
*/
const startsWithOneOf = (str: string, strings: string[]): boolean => {
export const startsWithOneOf = (str: string, strings: string[]): boolean => {
for (const s of strings) {
if (str.startsWith(s)) return true;
}

return false;
};

export { startsWithOneOf };
/** Returns true if `str` starts with one of the provided `strings`.
*
* ## Usage
* ```ts
* endsWithOneOf('cb', 'a', 'b'); // true
* endsWithOneOf('cc', 'a', 'b'); // false
* ```
*
* @param str
* @param strings
* @returns
*/
export const endsWithOneOf = (str: string, strings: string[]): boolean => {
for (const s of strings) {
if (str.endsWith(s)) return true;
}

return false;
};
88 changes: 32 additions & 56 deletions packages/cli/src/utils/config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import fs from 'node:fs';
import { createPathsMatcher, getTsconfig } from 'get-tsconfig';
import color from 'chalk';
import { createPathsMatcher } from 'get-tsconfig';
import path from 'pathe';
import * as v from 'valibot';
import { type Block, configFileSchema, manifestMeta } from '../types';
import { Err, Ok, type Result } from './blocks/ts/result';
import { ruleConfigSchema } from './build/check';
import { tryGetTsconfig } from './files';

const PROJECT_CONFIG_NAME = 'jsrepo.json';
const REGISTRY_CONFIG_NAME = 'jsrepo-build-config.json';
export const PROJECT_CONFIG_NAME = 'jsrepo.json';
export const REGISTRY_CONFIG_NAME = 'jsrepo-build-config.json';

const formatterSchema = v.union([v.literal('prettier'), v.literal('biome')]);
export const formatterSchema = v.union([v.literal('prettier'), v.literal('biome')]);

const pathsSchema = v.objectWithRest(
export const pathsSchema = v.objectWithRest(
{
'*': v.string(),
},
Expand All @@ -20,7 +22,7 @@ const pathsSchema = v.objectWithRest(

export type Paths = v.InferInput<typeof pathsSchema>;

const projectConfigSchema = v.object({
export const projectConfigSchema = v.object({
$schema: v.string(),
repos: v.optional(v.array(v.string()), []),
includeTests: v.boolean(),
Expand All @@ -30,7 +32,7 @@ const projectConfigSchema = v.object({
formatter: v.optional(formatterSchema),
});

const getProjectConfig = (cwd: string): Result<ProjectConfig, string> => {
export const getProjectConfig = (cwd: string): Result<ProjectConfig, string> => {
if (!fs.existsSync(path.join(cwd, PROJECT_CONFIG_NAME))) {
return Err('Could not find your configuration file! Please run `init`.');
}
Expand All @@ -51,7 +53,7 @@ export type ProjectConfig = v.InferOutput<typeof projectConfigSchema>;

export type Formatter = v.InferOutput<typeof formatterSchema>;

const registryConfigSchema = v.object({
export const registryConfigSchema = v.object({
$schema: v.string(),
meta: v.optional(manifestMeta),
configFiles: v.optional(v.array(configFileSchema)),
Expand All @@ -71,7 +73,7 @@ const registryConfigSchema = v.object({
rules: v.optional(ruleConfigSchema),
});

const getRegistryConfig = (cwd: string): Result<RegistryConfig | null, string> => {
export const getRegistryConfig = (cwd: string): Result<RegistryConfig | null, string> => {
if (!fs.existsSync(path.join(cwd, REGISTRY_CONFIG_NAME))) {
return Ok(null);
}
Expand All @@ -91,61 +93,47 @@ const getRegistryConfig = (cwd: string): Result<RegistryConfig | null, string> =
export type RegistryConfig = v.InferOutput<typeof registryConfigSchema>;

/** Resolves the paths relative to the cwd */
const resolvePaths = (paths: Paths, cwd: string): Result<Paths, string> => {
let config = getTsconfig(cwd, 'tsconfig.json');
let matcher: ((specifier: string) => string[]) | null = null;
export const resolvePaths = (paths: Paths, cwd: string): Result<Paths, string> => {
const config = tryGetTsconfig(cwd).unwrapOr(null);

if (!config) {
// if we don't find the config at first check for a jsconfig
config = getTsconfig(cwd, 'jsconfig.json');
}

if (config) {
matcher = createPathsMatcher(config);
}

let newPaths: Paths;
const matcher = config ? createPathsMatcher(config) : null;

if (!paths['*'].startsWith('.')) {
if (matcher === null) {
return Err("Cannot resolve aliases because we couldn't find a tsconfig!");
}

newPaths = {
'*': resolvePath(paths['*'], matcher, cwd),
};
} else {
newPaths = {
'*': path.relative(cwd, path.join(path.resolve(cwd), paths['*'])),
};
}
const newPaths: Paths = { '*': '' };

for (const [category, p] of Object.entries(paths)) {
if (category === '*') continue; // we already resolved this one

if (p.startsWith('.')) {
if (p.startsWith('./')) {
newPaths[category] = path.relative(cwd, path.join(path.resolve(cwd), p));
continue;
}

if (matcher === null) {
return Err("Cannot resolve aliases because we couldn't find a tsconfig!");
return Err(
`Cannot resolve ${color.bold(`\`"${category}": "${p}"\``)} from paths because we couldn't find a tsconfig! If you intended to use a relative path ensure that your path starts with ${color.bold('`./`')}.`
);
}

newPaths[category] = resolvePath(p, matcher, cwd);
const resolved = tryResolvePath(p, matcher, cwd);

if (!resolved) {
return Err(
`Cannot resolve ${color.bold(`\`"${category}": "${p}"\``)} from paths because we couldn't find a matching alias in the tsconfig. If you intended to use a relative path ensure that your path starts with ${color.bold('`./`')}.`
);
}

newPaths[category] = resolved;
}

return Ok(newPaths);
};

const resolvePath = (
const tryResolvePath = (
unresolvedPath: string,
matcher: (specifier: string) => string[],
cwd: string
): string => {
): string | undefined => {
const paths = matcher(unresolvedPath);

return path.relative(cwd, paths[0]);
return paths.length > 0 ? path.relative(cwd, paths[0]) : undefined;
};

/** Gets the path where the block should be installed.
Expand All @@ -155,7 +143,7 @@ const resolvePath = (
* @param cwd
* @returns
*/
const getPathForBlock = (block: Block, resolvedPaths: Paths, cwd: string): string => {
export const getPathForBlock = (block: Block, resolvedPaths: Paths, cwd: string): string => {
let directory: string;

if (resolvedPaths[block.category] !== undefined) {
Expand All @@ -166,15 +154,3 @@ const getPathForBlock = (block: Block, resolvedPaths: Paths, cwd: string): strin

return directory;
};

export {
PROJECT_CONFIG_NAME,
REGISTRY_CONFIG_NAME,
getProjectConfig,
getRegistryConfig,
projectConfigSchema,
registryConfigSchema,
formatterSchema,
resolvePaths,
getPathForBlock,
};
Loading

0 comments on commit 1c652c0

Please sign in to comment.