Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e6f5a40
feat(actions): introduce action for creating Stylelint configuration
outslept Dec 27, 2024
b07e8a7
feat(actions): introduce action for ensuring `package.json` existence
outslept Dec 27, 2024
776bf62
feat(actions): introduce action for installing project dependencies
outslept Dec 27, 2024
456d46f
feat(prompts): introduce prompt for installing dependencies
outslept Dec 27, 2024
a9d2f84
feat(prompts): introduce prompt for selecting package manager
outslept Dec 27, 2024
e0a6fea
feat(prompts): introduce prompt for usage preference
outslept Dec 27, 2024
12b04f7
feat: introduce messages module for user feedback
outslept Dec 27, 2024
bf3932a
feat: introduce signal handler for graceful cancellation
outslept Dec 27, 2024
b7282ce
feat: orchestrate Stylelint setup process in main application
outslept Dec 27, 2024
4b55675
chore(deps): added prompts, @types/prompts and @types/semver to the p…
outslept Dec 27, 2024
0d175b2
chore: removing the old implementation
outslept Dec 27, 2024
12218d4
feat: Update package.json description and keywords for broader packag…
outslept Dec 27, 2024
d5b43b6
style: removed extra space from package description + changed the REA…
outslept Dec 27, 2024
8fc8f4c
style: typo in readme
outslept Dec 27, 2024
7399d35
chore(rename): rename install-dependencies to install
outslept Dec 27, 2024
3ea00a9
chore: removing process-handlers
outslept Dec 27, 2024
1ded092
chore: removing messages file
outslept Dec 27, 2024
bc4c36b
feat(shell): created a mini-execa version
outslept Dec 27, 2024
13449ff
feat: took messages from a separate file directly into functions, add…
outslept Dec 27, 2024
b8372e4
chore: removing is cancelled from index.ts
outslept Dec 27, 2024
d7fbace
chore(meta): making keywords more concise
outslept Dec 27, 2024
df390ff
chore: removing extra [
outslept Dec 27, 2024
60af269
Merge branch 'idkgene/pkg' of https://github.com/idkgene/create-style…
outslept Dec 27, 2024
b9893d2
refactor: simplify install command by using 'add' directly
outslept Dec 28, 2024
93eed34
refactor: creating package.json with an `add` command, not the `init`
outslept Dec 28, 2024
7154888
refactor: removing usage-preference + adding package manager dependan…
outslept Dec 28, 2024
283d24a
feat: throwing an error with not package.json existing
outslept Dec 28, 2024
e0a9bdd
feat: more flexible post-install
outslept Dec 28, 2024
3ad07e0
feat: more tweaks to the project
outslept Dec 28, 2024
d12d28b
feat: cancel support for prompts
outslept Dec 28, 2024
239b62b
feat: major updates
outslept Jan 1, 2025
a8c1f11
tests: isWriteable
outslept Jan 1, 2025
d1b5bde
tests: helpers
outslept Jan 1, 2025
0f6184e
tests: shell
outslept Jan 1, 2025
8ce881a
tests: post-install
outslept Jan 1, 2025
96f0269
tests: help
outslept Jan 1, 2025
43dbf9a
feat: partial fixes
outslept Jan 5, 2025
ff7e27b
more fixes
outslept Jan 8, 2025
2e148ea
more tests, vitest configuration, esbuild setup, some pedantic naming…
outslept Jan 13, 2025
37dd272
npm-run-all is no longer maintained. migrated to concurrently
outslept Jan 13, 2025
1e1d526
refactored the code to reduce overall complexity, added some of the t…
outslept Feb 18, 2025
1c09c8f
minor changes to fix the previous problems, full unit tests suite add…
outslept Feb 25, 2025
1edacbf
removing eslint disable comment
outslept Feb 25, 2025
ad7d5f6
removing mentions of nvm because nodejs/download mentions them already
outslept Feb 25, 2025
1162ec9
comment about init command
outslept Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 14 additions & 20 deletions src/actions/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,37 @@ const CONFIG_FILE = 'stylelint.config.mjs';
const DEFAULT_CONFIG_CONTENT = `export default {
extends: ['stylelint-config-standard']
};`;
const INFO_PADDING = ' ';

async function findExistingConfig(): Promise<string | null> {
const configPath = path.join(process.cwd(), CONFIG_FILE);
const localConfigPath = path.join(process.cwd(), CONFIG_FILE);

try {
await fs.access(configPath);
await fs.access(localConfigPath);

return configPath;
return localConfigPath;
} catch {
const explorer = cosmiconfig('stylelint');
const result = await explorer.search();
const { filepath } = (await cosmiconfig('stylelint').search()) || {};

return result?.filepath ?? null;
return filepath ?? null;
}
}

function displayConfigPreview(): void {
newline();
log(
`${INFO_PADDING}${bgMagenta(white(' INFO '))}${' '.repeat(8)}${magenta(
'Creating Stylelint configuration file...',
)}`,
` ${bgMagenta(white(' INFO '))}${' '.repeat(8)}${magenta('Creating Stylelint configuration file...')}`,
);
log(`${' '.repeat(16)}The following configuration will be added to your project:`);
log(` ${' '.repeat(14)}The following configuration will be added to your project:`);
newline();

const configLines = DEFAULT_CONFIG_CONTENT.split('\n');
const boxContent = createBox(DEFAULT_CONFIG_CONTENT.split('\n'), {
fileName: CONFIG_FILE,
fileNameColor: cyan,
borderColor: gray,
textColor: white,
});

log(
`${createBox(configLines, {
fileName: CONFIG_FILE,
fileNameColor: cyan,
borderColor: gray,
textColor: white,
})}\n`,
);
log(`${boxContent}\n`);
}

export async function setupStylelintConfig(context: Context): Promise<void> {
Expand Down
12 changes: 6 additions & 6 deletions src/actions/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ export async function createContext(originalArgv: string[]): Promise<Context> {
);

const packageManagerFlags = {
'--use-npm': flags['--use-npm'],
'--use-yarn': flags['--use-yarn'],
'--use-pnpm': flags['--use-pnpm'],
'--use-bun': flags['--use-bun'],
'--use-deno': flags['--use-deno'],
'--use-npm': flags['--use-npm'] ?? false,
'--use-yarn': flags['--use-yarn'] ?? false,
'--use-pnpm': flags['--use-pnpm'] ?? false,
'--use-bun': flags['--use-bun'] ?? false,
'--use-deno': flags['--use-deno'] ?? false,
};

return {
Expand All @@ -58,7 +58,7 @@ export async function createContext(originalArgv: string[]): Promise<Context> {
isDryRun: Boolean(flags['--dry-run']),
shouldSkipInstall: Boolean(flags['--no-install']),
cwd: new URL(`${pathToFileURL(process.cwd())}/`),
packageManager: await getPackageManager(packageManagerFlags),
packageManager: getPackageManager(packageManagerFlags),
exit: (code) => process.exit(code),
};
}
55 changes: 26 additions & 29 deletions src/actions/help.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
import { blue, dim } from 'picocolors';
import { log } from '../utils/logger.js';

const INDENT = ' ';
const PM_OPTIONS = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const;

export function showHelp(): void {
const INDENT = {
SECTION: ' '.repeat(2),
OPTION: ' '.repeat(2),
};

const SPACES = {
// Alignment for the second line:
// Calculated as:
// - "Usage: " = 7 chars
// - "create-stylelint" = 15 chars
// - trailing space = 2 spaces
// Total = 24 spaces
SECOND_LINE: ' '.repeat(24),
};

log(`Usage: create-stylelint [-v | --version] [-h | --help] [--dry-run] [--no-install | --no-color]
${SPACES.SECOND_LINE}[--use-npm | --use-pnpm | --use-yarn | --use-bun | --use-deno]
const usageLine = 'Usage: create-stylelint [-v | --version] [-h | --help] [--dry-run] [--no-install | --no-color]';
const pmOptions = PM_OPTIONS.map(pm => `--use-${pm}`).join(' | ');

log(`${usageLine}
${' '.repeat(24)}[${pmOptions}]

Options:
${INDENT.OPTION}--use-npm ${dim('Use npm as package manager')}
${INDENT.OPTION}--use-pnpm ${dim('Use pnpm as package manager')}
${INDENT.OPTION}--use-yarn ${dim('Use yarn as package manager')}
${INDENT.OPTION}--use-bun ${dim('Use bun as package manager')}
${INDENT.OPTION}--use-deno ${dim('Use deno as package manager')}
${createPMOptions()}

${INDENT.OPTION}--dry-run ${dim('Preview changes without applying them')}
${INDENT.OPTION}--no-install ${dim('Skip dependency installation')}
${INDENT.OPTION}--no-color ${dim('Disable color')}
${createOption('--dry-run', 'Preview changes without applying them')}
${createOption('--no-install', 'Skip dependency installation')}
${createOption('--no-color', 'Disable color')}

${INDENT.OPTION}-h, --help ${dim('Show this help message')}
${INDENT.OPTION}-v, --version ${dim('Show version information')}
${createOption('-h, --help', 'Show this help message')}
${createOption('-v, --version', 'Show version information')}

Examples:
${INDENT.OPTION}create-stylelint
${INDENT.OPTION}create-stylelint --use-npm --no-install
${INDENT}create-stylelint
${INDENT}create-stylelint --use-npm --no-install

${dim('Need help?')} ${blue('https://github.com/stylelint/create-stylelint')}
`);
}

function createOption(flags: string, description: string) {
return `${INDENT}${flags.padEnd(16)} ${dim(description)}`;
}

function createPMOptions() {
return PM_OPTIONS.map(pm =>
createOption(`--use-${pm}`, `Use ${pm} as package manager`)
).join('\n');
}
35 changes: 15 additions & 20 deletions src/actions/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ import { getInstallConfirmation } from '../prompts/install.js';
import { resolvePackageVersion } from '../utils/registry.js';

const REQUIRED_PACKAGES = ['stylelint', 'stylelint-config-standard'];
const INFO_PREFIX = `${' '.repeat(2)}${bgMagenta(white(' INFO '))}${' '.repeat(8)}`;
const COMMAND_INFO_TEXT = magenta('Stylelint will run the following command:');
const DEPENDENCY_INSTALL_TEXT = 'Installing the necessary Stylelint dependencies...';

export async function installDependencies(context: Context): Promise<void> {
if (context.shouldSkipInstall) {
Expand All @@ -22,42 +19,40 @@ export async function installDependencies(context: Context): Promise<void> {
return;
}

const resolvedPackages = await Promise.all(
REQUIRED_PACKAGES.map(async (packageName) => ({
packageName,
requestedVersion: await resolvePackageVersion(packageName),
const packageVersions = await Promise.all(
REQUIRED_PACKAGES.map(async (pkg) => ({
packageName: pkg,
requestedVersion: await resolvePackageVersion(pkg),
})),
);

const installCommand = getInstallCommand(context.packageManager!, resolvedPackages);
const installCommand = getInstallCommand(context.packageManager!, packageVersions);
const infoPrefix = `${' '}${bgMagenta(white(' INFO '))}${' '.repeat(8)}`;

// Display command preview
newline();
log(`${INFO_PREFIX}${COMMAND_INFO_TEXT}`);
log(`${' '.repeat(16)}If you skip this step, you can always run it yourself later`);
log(`${infoPrefix}${magenta('Stylelint will run the following command:')}`);
log(`${' '.repeat(8)}If you skip this step, you can always run it yourself later`);
newline();
log(`${createBox([installCommand])}\n`);

const shouldProceed = await getInstallConfirmation(context);

if (!shouldProceed) return;
if (!(await getInstallConfirmation(context))) return;

if (context.isDryRun) {
logAction('--dry-run', 'Skipping dependency installation');

return;
}

const installationSpinner = ora(DEPENDENCY_INSTALL_TEXT).start();
const spinner = ora('Installing the necessary Stylelint dependencies...').start();

try {
const [...args] = installCommand.split(' ');
const [command, ...args] = installCommand.split(' ');

await x(context.packageManager!, args, {
nodeOptions: { cwd: process.cwd() },
});
installationSpinner.succeed('Successfully installed dependencies');
await x(command!, args, { nodeOptions: { cwd: process.cwd() } });
spinner.succeed('Successfully installed dependencies');
} catch (error) {
installationSpinner.fail(`Failed to install dependencies: ${(error as Error).message}`);
spinner.fail(`Failed to install dependencies: ${(error as Error).message}`);
log(dim('Please check your network connection and try again.\n'));
context.exit(1);
}
Expand Down
13 changes: 6 additions & 7 deletions src/actions/post-setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { bgGreen, gray, green, white } from 'picocolors';

import { log, newline } from '../utils/logger.js';
import type { Context } from './context.js';
import { getLintCommand } from '../utils/package-manager-commands.js';
Expand All @@ -11,9 +10,9 @@ async function showNextSteps(context: Context): Promise<void> {
newline();

log(
`${' '.repeat(2)}${bgGreen(white(' SUCCESS '))}${' '.repeat(5)}${green(
'Project setup complete!',
)}`,
`${' '.repeat(2)}${bgGreen(white(' SUCCESS '))}${' '.repeat(5)}${green(
'Project setup complete!',
)}`,
);

log(`${' '.repeat(16)}Run the following command to start linting:`);
Expand All @@ -22,9 +21,9 @@ async function showNextSteps(context: Context): Promise<void> {
newline();

log(
`${' '.repeat(2)}${gray(
'Need to customize?:',
)}`,
`${' '.repeat(2)}${gray(
'Need to customize?:',
)}`,
);

log(`${' '.repeat(2)}${'https://stylelint.io/user-guide/configure/'}`);
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export async function main(): Promise<void> {
log(`create-stylelint v${version}`);
process.exit(0);
} catch (error) {
console.error('Failed to get version:', error instanceof Error ? error.message : error);
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
}
Expand Down
39 changes: 16 additions & 23 deletions src/prompts/config.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
import { bgYellow, black, yellow } from 'picocolors';

import { log, newline } from '../utils/logger.js';
import type { Context } from '../actions/context.js';

export async function getConfigConfirmation(context: Context): Promise<boolean> {
const response = await context.prompt({
type: 'confirm',
name: 'proceed',
message: 'Would you like to create a Stylelint configuration file for your project?',
initial: true,
}, {
onCancel: () => {
newline();
const handleCancel = () => {
newline();
log(
`${' '.repeat(2)}${bgYellow(black(' CANCEL '))}${' '.repeat(6)}${yellow(
`${' '}${bgYellow(black(' CANCEL '))}${' '}${yellow(
'Stylelint configuration setup cancelled. If you change your mind, you can always run the configuration setup again.',
)}`,
);
newline();

context.exit(1);
}
});
};

if (!response.proceed) {
newline();
log(
`${' '.repeat(2)}${bgYellow(black(' CANCEL '))}${' '.repeat(6)}${yellow(
'Stylelint configuration setup cancelled. If you change your mind, you can always run the configuration setup again.',
)}`,
);
newline();
const { proceed } = await context.prompt(
{
type: 'confirm',
name: 'proceed',
message: 'Would you like to create a Stylelint configuration file for your project?',
initial: true,
},
{
onCancel: handleCancel,
},
);

context.exit(1);
}
if (!proceed) handleCancel();

return true;
}
13 changes: 6 additions & 7 deletions src/prompts/install.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { bgYellow, black, yellow } from 'picocolors';

import { log, newline } from '../utils/logger.js';
import type { Context } from '../actions/context.js';

export async function getInstallConfirmation(context: Context): Promise<boolean> {
const response = await context.prompt({
const { proceed } = await context.prompt({
type: 'confirm',
name: 'proceed',
message: 'Install Stylelint dependencies?',
initial: true,
});

if (!response.proceed) {
if (!proceed) {
const cancelPrefix = `${' '}${bgYellow(black(' CANCEL '))}${' '.repeat(6)}`;

newline();
log(
`${' '.repeat(2)}${bgYellow(black(' CANCEL '))}${' '.repeat(6)}${yellow(
'Skipping dependency installation for now. You can always run the install command again later.',
)}`,
`${cancelPrefix}${yellow('Skipping dependency installation for now. You can always run the install command again later.')}`,
);
newline();
}

return response.proceed;
return proceed;
}
Loading