Skip to content

Commit

Permalink
Major cleanup to blitz new dependency update + graceful failure (Cl…
Browse files Browse the repository at this point in the history
…oses #202, #284)

Co-Authored-By: Adam Markon <[email protected]> (patch)
  • Loading branch information
Skn0tt authored May 1, 2020
1 parent b0120d7 commit b08de10
Show file tree
Hide file tree
Showing 17 changed files with 452 additions and 111 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
2 changes: 1 addition & 1 deletion examples/store/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
}
2 changes: 1 addition & 1 deletion packages/cli/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
// collectCoverage: !!`Boolean(process.env.CI)`,
collectCoverageFrom: ['src/**/*.ts'],
coveragePathIgnorePatterns: ['/templates/'],
modulePathIgnorePatterns: ['tmp', 'lib'],
modulePathIgnorePatterns: ['<rootDir>/tmp', '<rootDir>/lib'],
testPathIgnorePatterns: ['src/commands/test.ts'],
// TODO enable threshold
// coverageThreshold: {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"fs-extra": "^9.0.0",
"fs-readdir-recursive": "^1.1.0",
"globby": "^11.0.0",
"got": "^11.0.2",
"has-yarn": "^2.1.0",
"hasbin": "^1.2.3",
"mem-fs": "^1.1.3",
Expand All @@ -59,7 +60,8 @@
"@oclif/dev-cli": "^1.22.2",
"@oclif/test": "^1.2.5",
"@prisma/cli": "2.0.0-beta.3",
"@types/pluralize": "^0.0.29"
"@types/pluralize": "^0.0.29",
"nock": "13.0.0-beta.3"
},
"oclif": {
"commands": "./lib/src/commands",
Expand Down
15 changes: 11 additions & 4 deletions packages/cli/src/commands/new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import Command from '../command'
import AppGenerator from '../generators/app'
import chalk from 'chalk'
import hasbin from 'hasbin'
import {log} from '@blitzjs/server'
const debug = require('debug')('blitz:new')

import PromptAbortedError from '../errors/prompt-aborted'

export interface Flags {
ts: boolean
yarn: boolean
'skip-install': boolean
}

export default class New extends Command {
Expand All @@ -35,6 +37,12 @@ export default class New extends Command {
default: hasbin.sync('yarn'),
allowNo: true,
}),
'skip-install': flags.boolean({
description: 'Skip package installation',
hidden: true,
default: false,
allowNo: true,
}),
'dry-run': flags.boolean({description: 'show what files will be created without writing them to disk'}),
}

Expand All @@ -54,14 +62,13 @@ export default class New extends Command {
useTs: !flags.js,
yarn: flags.yarn,
version: this.config.version,
skipInstall: flags['skip-install'],
})

const themeColor = '6700AB'

try {
this.log('\n' + chalk.hex(themeColor).bold('Hang tight while we set up your new Blitz app!') + '\n')
this.log('\n' + log.withBrand('Hang tight while we set up your new Blitz app!') + '\n')
await generator.run()
this.log('\n' + chalk.hex(themeColor).bold('Your new Blitz app is ready! Next steps:') + '\n')
this.log('\n' + log.withBrand('Your new Blitz app is ready! Next steps:') + '\n')
this.log(chalk.yellow(` 1. cd ${args.name}`))
this.log(chalk.yellow(` 2. blitz start`))
this.log(chalk.yellow(` 3. You create new pages by placing components inside app/pages/\n`))
Expand Down
81 changes: 47 additions & 34 deletions packages/cli/src/generators/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import chalk from 'chalk'
import username from 'username'
import {readJSONSync, writeJson} from 'fs-extra'
import {join} from 'path'
import {replaceDependencies} from '../utils/replace-dependencies'
import {replaceBlitzDependency} from '../utils/replace-blitz-dependency'
import {fetchLatestVersionsFor} from '../utils/fetch-latest-version-for'
import {log} from '@blitzjs/server'

const themeColor = '6700AB'
import {getBlitzDependencyVersion} from '../utils/get-blitz-dependency-version'

export interface AppGeneratorOptions extends GeneratorOptions {
appName: string
useTs: boolean
yarn: boolean
version: string
skipInstall: boolean
}

class AppGenerator extends Generator<AppGeneratorOptions> {
Expand All @@ -39,48 +38,62 @@ class AppGenerator extends Generator<AppGeneratorOptions> {
async postWrite() {
const pkgJsonLocation = join(this.destinationPath(), 'package.json')
const pkg = readJSONSync(pkgJsonLocation)
const pkgDependencies = Object.keys(pkg.dependencies)
const pkgDevDependencies = Object.keys(pkg.devDependencies)

console.log('') // New line needed
const spinner = log.spinner(log.withBranded('Retrieving the freshest of dependencies')).start()

const dependenciesArray = await Promise.all([
replaceDependencies(pkg, pkgDependencies, 'dependencies'),
replaceDependencies(pkg, pkgDevDependencies, 'devDependencies'),
const spinner = log.spinner(log.withBrand('Retrieving the freshest of dependencies')).start()

const [
{value: newDependencies, isFallback: dependenciesUsedFallback},
{value: newDevDependencies, isFallback: devDependenciesUsedFallback},
{value: blitzDependencyVersion, isFallback: blitzUsedFallback},
] = await Promise.all([
fetchLatestVersionsFor(pkg.dependencies),
fetchLatestVersionsFor(pkg.devDependencies),
getBlitzDependencyVersion(this.options.version),
])

for (let i = 0; i < dependenciesArray.length; i++) {
const {key, dependencies} = dependenciesArray[i]
pkg[key] = replaceBlitzDependency(dependencies, this.options.version)
}
pkg.dependencies = newDependencies
pkg.devDependencies = newDevDependencies
pkg.dependencies.blitz = blitzDependencyVersion

await writeJson(pkgJsonLocation, pkg, {spaces: 2})
const fallbackUsed = dependenciesUsedFallback || devDependenciesUsedFallback || blitzUsedFallback

spinner.succeed()
await writeJson(pkgJsonLocation, pkg, {spaces: 2})

console.log(chalk.hex(themeColor).bold('\nInstalling those dependencies...'))
console.log('Scary warning messages during this part are unfortunately normal.\n')
if (!fallbackUsed && !this.options.skipInstall) {
spinner.succeed()
log.branded('\nInstalling those dependencies...')
console.log('Scary warning messages during this part are unfortunately normal.\n')

const result = spawn.sync(this.options.yarn ? 'yarn' : 'npm', ['install'], {stdio: 'inherit'})
if (result.status !== 0) {
throw new Error()
}
const result = spawn.sync(this.options.yarn ? 'yarn' : 'npm', ['install'], {stdio: 'inherit'})
if (result.status !== 0) {
throw new Error()
}

console.log(chalk.hex(themeColor).bold('\nDependencies successfully installed.'))
log.branded('\nDependencies successfully installed.')

const runLocalNodeCLI = (command: string) => {
if (this.options.yarn) {
return spawn.sync('yarn', ['run', ...command.split(' ')])
} else {
return spawn.sync('npx', command.split(' '))
const runLocalNodeCLI = (command: string) => {
if (this.options.yarn) {
return spawn.sync('yarn', ['run', ...command.split(' ')])
} else {
return spawn.sync('npx', command.split(' '))
}
}
}

// Ensure the generated files are formatted with the installed prettier version
const prettierResult = runLocalNodeCLI('prettier --loglevel silent --write .')
if (prettierResult.status !== 0) {
throw new Error('Failed running prettier')
// Ensure the generated files are formatted with the installed prettier version
const prettierResult = runLocalNodeCLI('prettier --loglevel silent --write .')
if (prettierResult.status !== 0) {
throw new Error('Failed running prettier')
}
} else {
console.log('') // New line needed
spinner.fail(
chalk.red.bold(
`We had some trouble connecting to the network, so we'll skip installing your dependencies right now. Make sure to run ${
this.options.yarn ? "'yarn'" : "'npm install'"
} once you're connected again.`,
),
)
}

// TODO: someone please clean up this ugly code :D
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/utils/fallbackable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Fallbackable<T> {
value: T
isFallback: boolean
}
31 changes: 31 additions & 0 deletions packages/cli/src/utils/fetch-latest-version-for.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {getLatestVersion} from './get-latest-version'
import {Fallbackable} from './fallbackable'

export const fetchLatestVersionsFor = async <T extends Record<string, string>>(
dependencies: T,
): Promise<Fallbackable<T>> => {
const entries = Object.entries(dependencies)

let fallbackUsed = false

const updated = await Promise.all(
entries.map(async ([dep, version]) => {
if (version.match(/\d.x/)) {
const {value: latestVersion, isFallback} = await getLatestVersion(dep, version)

if (isFallback) {
fallbackUsed = true
}

return [dep, latestVersion]
} else {
return [dep, version]
}
}),
)

return {
isFallback: fallbackUsed,
value: Object.fromEntries(updated),
}
}
19 changes: 19 additions & 0 deletions packages/cli/src/utils/get-blitz-dependency-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {fetchDistTags} from './npm-fetch'
import {Fallbackable} from './fallbackable'
import {logFailedVersionFetch} from './get-latest-version'

export const getBlitzDependencyVersion = async (cliVersion: string): Promise<Fallbackable<string>> => {
try {
const {latest, canary} = await fetchDistTags('blitz')

if (cliVersion.includes('canary')) {
return {value: canary, isFallback: false}
}

return {value: latest, isFallback: false}
} catch (error) {
const fallback = 'latest'
logFailedVersionFetch('blitz', fallback)
return {value: fallback, isFallback: true}
}
}
53 changes: 37 additions & 16 deletions packages/cli/src/utils/get-latest-version.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import {fetchAllVersions, fetchLatestDistVersion} from './npm-fetch'
import {log} from '@blitzjs/server'
import {Fallbackable} from './fallbackable'
import chalk from 'chalk'

export const getLatestVersion = async (dependency: string, templateVersion: string) => {
export const logFailedVersionFetch = (dependency: string, fallback: string) => {
log.clearLine(
log.withWarning(
`Failed to fetch latest version of '${chalk.bold(dependency)}', falling back to '${chalk.bold(
fallback,
)}'.\n`,
),
)
}

export const getLatestVersion = async (
dependency: string,
templateVersion: string = '',
): Promise<Fallbackable<string>> => {
const major = templateVersion.replace('.x', '')
const allVersions = await fetchAllVersions(dependency)
const latestDistVersion = await fetchLatestDistVersion(dependency)

if (!allVersions || !latestDistVersion) {
return templateVersion
}
try {
const [allVersions, latestDistVersion] = await Promise.all([
fetchAllVersions(dependency),
fetchLatestDistVersion(dependency),
])

const latestVersion = Object.keys(allVersions)
.filter((version) => version.startsWith(major))
.sort((a, b) => a.localeCompare(b, undefined, {numeric: true}))
.reverse()[0]
const latestVersion = Object.keys(allVersions)
.filter((version) => version.startsWith(major))
.sort((a, b) => a.localeCompare(b, undefined, {numeric: true}))
.reverse()[0]

// If the latest tagged version matches our pinned major, use that, otherwise use the
// latest untagged which does
if (latestDistVersion.startsWith(major)) {
return latestDistVersion
} else {
return latestVersion
// If the latest tagged version matches our pinned major, use that, otherwise use the
// latest untagged which does
if (latestDistVersion.startsWith(major)) {
return {value: latestDistVersion, isFallback: false}
} else {
return {value: latestVersion, isFallback: false}
}
} catch (error) {
const fallback = templateVersion
logFailedVersionFetch(dependency, fallback)
return {value: fallback, isFallback: false}
}
}
36 changes: 21 additions & 15 deletions packages/cli/src/utils/npm-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import fetch from 'node-fetch'
import got from 'got'

type PackageInformation = any
type NpmDepResponse = {versions: Record<string, PackageInformation>}

export const fetchAllVersions = async (dependency: string) => {
const res = await fetch(`https://registry.npmjs.org/${dependency}`)
if (res.ok) {
const json = await res.json()
return json.versions as string[]
} else {
return
}
const res = await got(`https://registry.npmjs.org/${dependency}`, {
retry: {limit: 3},
responseType: 'json',
}).json<NpmDepResponse>()
return Object.keys(res.versions)
}

type NpmDistTagsResponse = {latest: string; canary: string}

export const fetchDistTags = async (dependency: string) => {
const res = await got(`https://registry.npmjs.org/-/package/${dependency}/dist-tags`, {
retry: {limit: 3},
responseType: 'json',
}).json<NpmDistTagsResponse>()
return res
}

export const fetchLatestDistVersion = async (dependency: string) => {
const res = await fetch(`https://registry.npmjs.org/-/package/${dependency}/dist-tags`)
if (res.ok) {
const json = await res.json()
return json.latest as string
} else {
return
}
const res = await fetchDistTags(dependency)
return res.latest
}
7 changes: 0 additions & 7 deletions packages/cli/src/utils/replace-blitz-dependency.ts

This file was deleted.

16 changes: 0 additions & 16 deletions packages/cli/src/utils/replace-dependencies.ts

This file was deleted.

Loading

0 comments on commit b08de10

Please sign in to comment.