Support skip install deps (#242)
* Support skip install deps * fmt * Create new-hats-deliver.md Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
f5ecbee192
commit
760a7a5509
11 changed files with 54 additions and 26 deletions
5
.changeset/new-hats-deliver.md
Normal file
5
.changeset/new-hats-deliver.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"create-astro": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Rename the `--run` option to `--skip-install` for clarity.
|
|
@ -18,6 +18,7 @@
|
||||||
"create-astro.js"
|
"create-astro.js"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"decamelize": "^5.0.0",
|
||||||
"decompress": "^4.2.1",
|
"decompress": "^4.2.1",
|
||||||
"ink": "^3.0.8",
|
"ink": "^3.0.8",
|
||||||
"ink-select-input": "^4.2.0",
|
"ink-select-input": "^4.2.0",
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Finalize from './Finalize';
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
use: 'npm' | 'yarn';
|
use: 'npm' | 'yarn';
|
||||||
run: boolean;
|
skipInstall?: boolean;
|
||||||
projectExists?: boolean;
|
projectExists?: boolean;
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
|
@ -18,7 +18,7 @@ interface Context {
|
||||||
ready?: boolean;
|
ready?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStep = ({ projectName, projectExists: exists, template, force, ready }: Context) => {
|
const getStep = ({ projectName, projectExists: exists, template, force, ready, skipInstall }: Context) => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case !projectName:
|
case !projectName:
|
||||||
return {
|
return {
|
||||||
|
@ -35,16 +35,21 @@ const getStep = ({ projectName, projectExists: exists, template, force, ready }:
|
||||||
key: 'template',
|
key: 'template',
|
||||||
Component: Template,
|
Component: Template,
|
||||||
};
|
};
|
||||||
case !ready:
|
case !ready && !skipInstall:
|
||||||
return {
|
return {
|
||||||
key: 'install',
|
key: 'install',
|
||||||
Component: Install,
|
Component: Install,
|
||||||
};
|
};
|
||||||
default:
|
case ready:
|
||||||
return {
|
return {
|
||||||
key: 'final',
|
key: 'final',
|
||||||
Component: Finalize,
|
Component: Finalize,
|
||||||
};
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
key: 'empty',
|
||||||
|
Component: () => <></>,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -70,7 +75,12 @@ const App: FC<{ context: Context }> = ({ context }) => {
|
||||||
|
|
||||||
if (state.projectName && (state.projectExists === false || state.force) && state.template) {
|
if (state.projectName && (state.projectExists === false || state.force) && state.template) {
|
||||||
if (state.force) emptyDir(state.projectName);
|
if (state.force) emptyDir(state.projectName);
|
||||||
prepareTemplate(context.use, state.template, state.projectName).then(() => {
|
prepareTemplate({
|
||||||
|
use: context.use,
|
||||||
|
templateName: state.template,
|
||||||
|
projectName: state.projectName,
|
||||||
|
skipInstall: state.skipInstall,
|
||||||
|
}).then(() => {
|
||||||
if (isSubscribed) {
|
if (isSubscribed) {
|
||||||
setState((v) => {
|
setState((v) => {
|
||||||
const newState = { ...v, ready: true };
|
const newState = { ...v, ready: true };
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Box, Text, useInput, useApp } from 'ink';
|
import { Box, Text, useApp } from 'ink';
|
||||||
|
import { relative } from 'path';
|
||||||
import Spacer from './Spacer';
|
import Spacer from './Spacer';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ const Confirm: FC<{ message?: any; context: any; onSubmit: (value: boolean) => v
|
||||||
<Text color="#FFBE2D">{'[uh-oh]'}</Text>
|
<Text color="#FFBE2D">{'[uh-oh]'}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}
|
{' '}
|
||||||
It appears <Text color="#17C083">./{projectName}</Text> is not empty. Overwrite?
|
It appears <Text color="#17C083">./{relative(process.cwd(), projectName)}</Text> is not empty. Overwrite?
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
|
import { relative } from 'path';
|
||||||
import { cancelProcessListeners } from '../utils';
|
import { cancelProcessListeners } from '../utils';
|
||||||
|
|
||||||
const Finalize: FC<{ context: any }> = ({ context: { use, projectName } }) => {
|
const Finalize: FC<{ context: any }> = ({ context: { use, projectName } }) => {
|
||||||
|
@ -8,20 +9,22 @@ const Finalize: FC<{ context: any }> = ({ context: { use, projectName } }) => {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const projectPath = `./${relative(process.cwd(), projectName)}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<Text color="#17C083">{'[ yes ]'}</Text>
|
<Text color="#17C083">{'[ yes ]'}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{' '}
|
{' '}
|
||||||
Project initialized at <Text color="#3894FF">./{projectName}</Text>
|
Project initialized at <Text color="#3894FF">{projectPath}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" marginY={1}>
|
<Box display="flex" marginY={1}>
|
||||||
<Text dimColor>{'[ tip ]'}</Text>
|
<Text dimColor>{'[ tip ]'}</Text>
|
||||||
<Box display="flex" marginLeft={1} flexDirection="column">
|
<Box display="flex" marginLeft={1} flexDirection="column">
|
||||||
<Text>Get started by running</Text>
|
<Text>Get started by running</Text>
|
||||||
<Text color="#3894FF">cd ./{projectName}</Text>
|
<Text color="#3894FF">cd {projectPath}</Text>
|
||||||
<Text color="#3894FF">{use} start</Text>
|
<Text color="#3894FF">{use} start</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
|
import decamelize from 'decamelize';
|
||||||
import { ARGS, ARG } from '../config';
|
import { ARGS, ARG } from '../config';
|
||||||
|
|
||||||
const Type: FC<{ type: any; enum?: string[] }> = ({ type, enum: e }) => {
|
const Type: FC<{ type: any; enum?: string[] }> = ({ type, enum: e }) => {
|
||||||
|
@ -71,7 +72,7 @@ const Help: FC<{ context: any }> = ({ context: { templates } }) => {
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginBottom={1} marginLeft={2} display="flex" flexDirection="column">
|
<Box marginBottom={1} marginLeft={2} display="flex" flexDirection="column">
|
||||||
{Object.entries(ARGS).map(([name, info]) => (
|
{Object.entries(ARGS).map(([name, info]) => (
|
||||||
<Command key={name} name={name} info={name === 'template' ? { ...info, enum: templates.map(({ value }) => value) } : info} />
|
<Command key={name} name={decamelize(name, { separator: '-' })} info={name === 'template' ? { ...info, enum: templates.map(({ value }) => value) } : info} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type * as arg from 'arg';
|
import type * as arg from 'arg';
|
||||||
|
import decamelize from 'decamelize';
|
||||||
|
|
||||||
export interface ARG {
|
export interface ARG {
|
||||||
type: any;
|
type: any;
|
||||||
|
@ -17,9 +18,9 @@ export const ARGS: Record<string, ARG> = {
|
||||||
enum: ['npm', 'yarn'],
|
enum: ['npm', 'yarn'],
|
||||||
description: 'specifies package manager to use',
|
description: 'specifies package manager to use',
|
||||||
},
|
},
|
||||||
run: {
|
skipInstall: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
description: 'should dependencies be installed automatically?',
|
description: 'should installing dependencies be skipped?',
|
||||||
},
|
},
|
||||||
force: {
|
force: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -39,7 +40,7 @@ export const ARGS: Record<string, ARG> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const args = Object.entries(ARGS).reduce((acc, [name, info]) => {
|
export const args = Object.entries(ARGS).reduce((acc, [name, info]) => {
|
||||||
const key = `--${name}`;
|
const key = `--${decamelize(name, { separator: '-' })}`;
|
||||||
const spec = { ...acc, [key]: info.type };
|
const spec = { ...acc, [key]: info.type };
|
||||||
|
|
||||||
if (info.alias) {
|
if (info.alias) {
|
||||||
|
|
|
@ -25,9 +25,9 @@ export default async function createAstro() {
|
||||||
const use = (args['--use'] ?? pkgManager) as 'npm' | 'yarn';
|
const use = (args['--use'] ?? pkgManager) as 'npm' | 'yarn';
|
||||||
const template = args['--template'];
|
const template = args['--template'];
|
||||||
const force = args['--force'];
|
const force = args['--force'];
|
||||||
const run = args['--run'] ?? true;
|
const skipInstall = args['--skip-install'];
|
||||||
|
|
||||||
const app = render(<App context={{ projectName, template, templates, force, run, use }} />);
|
const app = render(<App context={{ projectName, template, templates, force, skipInstall, use }} />);
|
||||||
|
|
||||||
const onError = () => {
|
const onError = () => {
|
||||||
if (app) app.clear();
|
if (app) app.clear();
|
||||||
|
|
|
@ -61,16 +61,14 @@ export async function rewriteFiles(projectName: string) {
|
||||||
return Promise.all(tasks);
|
return Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareTemplate(use: 'npm' | 'yarn', name: string, dest: string) {
|
export async function prepareTemplate(options: { use: 'npm' | 'yarn'; templateName: string; projectName: string; skipInstall?: boolean }) {
|
||||||
const projectName = dest;
|
const { use, templateName, projectName, skipInstall } = options;
|
||||||
dest = resolve(dest);
|
const dest = resolve(projectName);
|
||||||
const template = fileURLToPath(new URL(`./templates/${name}.tgz`, import.meta.url));
|
const template = fileURLToPath(new URL(`./templates/${templateName}.tgz`, import.meta.url));
|
||||||
await decompress(template, dest);
|
await decompress(template, dest);
|
||||||
await rewriteFiles(projectName);
|
await rewriteFiles(projectName);
|
||||||
try {
|
if (!skipInstall) {
|
||||||
await run(use, use === 'npm' ? 'i' : null, dest);
|
await run(use, use === 'npm' ? 'i' : null, dest).catch(() => cleanup(true));
|
||||||
} catch (e) {
|
|
||||||
cleanup(true);
|
|
||||||
}
|
}
|
||||||
isDone = true;
|
isDone = true;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -19,10 +19,13 @@ CreateAstro.before(async () => {
|
||||||
|
|
||||||
for (const template of templates) {
|
for (const template of templates) {
|
||||||
CreateAstro(template, async () => {
|
CreateAstro(template, async () => {
|
||||||
await execa('../../create-astro.js', [template, '--template', template], { cwd });
|
const { stdout } = await execa('../../create-astro.js', [`./${template}`, '--template', template, '--skip-install'], { cwd });
|
||||||
|
|
||||||
|
// test: path should formatted as './{dirName}'
|
||||||
|
assert.not.match(stdout, '././');
|
||||||
|
|
||||||
const DOES_HAVE = ['.gitignore', 'package.json', 'public', 'src'];
|
const DOES_HAVE = ['.gitignore', 'package.json', 'public', 'src'];
|
||||||
const DOES_NOT_HAVE = ['_gitignore', 'meta.json'];
|
const DOES_NOT_HAVE = ['_gitignore', 'meta.json', 'node_modules'];
|
||||||
|
|
||||||
// test: template contains essential files & folders
|
// test: template contains essential files & folders
|
||||||
for (const file of DOES_HAVE) {
|
for (const file of DOES_HAVE) {
|
||||||
|
@ -31,7 +34,7 @@ for (const template of templates) {
|
||||||
|
|
||||||
// test: template DOES NOT contain files supposed to be stripped away
|
// test: template DOES NOT contain files supposed to be stripped away
|
||||||
for (const file of DOES_NOT_HAVE) {
|
for (const file of DOES_NOT_HAVE) {
|
||||||
assert.not.ok(fs.existsSync(path.join(cwd, template, `does not have ${file}`)));
|
assert.not.ok(fs.existsSync(path.join(cwd, template, file)), `does not have ${file}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3306,6 +3306,11 @@ decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
|
||||||
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
|
||||||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||||
|
|
||||||
|
decamelize@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.0.tgz#88358157b010ef133febfd27c18994bd80c6215b"
|
||||||
|
integrity sha512-U75DcT5hrio3KNtvdULAWnLiAPbFUC4191ldxMmj4FA/mRuBnmDwU0boNfPyFRhnan+Jm+haLeSn3P0afcBn4w==
|
||||||
|
|
||||||
decode-uri-component@^0.2.0:
|
decode-uri-component@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz"
|
||||||
|
|
Loading…
Add table
Reference in a new issue