[ci] format

This commit is contained in:
natemoo-re 2023-02-06 16:21:48 +00:00 committed by fredkbot
parent 8d2187d8b8
commit 6a59531ff9
20 changed files with 409 additions and 298 deletions

View file

@ -1,7 +1,7 @@
import os from 'node:os';
import arg from 'arg';
import detectPackageManager from 'which-pm-runs';
import { prompt } from '@astrojs/cli-kit';
import arg from 'arg';
import os from 'node:os';
import detectPackageManager from 'which-pm-runs';
import { getName, getVersion } from '../messages.js';
@ -26,27 +26,29 @@ export interface Context {
exit(code: number): never;
}
export async function getContext(argv: string[]): Promise<Context> {
const flags = arg({
'--template': String,
'--ref': String,
'--yes': Boolean,
'--no': Boolean,
'--install': Boolean,
'--no-install': Boolean,
'--git': Boolean,
'--no-git': Boolean,
'--typescript': String,
'--skip-houston': Boolean,
'--dry-run': Boolean,
'--help': Boolean,
'--fancy': Boolean,
const flags = arg(
{
'--template': String,
'--ref': String,
'--yes': Boolean,
'--no': Boolean,
'--install': Boolean,
'--no-install': Boolean,
'--git': Boolean,
'--no-git': Boolean,
'--typescript': String,
'--skip-houston': Boolean,
'--dry-run': Boolean,
'--help': Boolean,
'--fancy': Boolean,
'-y': '--yes',
'-n': '--no',
'-h': '--help',
}, { argv, permissive: true });
'-y': '--yes',
'-n': '--no',
'-h': '--help',
},
{ argv, permissive: true }
);
const pkgManager = detectPackageManager()?.name ?? 'npm';
const [username, version] = await Promise.all([getName(), getVersion()]);
@ -75,8 +77,10 @@ export async function getContext(argv: string[]): Promise<Context> {
if (typescript == undefined) typescript = 'strict';
}
skipHouston = ((os.platform() === 'win32' && !fancy) || skipHouston) ?? [yes, no, install, git, typescript].some((v) => v !== undefined);
skipHouston =
((os.platform() === 'win32' && !fancy) || skipHouston) ??
[yes, no, install, git, typescript].some((v) => v !== undefined);
const context: Context = {
help,
prompt,
@ -95,7 +99,7 @@ export async function getContext(argv: string[]): Promise<Context> {
cwd,
exit(code) {
process.exit(code);
}
}
},
};
return context;
}

View file

@ -1,9 +1,11 @@
import type { Context } from "./context";
import type { Context } from './context';
import { title, info, spinner } from '../messages.js';
import { execa } from 'execa';
import { info, spinner, title } from '../messages.js';
export async function dependencies(ctx: Pick<Context, 'install'|'yes'|'prompt'|'pkgManager'|'cwd'|'dryRun'>) {
export async function dependencies(
ctx: Pick<Context, 'install' | 'yes' | 'prompt' | 'pkgManager' | 'cwd' | 'dryRun'>
) {
let deps = ctx.install ?? ctx.yes;
if (deps === undefined) {
({ deps } = await ctx.prompt({
@ -33,11 +35,10 @@ export async function dependencies(ctx: Pick<Context, 'install'|'yes'|'prompt'|'
}
}
async function install({ pkgManager, cwd }: { pkgManager: string, cwd: string }) {
const installExec = execa(pkgManager, ['install'], { cwd });
return new Promise<void>((resolve, reject) => {
installExec.on('error', (error) => reject(error));
installExec.on('close', () => resolve());
});
async function install({ pkgManager, cwd }: { pkgManager: string; cwd: string }) {
const installExec = execa(pkgManager, ['install'], { cwd });
return new Promise<void>((resolve, reject) => {
installExec.on('error', (error) => reject(error));
installExec.on('close', () => resolve());
});
}

View file

@ -1,15 +1,15 @@
import type { Context } from "./context";
import fs from 'node:fs';
import path from 'node:path';
import type { Context } from './context';
import { color } from '@astrojs/cli-kit';
import { title, info, spinner } from '../messages.js';
import { execa } from 'execa';
import { info, spinner, title } from '../messages.js';
export async function git(ctx: Pick<Context, 'cwd'|'git'|'yes'|'prompt'|'dryRun'>) {
export async function git(ctx: Pick<Context, 'cwd' | 'git' | 'yes' | 'prompt' | 'dryRun'>) {
if (fs.existsSync(path.join(ctx.cwd, '.git'))) {
await info('Nice!', `Git has already been initialized`);
return
return;
}
let _git = ctx.git ?? ctx.yes;
if (_git === undefined) {
@ -43,6 +43,15 @@ async function init({ cwd }: { cwd: string }) {
try {
await execa('git', ['init'], { cwd, stdio: 'ignore' });
await execa('git', ['add', '-A'], { cwd, stdio: 'ignore' });
await execa('git', ['commit', '-m', 'Initial commit from Astro', '--author="houston[bot] <astrobot-houston@users.noreply.github.com>"'], { cwd, stdio: 'ignore' });
await execa(
'git',
[
'commit',
'-m',
'Initial commit from Astro',
'--author="houston[bot] <astrobot-houston@users.noreply.github.com>"',
],
{ cwd, stdio: 'ignore' }
);
} catch (e) {}
}

View file

@ -1,10 +1,10 @@
import { type Context } from './context';
import { banner, welcome, say } from '../messages.js';
import { label, color } from '@astrojs/cli-kit';
import { color, label } from '@astrojs/cli-kit';
import { random } from '@astrojs/cli-kit/utils';
import { banner, say, welcome } from '../messages.js';
export async function intro(ctx: Pick<Context, 'skipHouston'|'version'|'username'>) {
export async function intro(ctx: Pick<Context, 'skipHouston' | 'version' | 'username'>) {
if (!ctx.skipHouston) {
await say([
[

View file

@ -1,9 +1,9 @@
import { Context } from "./context";
import path from 'node:path';
import { Context } from './context';
import { nextSteps, say } from '../messages.js';
export async function next(ctx: Pick<Context, 'cwd'|'pkgManager'|'skipHouston'>) {
export async function next(ctx: Pick<Context, 'cwd' | 'pkgManager' | 'skipHouston'>) {
let projectDir = path.relative(process.cwd(), ctx.cwd);
const devCmd = ctx.pkgManager === 'npm' ? 'npm run dev' : `${ctx.pkgManager} dev`;
await nextSteps({ projectDir, devCmd });

View file

@ -1,12 +1,12 @@
import type { Context } from "./context";
import type { Context } from './context';
import { color, generateProjectName } from '@astrojs/cli-kit';
import { title, info, log } from '../messages.js';
import path from 'node:path';
import { info, log, title } from '../messages.js';
import { isEmpty, toValidName } from './shared.js';
export async function projectName(ctx: Pick<Context, 'cwd'|'prompt'|'projectName'|'exit'>) {
export async function projectName(ctx: Pick<Context, 'cwd' | 'prompt' | 'projectName' | 'exit'>) {
await checkCwd(ctx.cwd);
if (!ctx.cwd || !isEmpty(ctx.cwd)) {

View file

@ -42,20 +42,18 @@ export function isEmpty(dirPath: string) {
}
export function isValidName(projectName: string) {
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
projectName,
)
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(projectName);
}
export function toValidName(projectName: string) {
if (isValidName(projectName)) return projectName;
return projectName
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z\d\-~]+/g, '-')
return projectName
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z\d\-~]+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '')
.replace(/-+$/, '');
}

View file

@ -1,14 +1,15 @@
/* eslint no-console: 'off' */
import type { Context } from "./context";
import type { Context } from './context';
import { color } from '@astrojs/cli-kit';
import { downloadTemplate } from 'giget';
import fs from 'node:fs';
import path from 'node:path';
import { downloadTemplate } from 'giget';
import { error } from '../messages.js';
import { color } from '@astrojs/cli-kit';
import { title, info, spinner } from '../messages.js';
import { error, info, spinner, title } from '../messages.js';
export async function template(ctx: Pick<Context, 'template'|'prompt'|'dryRun'|'exit'|'exit'>) {
export async function template(
ctx: Pick<Context, 'template' | 'prompt' | 'dryRun' | 'exit' | 'exit'>
) {
if (!ctx.template) {
const { template: tmpl } = await ctx.prompt({
name: 'template',
@ -43,18 +44,27 @@ export async function template(ctx: Pick<Context, 'template'|'prompt'|'dryRun'|'
// some files are only needed for online editors when using astro.new. Remove for create-astro installs.
const FILES_TO_REMOVE = ['sandbox.config.json', 'CHANGELOG.md'];
const FILES_TO_UPDATE = {
'package.json': (file: string, overrides: { name: string }) => fs.promises.readFile(file, 'utf-8').then(value => (
fs.promises.writeFile(file, JSON.stringify(Object.assign(JSON.parse(value), Object.assign(overrides, { private: undefined })), null, '\t'), 'utf-8')
))
}
'package.json': (file: string, overrides: { name: string }) =>
fs.promises
.readFile(file, 'utf-8')
.then((value) =>
fs.promises.writeFile(
file,
JSON.stringify(
Object.assign(JSON.parse(value), Object.assign(overrides, { private: undefined })),
null,
'\t'
),
'utf-8'
)
),
};
export default async function copyTemplate(tmpl: string, ctx: Context) {
const ref = ctx.ref || 'latest';
const isThirdParty = tmpl.includes('/');
const templateTarget = isThirdParty
? tmpl
: `github:withastro/astro/examples/${tmpl}#${ref}`;
const templateTarget = isThirdParty ? tmpl : `github:withastro/astro/examples/${tmpl}#${ref}`;
// Copy
if (!ctx.dryRun) {
@ -64,7 +74,7 @@ export default async function copyTemplate(tmpl: string, ctx: Context) {
provider: 'github',
cwd: ctx.cwd,
dir: '.',
})
});
} catch (err: any) {
fs.rmdirSync(ctx.cwd);
if (err.message.includes('404')) {
@ -85,9 +95,9 @@ export default async function copyTemplate(tmpl: string, ctx: Context) {
const updateFiles = Object.entries(FILES_TO_UPDATE).map(async ([file, update]) => {
const fileLoc = path.resolve(path.join(ctx.cwd, file));
if (fs.existsSync(fileLoc)) {
return update(fileLoc, { name: ctx.projectName! })
return update(fileLoc, { name: ctx.projectName! });
}
})
});
await Promise.all([...removeFiles, ...updateFiles]);
}

View file

@ -1,13 +1,15 @@
import type { Context } from "./context";
import type { Context } from './context';
import fs from 'node:fs'
import { readFile } from 'node:fs/promises'
import { color } from '@astrojs/cli-kit';
import fs from 'node:fs';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import stripJsonComments from 'strip-json-comments';
import { color } from '@astrojs/cli-kit';
import { title, info, error, typescriptByDefault, spinner } from '../messages.js';
import { error, info, spinner, title, typescriptByDefault } from '../messages.js';
export async function typescript(ctx: Pick<Context, 'typescript'|'yes'|'prompt'|'dryRun'|'cwd'|'exit'>) {
export async function typescript(
ctx: Pick<Context, 'typescript' | 'yes' | 'prompt' | 'dryRun' | 'cwd' | 'exit'>
) {
let ts = ctx.typescript ?? (typeof ctx.yes !== 'undefined' ? 'strict' : undefined);
if (ts === undefined) {
const { useTs } = await ctx.prompt({
@ -68,7 +70,7 @@ export async function typescript(ctx: Pick<Context, 'typescript'|'yes'|'prompt'|
export async function setupTypeScript(value: string, { cwd }: { cwd: string }) {
const templateTSConfigPath = path.join(cwd, 'tsconfig.json');
try {
const data = await readFile(templateTSConfigPath, { encoding: 'utf-8' })
const data = await readFile(templateTSConfigPath, { encoding: 'utf-8' });
const templateTSConfig = JSON.parse(stripJsonComments(data));
if (templateTSConfig && typeof templateTSConfig === 'object') {
const result = Object.assign(templateTSConfig, {
@ -77,7 +79,9 @@ export async function setupTypeScript(value: string, { cwd }: { cwd: string }) {
fs.writeFileSync(templateTSConfigPath, JSON.stringify(result, null, 2));
} else {
throw new Error("There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed")
throw new Error(
"There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed"
);
}
} catch (err) {
if (err && (err as any).code === 'ENOENT') {

View file

@ -1,18 +1,18 @@
import { getContext } from './actions/context.js';
import { setStdout } from './messages.js';
import { help } from './actions/help.js';
import { intro } from './actions/intro.js';
import { projectName } from './actions/project-name.js';
import { template } from './actions/template.js'
import { dependencies } from './actions/dependencies.js';
import { git } from './actions/git.js';
import { typescript, setupTypeScript } from './actions/typescript.js';
import { help } from './actions/help.js';
import { intro } from './actions/intro.js';
import { next } from './actions/next-steps.js';
import { projectName } from './actions/project-name.js';
import { template } from './actions/template.js';
import { setupTypeScript, typescript } from './actions/typescript.js';
import { setStdout } from './messages.js';
const exit = () => process.exit(0)
process.on('SIGINT', exit)
process.on('SIGTERM', exit)
const exit = () => process.exit(0);
process.on('SIGINT', exit);
process.on('SIGTERM', exit);
// Please also update the installation instructions in the docs at
// https://github.com/withastro/docs/blob/main/src/pages/en/install/auto.md
@ -29,18 +29,10 @@ export async function main() {
return;
}
const steps = [
intro,
projectName,
template,
dependencies,
git,
typescript,
next
]
const steps = [intro, projectName, template, dependencies, git, typescript, next];
for (const step of steps) {
await step(ctx)
await step(ctx);
}
process.exit(0);
}
@ -55,5 +47,5 @@ export {
git,
typescript,
setupTypeScript,
next
}
next,
};

View file

@ -1,8 +1,8 @@
/* eslint no-console: 'off' */
import { color, label, say as houston, spinner as load } from '@astrojs/cli-kit';
import { align, sleep } from '@astrojs/cli-kit/utils';
import { exec } from 'node:child_process';
import { get } from 'node:https';
import { color, label, spinner as load, say as houston } from '@astrojs/cli-kit';
import { sleep, align } from '@astrojs/cli-kit/utils';
import stripAnsi from 'strip-ansi';
let stdout = process.stdout;
@ -11,113 +11,136 @@ export function setStdout(writable: typeof process.stdout) {
stdout = writable;
}
export async function say(messages: string|string[], { clear = false, hat = '' } = {}) {
export async function say(messages: string | string[], { clear = false, hat = '' } = {}) {
return houston(messages, { clear, hat, stdout });
}
export async function spinner(args: { start: string; end: string; while: (...args: any) => Promise<any>; }) {
export async function spinner(args: {
start: string;
end: string;
while: (...args: any) => Promise<any>;
}) {
await load(args, { stdout });
}
export const title = (text: string) => align(label(text), 'end', 7) + ' ';
export const welcome = [
`Let's claim your corner of the internet.`,
`I'll be your assistant today.`,
`Let's build something awesome!`,
`Let's build something great!`,
`Let's build something fast!`,
`Let's build the web we want.`,
`Let's make the web weird!`,
`Let's make the web a better place!`,
`Let's create a new project!`,
`Let's create something unique!`,
`Time to build a new website.`,
`Time to build a faster website.`,
`Time to build a sweet new website.`,
`We're glad to have you on board.`,
`Keeping the internet weird since 2021.`,
`Initiating launch sequence...`,
`Initiating launch sequence... right... now!`,
`Awaiting further instructions.`,
]
`Let's claim your corner of the internet.`,
`I'll be your assistant today.`,
`Let's build something awesome!`,
`Let's build something great!`,
`Let's build something fast!`,
`Let's build the web we want.`,
`Let's make the web weird!`,
`Let's make the web a better place!`,
`Let's create a new project!`,
`Let's create something unique!`,
`Time to build a new website.`,
`Time to build a faster website.`,
`Time to build a sweet new website.`,
`We're glad to have you on board.`,
`Keeping the internet weird since 2021.`,
`Initiating launch sequence...`,
`Initiating launch sequence... right... now!`,
`Awaiting further instructions.`,
];
export const getName = () => new Promise<string>((resolve) => {
exec('git config user.name', { encoding: 'utf-8' }, (_1, gitName, _2) => {
if (gitName.trim()) {
return resolve(gitName.split(' ')[0].trim());
}
exec('whoami', { encoding: 'utf-8' }, (_3, whoami, _4) => {
if (whoami.trim()) {
return resolve(whoami.split(' ')[0].trim());
}
return resolve('astronaut');
});
});
});
export const getName = () =>
new Promise<string>((resolve) => {
exec('git config user.name', { encoding: 'utf-8' }, (_1, gitName, _2) => {
if (gitName.trim()) {
return resolve(gitName.split(' ')[0].trim());
}
exec('whoami', { encoding: 'utf-8' }, (_3, whoami, _4) => {
if (whoami.trim()) {
return resolve(whoami.split(' ')[0].trim());
}
return resolve('astronaut');
});
});
});
let v: string;
export const getVersion = () => new Promise<string>((resolve) => {
if (v) return resolve(v);
get('https://registry.npmjs.org/astro/latest', (res) => {
let body = '';
res.on('data', chunk => body += chunk)
res.on('end', () => {
const { version } = JSON.parse(body);
v = version;
resolve(version);
})
})
})
export const getVersion = () =>
new Promise<string>((resolve) => {
if (v) return resolve(v);
get('https://registry.npmjs.org/astro/latest', (res) => {
let body = '';
res.on('data', (chunk) => (body += chunk));
res.on('end', () => {
const { version } = JSON.parse(body);
v = version;
resolve(version);
});
});
});
export const log = (message: string) => stdout.write(message + "\n");
export const banner = async (version: string) => log(`\n${label('astro', color.bgGreen, color.black)} ${color.green(color.bold(`v${version}`))} ${color.bold('Launch sequence initiated.')}`);
export const log = (message: string) => stdout.write(message + '\n');
export const banner = async (version: string) =>
log(
`\n${label('astro', color.bgGreen, color.black)} ${color.green(
color.bold(`v${version}`)
)} ${color.bold('Launch sequence initiated.')}`
);
export const info = async (prefix: string, text: string) => {
await sleep(100)
if (stdout.columns < 80) {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)}`);
log(`${' '.repeat(9)}${color.dim(text)}`);
} else {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)} ${color.dim(text)}`);
}
}
await sleep(100);
if (stdout.columns < 80) {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)}`);
log(`${' '.repeat(9)}${color.dim(text)}`);
} else {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)} ${color.dim(text)}`);
}
};
export const error = async (prefix: string, text: string) => {
if (stdout.columns < 80) {
log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)}`);
log(`${' '.repeat(9)}${color.dim(text)}`);
} else {
log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)} ${color.dim(text)}`);
}
}
if (stdout.columns < 80) {
log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)}`);
log(`${' '.repeat(9)}${color.dim(text)}`);
} else {
log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)} ${color.dim(text)}`);
}
};
export const typescriptByDefault = async () => {
await info(`No worries!`, 'TypeScript is supported in Astro by default,');
log(`${' '.repeat(9)}${color.dim('but you are free to continue writing JavaScript instead.')}`);
await sleep(1000);
}
await info(`No worries!`, 'TypeScript is supported in Astro by default,');
log(`${' '.repeat(9)}${color.dim('but you are free to continue writing JavaScript instead.')}`);
await sleep(1000);
};
export const nextSteps = async ({ projectDir, devCmd }: { projectDir: string, devCmd: string }) => {
const max = stdout.columns;
const prefix = max < 80 ? ' ' : ' '.repeat(9);
await sleep(200);
log(`\n ${color.bgCyan(` ${color.black('next')} `)} ${color.bold('Liftoff confirmed. Explore your project!')}`)
await sleep(100);
if (projectDir !== '') {
const enter = [`\n${prefix}Enter your project directory using`, color.cyan(`cd ./${projectDir}`, '')];
const len = enter[0].length + stripAnsi(enter[1]).length;
log(enter.join((len > max) ? '\n' + prefix : ' '));
}
log(`${prefix}Run ${color.cyan(devCmd)} to start the dev server. ${color.cyan('CTRL+C')} to stop.`)
await sleep(100);
log(`${prefix}Add frameworks like ${color.cyan(`react`)} or ${color.cyan('tailwind')} using ${color.cyan('astro add')}.`)
await sleep(100);
log(`\n${prefix}Stuck? Join us at ${color.cyan(`https://astro.build/chat`)}`)
await sleep(200);
}
export const nextSteps = async ({ projectDir, devCmd }: { projectDir: string; devCmd: string }) => {
const max = stdout.columns;
const prefix = max < 80 ? ' ' : ' '.repeat(9);
await sleep(200);
log(
`\n ${color.bgCyan(` ${color.black('next')} `)} ${color.bold(
'Liftoff confirmed. Explore your project!'
)}`
);
await sleep(100);
if (projectDir !== '') {
const enter = [
`\n${prefix}Enter your project directory using`,
color.cyan(`cd ./${projectDir}`, ''),
];
const len = enter[0].length + stripAnsi(enter[1]).length;
log(enter.join(len > max ? '\n' + prefix : ' '));
}
log(
`${prefix}Run ${color.cyan(devCmd)} to start the dev server. ${color.cyan('CTRL+C')} to stop.`
);
await sleep(100);
log(
`${prefix}Add frameworks like ${color.cyan(`react`)} or ${color.cyan(
'tailwind'
)} using ${color.cyan('astro add')}.`
);
await sleep(100);
log(`\n${prefix}Stuck? Join us at ${color.cyan(`https://astro.build/chat`)}`);
await sleep(200);
};
export function printHelp({
commandName,
@ -154,9 +177,7 @@ export function printHelp({
if (headline) {
message.push(
linebreak(),
`${title(commandName)} ${color.green(
`v${process.env.PACKAGE_VERSION ?? ''}`
)} ${headline}`
`${title(commandName)} ${color.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}`
);
}

View file

@ -10,53 +10,53 @@ describe('context', () => {
expect(ctx.template).to.be.undefined;
expect(ctx.skipHouston).to.eq(os.platform() === 'win32');
expect(ctx.dryRun).to.be.undefined;
})
});
it('project name', async () => {
const ctx = await getContext(['foobar']);
expect(ctx.projectName).to.eq('foobar');
})
});
it('template', async () => {
const ctx = await getContext(['--template', 'minimal']);
expect(ctx.template).to.eq('minimal');
})
});
it('skip houston (explicit)', async () => {
const ctx = await getContext(['--skip-houston']);
expect(ctx.skipHouston).to.eq(true);
})
});
it('skip houston (yes)', async () => {
const ctx = await getContext(['-y']);
expect(ctx.skipHouston).to.eq(true);
})
});
it('skip houston (no)', async () => {
const ctx = await getContext(['-n']);
expect(ctx.skipHouston).to.eq(true);
})
});
it('skip houston (install)', async () => {
const ctx = await getContext(['--install']);
expect(ctx.skipHouston).to.eq(true);
})
});
it('dry run', async () => {
const ctx = await getContext(['--dry-run']);
expect(ctx.dryRun).to.eq(true);
})
});
it('install', async () => {
const ctx = await getContext(['--install']);
expect(ctx.install).to.eq(true);
})
});
it('no install', async () => {
const ctx = await getContext(['--no-install']);
expect(ctx.install).to.eq(false);
})
});
it('git', async () => {
const ctx = await getContext(['--git']);
expect(ctx.git).to.eq(true);
})
});
it('no git', async () => {
const ctx = await getContext(['--no-git']);
expect(ctx.git).to.eq(false);
})
});
it('typescript', async () => {
const ctx = await getContext(['--typescript', 'strict']);
expect(ctx.typescript).to.eq('strict');
})
})
});
});

View file

@ -7,36 +7,66 @@ describe('dependencies', () => {
const fixture = setup();
it('--yes', async () => {
const context = { cwd: '', yes: true, pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: true }))};
const context = {
cwd: '',
yes: true,
pkgManager: 'npm',
dryRun: true,
prompt: () => ({ deps: true }),
};
await dependencies(context);
expect(fixture.hasMessage('Skipping dependency installation')).to.be.true;
})
});
it('prompt yes', async () => {
const context = { cwd: '', pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: true })), install: undefined };
const context = {
cwd: '',
pkgManager: 'npm',
dryRun: true,
prompt: () => ({ deps: true }),
install: undefined,
};
await dependencies(context);
expect(fixture.hasMessage('Skipping dependency installation')).to.be.true;
expect(context.install).to.eq(true);
})
});
it('prompt no', async () => {
const context = { cwd: '', pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: false })), install: undefined };
const context = {
cwd: '',
pkgManager: 'npm',
dryRun: true,
prompt: () => ({ deps: false }),
install: undefined,
};
await dependencies(context);
expect(fixture.hasMessage('Skipping dependency installation')).to.be.true;
expect(context.install).to.eq(false);
})
});
it('--install', async () => {
const context = { cwd: '', install: true, pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: false })) };
const context = {
cwd: '',
install: true,
pkgManager: 'npm',
dryRun: true,
prompt: () => ({ deps: false }),
};
await dependencies(context);
expect(fixture.hasMessage('Skipping dependency installation')).to.be.true;
expect(context.install).to.eq(true);
})
});
it('--no-install', async () => {
const context = { cwd: '', install: false, pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: false })) };
const context = {
cwd: '',
install: false,
pkgManager: 'npm',
dryRun: true,
prompt: () => ({ deps: false }),
};
await dependencies(context);
expect(fixture.hasMessage('Skipping dependency installation')).to.be.true;
expect(context.install).to.eq(false);
})
})
});
});

View file

@ -10,14 +10,19 @@ describe('git', () => {
const fixture = setup();
it('none', async () => {
const context = { cwd: '', dryRun: true, prompt: (() => ({ git: false }))};
const context = { cwd: '', dryRun: true, prompt: () => ({ git: false }) };
await git(context);
expect(fixture.hasMessage('Skipping Git initialization')).to.be.true;
})
});
it('already initialized', async () => {
const context = { git: true, cwd: './test/fixtures/not-empty', dryRun: true, prompt: (() => ({ git: false }))};
const context = {
git: true,
cwd: './test/fixtures/not-empty',
dryRun: true,
prompt: () => ({ git: false }),
};
await execa('git', ['init'], { cwd: './test/fixtures/not-empty' });
await git(context);
@ -25,19 +30,19 @@ describe('git', () => {
// Cleanup
fs.rmSync('./test/fixtures/not-empty/.git', { recursive: true, force: true });
})
});
it('yes (--dry-run)', async () => {
const context = { cwd: '', dryRun: true, prompt: (() => ({ git: true }))};
const context = { cwd: '', dryRun: true, prompt: () => ({ git: true }) };
await git(context);
expect(fixture.hasMessage('Skipping Git initialization')).to.be.true;
})
});
it('no (--dry-run)', async () => {
const context = { cwd: '', dryRun: true, prompt: (() => ({ git: false }))};
const context = { cwd: '', dryRun: true, prompt: () => ({ git: false }) };
await git(context);
expect(fixture.hasMessage('Skipping Git initialization')).to.be.true;
})
})
});
});

View file

@ -10,11 +10,11 @@ describe('intro', () => {
await intro({ skipHouston: false, version: '0.0.0', username: 'user' });
expect(fixture.hasMessage('Houston:')).to.be.true;
expect(fixture.hasMessage('Welcome to astro v0.0.0')).to.be.true;
})
});
it('--skip-houston', async () => {
await intro({ skipHouston: true, version: '0.0.0', username: 'user' });
expect(fixture.length()).to.eq(1);
expect(fixture.hasMessage('Houston:')).to.be.false;
expect(fixture.hasMessage('Launch sequence initiated')).to.be.true;
})
})
});
});

View file

@ -11,10 +11,10 @@ describe('next steps', () => {
expect(fixture.hasMessage('Liftoff confirmed.')).to.be.true;
expect(fixture.hasMessage('npm run dev')).to.be.true;
expect(fixture.hasMessage('Good luck out there, astronaut!')).to.be.true;
})
});
it('--skip-houston', async () => {
await next({ skipHouston: true, cwd: './it/fixtures/not-empty', pkgManager: 'npm' });
expect(fixture.hasMessage('Good luck out there, astronaut!')).to.be.false;
})
})
});
});

View file

@ -7,73 +7,81 @@ describe('project name', () => {
const fixture = setup();
it('pass in name', async () => {
const context = { projectName: '', cwd: './foo/bar/baz', prompt: (() => {})};
const context = { projectName: '', cwd: './foo/bar/baz', prompt: () => {} };
await projectName(context);
expect(context.cwd).to.eq('./foo/bar/baz');
expect(context.projectName).to.eq('baz');
})
});
it('dot', async () => {
const context = { projectName: '', cwd: '.', prompt: (() => ({ name: 'foobar' }))};
const context = { projectName: '', cwd: '.', prompt: () => ({ name: 'foobar' }) };
await projectName(context);
expect(fixture.hasMessage('"." is not empty!')).to.be.true;
expect(context.projectName).to.eq('foobar');
})
});
it('dot slash', async () => {
const context = { projectName: '', cwd: './', prompt: (() => ({ name: 'foobar' }))};
const context = { projectName: '', cwd: './', prompt: () => ({ name: 'foobar' }) };
await projectName(context);
expect(fixture.hasMessage('"./" is not empty!')).to.be.true;
expect(context.projectName).to.eq('foobar');
})
});
it('empty', async () => {
const context = { projectName: '', cwd: './test/fixtures/empty', prompt: (() => ({ name: 'foobar' }))};
const context = {
projectName: '',
cwd: './test/fixtures/empty',
prompt: () => ({ name: 'foobar' }),
};
await projectName(context);
expect(fixture.hasMessage('"./test/fixtures/empty" is not empty!')).to.be.false;
expect(context.projectName).to.eq('empty');
})
});
it('not empty', async () => {
const context = { projectName: '', cwd: './test/fixtures/not-empty', prompt: (() => ({ name: 'foobar' }))};
const context = {
projectName: '',
cwd: './test/fixtures/not-empty',
prompt: () => ({ name: 'foobar' }),
};
await projectName(context);
expect(fixture.hasMessage('"./test/fixtures/not-empty" is not empty!')).to.be.true;
expect(context.projectName).to.eq('foobar');
})
});
it('basic', async () => {
const context = { projectName: '', cwd: '', prompt: (() => ({ name: 'foobar' }))};
const context = { projectName: '', cwd: '', prompt: () => ({ name: 'foobar' }) };
await projectName(context);
expect(context.cwd).to.eq('foobar');
expect(context.projectName).to.eq('foobar');
})
});
it('normalize', async () => {
const context = { projectName: '', cwd: '', prompt: (() => ({ name: 'Invalid Name' }))};
const context = { projectName: '', cwd: '', prompt: () => ({ name: 'Invalid Name' }) };
await projectName(context);
expect(context.cwd).to.eq('Invalid Name');
expect(context.projectName).to.eq('invalid-name');
})
});
it('remove leading/trailing dashes', async () => {
const context = { projectName: '', cwd: '', prompt: (() => ({ name: '(invalid)' }))};
const context = { projectName: '', cwd: '', prompt: () => ({ name: '(invalid)' }) };
await projectName(context);
expect(context.projectName).to.eq('invalid');
})
});
it('handles scoped packages', async () => {
const context = { projectName: '', cwd: '', prompt: (() => ({ name: '@astro/site' }))};
const context = { projectName: '', cwd: '', prompt: () => ({ name: '@astro/site' }) };
await projectName(context);
expect(context.cwd).to.eq('@astro/site');
expect(context.projectName).to.eq('@astro/site');
})
})
});
});

View file

@ -7,30 +7,30 @@ describe('template', () => {
const fixture = setup();
it('none', async () => {
const context = { template: '', cwd: '', dryRun: true, prompt: (() => ({ template: 'blog' })) };
const context = { template: '', cwd: '', dryRun: true, prompt: () => ({ template: 'blog' }) };
await template(context);
expect(fixture.hasMessage('Skipping template copying')).to.be.true;
expect(context.template).to.eq('blog');
})
});
it('minimal (--dry-run)', async () => {
const context = { template: 'minimal', cwd: '', dryRun: true, prompt: (() => {})};
const context = { template: 'minimal', cwd: '', dryRun: true, prompt: () => {} };
await template(context);
expect(fixture.hasMessage('Using minimal as project template')).to.be.true;
})
});
it('basics (--dry-run)', async () => {
const context = { template: 'basics', cwd: '', dryRun: true, prompt: (() => {})};
const context = { template: 'basics', cwd: '', dryRun: true, prompt: () => {} };
await template(context);
expect(fixture.hasMessage('Using basics as project template')).to.be.true;
})
});
it('blog (--dry-run)', async () => {
const context = { template: 'blog', cwd: '', dryRun: true, prompt: (() => {})};
const context = { template: 'blog', cwd: '', dryRun: true, prompt: () => {} };
await template(context);
expect(fixture.hasMessage('Using blog as project template')).to.be.true;
})
})
});
});

View file

@ -1,7 +1,7 @@
import { expect } from 'chai';
import fs from 'node:fs'
import { fileURLToPath } from 'node:url'
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { typescript, setupTypeScript } from '../dist/index.js';
import { setup } from './utils.js';
@ -10,70 +10,97 @@ describe('typescript', () => {
const fixture = setup();
it('none', async () => {
const context = { cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict', useTs: true }))};
const context = { cwd: '', dryRun: true, prompt: () => ({ ts: 'strict', useTs: true }) };
await typescript(context);
expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true;
})
});
it('use false', async () => {
const context = { cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict', useTs: false }))};
const context = { cwd: '', dryRun: true, prompt: () => ({ ts: 'strict', useTs: false }) };
await typescript(context);
expect(fixture.hasMessage('No worries')).to.be.true;
})
});
it('strict', async () => {
const context = { typescript: 'strict', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' }))};
const context = {
typescript: 'strict',
cwd: '',
dryRun: true,
prompt: () => ({ ts: 'strict' }),
};
await typescript(context);
expect(fixture.hasMessage('Using strict TypeScript configuration')).to.be.true;
expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true;
})
});
it('default', async () => {
const context = { typescript: 'default', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' }))};
const context = {
typescript: 'default',
cwd: '',
dryRun: true,
prompt: () => ({ ts: 'strict' }),
};
await typescript(context);
expect(fixture.hasMessage('Using default TypeScript configuration')).to.be.true;
expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true;
})
});
it('relaxed', async () => {
const context = { typescript: 'relaxed', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' }))};
const context = {
typescript: 'relaxed',
cwd: '',
dryRun: true,
prompt: () => ({ ts: 'strict' }),
};
await typescript(context);
expect(fixture.hasMessage('Using relaxed TypeScript configuration')).to.be.true;
expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true;
})
});
it('other', async () => {
const context = { typescript: 'other', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' })), exit(code) { throw code }};
const context = {
typescript: 'other',
cwd: '',
dryRun: true,
prompt: () => ({ ts: 'strict' }),
exit(code) {
throw code;
},
};
let err = null;
try {
await typescript(context);
} catch (e) {
err = e;
}
expect(err).to.eq(1)
})
})
expect(err).to.eq(1);
});
});
describe('typescript: setup', () => {
it('none', async () => {
const root = new URL('./fixtures/empty/', import.meta.url);
const tsconfig = new URL('./tsconfig.json', root);
await setupTypeScript('strict', { cwd: fileURLToPath(root) })
expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ "extends": "astro/tsconfigs/strict" });
await setupTypeScript('strict', { cwd: fileURLToPath(root) });
expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({
extends: 'astro/tsconfigs/strict',
});
fs.rmSync(tsconfig);
})
});
it('exists', async () => {
const root = new URL('./fixtures/not-empty/', import.meta.url);
const tsconfig = new URL('./tsconfig.json', root);
await setupTypeScript('strict', { cwd: fileURLToPath(root) })
expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ "extends": "astro/tsconfigs/strict" });
await setupTypeScript('strict', { cwd: fileURLToPath(root) });
expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({
extends: 'astro/tsconfigs/strict',
});
fs.writeFileSync(tsconfig, `{}`);
})
})
});
});

View file

@ -4,26 +4,28 @@ import stripAnsi from 'strip-ansi';
export function setup() {
const ctx = { messages: [] };
before(() => {
setStdout(Object.assign({}, process.stdout, {
write(buf) {
ctx.messages.push(stripAnsi(String(buf)).trim())
return true;
}
}))
setStdout(
Object.assign({}, process.stdout, {
write(buf) {
ctx.messages.push(stripAnsi(String(buf)).trim());
return true;
},
})
);
});
beforeEach(() => {
ctx.messages = [];
})
});
return {
messages() {
return ctx.messages
return ctx.messages;
},
length() {
return ctx.messages.length
return ctx.messages.length;
},
hasMessage(content) {
return !!ctx.messages.find(msg => msg.includes(content))
}
return !!ctx.messages.find((msg) => msg.includes(content));
},
};
}