-
Notifications
You must be signed in to change notification settings - Fork 84
feat: tutorialkit eject
command
#81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
f68571f
4513bfc
f1b66af
3228705
98740da
bc536b9
e303332
ce68177
2047b0d
72bffb2
e374027
17d194c
fe1211d
0a8b9dd
b3a8135
499dd36
f00eeee
ddb7ab6
fc2bde2
e8a1f7a
9fd7ee0
4013b62
a1f1aff
f5ca361
75925a8
3cafab6
1033feb
be68c98
c03d28e
0e2b6d8
a52d0bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import chalk from 'chalk'; | ||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import type { Arguments } from 'yargs-parser'; | ||
import { pkg } from '../../pkg.js'; | ||
import { DEFAULT_VALUES, type EjectOptions } from './options.js'; | ||
import { errorLabel, printHelp } from '../../utils/messages.js'; | ||
import { parseAstroConfig, replaceArgs, generateAstroConfig } from '../../utils/astro-config.js'; | ||
|
||
export function ejectRoutes(flags: Arguments) { | ||
if (flags._[1] === 'help' || flags.help || flags.h) { | ||
printHelp({ | ||
commandName: `${pkg.name} eject`, | ||
usage: '[folder] [...options]', | ||
tables: { | ||
Options: [ | ||
[ | ||
'--force', | ||
`Overwrite existing files in the target directory without prompting (default ${chalk.yellow(DEFAULT_VALUES.force)})`, | ||
], | ||
], | ||
}, | ||
}); | ||
|
||
return 0; | ||
} | ||
|
||
try { | ||
return _eject(flags); | ||
} catch (error) { | ||
console.error(`${errorLabel()} Command failed`); | ||
|
||
if (error.stack) { | ||
console.error(`\n${error.stack}`); | ||
} | ||
|
||
process.exit(1); | ||
} | ||
} | ||
|
||
async function _eject(flags: EjectOptions) { | ||
let folderPath = flags._[1] !== undefined ? String(flags._[1]) : undefined; | ||
|
||
if (folderPath === undefined) { | ||
folderPath = process.cwd(); | ||
} else { | ||
folderPath = path.resolve(process.cwd(), folderPath); | ||
} | ||
|
||
/** | ||
* First we make sure that the destination has the correct files | ||
* and that there won't be any files overriden in the process. | ||
* | ||
* If they are any and `force` was not specified we abort. | ||
Nemikolh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
const { astroConfigPath, srcPath, srcDestPath } = validateDestination(folderPath, flags.force); | ||
|
||
/** | ||
* We now proceed with the astro configuration. | ||
* | ||
* There we must disable the default routes so that the | ||
* new routes that we're copying will be automatically picked up. | ||
*/ | ||
const astroConfig = await parseAstroConfig(astroConfigPath); | ||
|
||
replaceArgs({ defaultRoutes: false }, astroConfig); | ||
|
||
fs.writeFileSync(astroConfigPath, generateAstroConfig(astroConfig)); | ||
|
||
/** | ||
* We now finalize by copying all the assets from the `default` folder | ||
* into the `src` folder. | ||
*/ | ||
fs.cpSync(srcPath, srcDestPath, { recursive: true }); | ||
} | ||
|
||
function validateDestination(folder: string, force: boolean) { | ||
assertExists(folder); | ||
|
||
const astroConfigPath = assertExists(path.join(folder, 'astro.config.ts')); | ||
const srcDestPath = assertExists(path.join(folder, 'src')); | ||
|
||
const localAstroIntegration = assertExists(path.resolve(folder, 'node_modules', '@tutorialkit', 'astro')); | ||
|
||
const srcPath = path.join(localAstroIntegration, 'dist', 'default'); | ||
|
||
// check that they are no collision | ||
Nemikolh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!force) { | ||
walk(srcPath, (relativePath) => { | ||
const destination = path.join(srcDestPath, relativePath); | ||
|
||
if (fs.existsSync(destination)) { | ||
throw new Error( | ||
`Eject aborted because '${destination}' would be overriden by this command. Use --force to ignore this error.`, | ||
Nemikolh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
} | ||
}); | ||
} | ||
|
||
return { | ||
astroConfigPath, | ||
srcPath, | ||
srcDestPath, | ||
}; | ||
} | ||
|
||
function assertExists(filePath: string) { | ||
if (!fs.existsSync(filePath)) { | ||
throw new Error(`${filePath} does not exists!`); | ||
} | ||
|
||
return filePath; | ||
} | ||
|
||
function walk(root: string, visit: (relativeFilePath: string) => void) { | ||
function traverse(folder: string, pathPrefix: string) { | ||
for (const filename of fs.readdirSync(folder)) { | ||
const filePath = path.join(folder, filename); | ||
const stat = fs.statSync(filePath); | ||
|
||
const relativeFilePath = path.join(pathPrefix, filename); | ||
|
||
if (stat.isDirectory()) { | ||
traverse(filePath, relativeFilePath); | ||
} else { | ||
visit(relativeFilePath); | ||
} | ||
} | ||
} | ||
|
||
traverse(root, ''); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export interface EjectOptions { | ||
_: Array<string | number>; | ||
force?: boolean; | ||
} | ||
|
||
export const DEFAULT_VALUES = { | ||
force: false, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,13 @@ | |
import chalk from 'chalk'; | ||
import yargs from 'yargs-parser'; | ||
import { createTutorial } from './commands/create/index.js'; | ||
import { ejectRoutes } from './commands/eject/index.js'; | ||
import { pkg } from './pkg.js'; | ||
import { errorLabel, primaryLabel, printHelp } from './utils/messages.js'; | ||
|
||
type CLICommand = 'version' | 'help' | 'create'; | ||
type CLICommand = 'version' | 'help' | 'create' | 'eject'; | ||
|
||
const supportedCommands = new Set(['version', 'help', 'create']); | ||
const supportedCommands = new Set<string>(['version', 'help', 'create', 'eject'] satisfies CLICommand[]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we just type this as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't because then we get a type error below when trying to do The reason I made that change was to make sure the values are checked against the |
||
|
||
cli(); | ||
|
||
|
@@ -52,6 +53,9 @@ async function runCommand(cmd: CLICommand, flags: yargs.Arguments): Promise<numb | |
case 'create': { | ||
return createTutorial(flags); | ||
} | ||
case 'eject': { | ||
return ejectRoutes(flags); | ||
} | ||
default: { | ||
console.error(`${errorLabel()} Unknown command ${chalk.red(cmd)}`); | ||
return 1; | ||
|
Uh oh!
There was an error while loading. Please reload this page.