Merge pull request #13768 from desktop/ts-4.5-minimal

Upgrade to TypeScript 4.5
This commit is contained in:
Markus Olsson 2022-02-03 13:40:52 +01:00 committed by GitHub
commit 65e48995be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 867 additions and 822 deletions

View file

@ -2,7 +2,6 @@ root: true
parser: '@typescript-eslint/parser'
plugins:
- '@typescript-eslint'
- babel
- react
- json
- jsdoc
@ -100,9 +99,7 @@ rules:
'@typescript-eslint/no-explicit-any': off
'@typescript-eslint/no-inferrable-types': off
'@typescript-eslint/no-empty-function': off
# Babel
babel/no-invalid-this: error
'@typescript-eslint/no-redeclare': error
# React
react/jsx-boolean-value:
@ -114,6 +111,10 @@ rules:
react/jsx-uses-vars: error
react/jsx-uses-react: error
react/no-unused-state: error
react/no-unused-prop-types: error
react/prop-types:
- error
- ignore: ['children']
# JSDoc
jsdoc/check-alignment: error
@ -136,9 +137,9 @@ rules:
###########
curly: error
no-new-wrappers: error
no-redeclare:
- error
- builtinGlobals: true
# We'll use no-redeclare from @typescript/eslint-plugin instead as that
# supports overloads
no-redeclare: off
no-eval: error
no-sync: error
no-var: error

View file

@ -20,7 +20,7 @@ export interface IMatch<T> {
export type KeyFunction<T> = (item: T) => ReadonlyArray<string>
export function match<T, _K extends keyof T>(
export function match<T>(
query: string,
items: ReadonlyArray<T>,
getKey: KeyFunction<T>

View file

@ -55,57 +55,6 @@ declare const __UPDATES_URL__: string
*/
declare const __PROCESS_KIND__: 'main' | 'ui' | 'crash' | 'highlighter'
/**
* The IdleDeadline interface is used as the data type of the input parameter to
* idle callbacks established by calling Window.requestIdleCallback(). It offers
* a method, timeRemaining(), which lets you determine how much longer the user
* agent estimates it will remain idle and a property, didTimeout, which lets
* you determine if your callback is executing because its timeout duration
* expired.
*
* https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline
*/
interface IdleDeadline {
readonly didTimeout: boolean
readonly timeRemaining: () => DOMHighResTimeStamp
}
/**
* Contains optional configuration parameters for the requestIdleCallback
* function.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
*/
interface IdleCallbackOptions {
/**
* If timeout is specified and has a positive value, and the callback has not
* already been called by the time timeout milliseconds have passed, the
* timeout will be called during the next idle period, even if doing so risks
* causing a negative performance impact..
*/
readonly timeout: number
}
/**
* The window.requestIdleCallback() method queues a function to be called during
* a browser's idle periods. This enables developers to perform background and
* low priority work on the main event loop, without impacting latency-critical
* events such as animation and input response. Functions are generally called
* in first-in-first-out order; however, callbacks which have a timeout
* specified may be called out-of-order if necessary in order to run them before
* the timeout elapses.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
*
* @param options Contains optional configuration parameters. Currently only one
* property is defined:
* timeout:
*/
declare function requestIdleCallback(
fn: (deadline: IdleDeadline) => void,
options?: IdleCallbackOptions
): number
interface IDesktopLogger {
/**
* Writes a log message at the 'error' level.
@ -181,9 +130,6 @@ declare const log: IDesktopLogger
declare namespace NodeJS {
interface Process extends EventEmitter {
once(event: 'exit', listener: Function): this
once(event: 'uncaughtException', listener: (error: Error) => void): this
on(event: 'uncaughtException', listener: (error: Error) => void): this
on(
event: 'send-non-fatal-exception',
listener: (error: Error, context?: { [key: string]: string }) => void
@ -197,21 +143,7 @@ declare namespace NodeJS {
}
}
interface XMLHttpRequest extends XMLHttpRequestEventTarget {
/**
* Initiates the request. The optional argument provides the request body. The argument is ignored if request method is GET or HEAD.
* Throws an "InvalidStateError" DOMException if either state is not opened or the send() flag is set.
*/
send(body?: Document | BodyInit | null): void
}
declare namespace Electron {
interface RequestOptions {
readonly method: string
readonly url: string
readonly headers: any
}
type AppleActionOnDoubleClickPref = 'Maximize' | 'Minimize' | 'None'
interface SystemPreferences {
@ -222,19 +154,6 @@ declare namespace Electron {
}
}
// https://wicg.github.io/ResizeObserver/#resizeobserverentry
interface IResizeObserverEntry {
readonly target: HTMLElement
readonly contentRect: ClientRect
}
declare class ResizeObserver {
public constructor(cb: (entries: ReadonlyArray<IResizeObserverEntry>) => void)
public disconnect(): void
public observe(e: HTMLElement): void
}
declare module 'file-metadata' {
// eslint-disable-next-line no-restricted-syntax
function fileMetadata(path: string): Promise<plist.PlistObject>
@ -246,6 +165,9 @@ interface Window {
Element: typeof Element
}
interface HTMLDialogElement {
showModal: () => void
}
/**
* Obtain the number of elements of a tuple type
*

View file

@ -23,9 +23,6 @@ let oauthState: IOAuthState | null = null
* flow.
*/
export function askUserToOAuth(endpoint: string) {
// Disable the lint warning since we're storing the `resolve` and `reject`
// functions.
// tslint:disable-next-line:promise-must-complete
return new Promise<Account>((resolve, reject) => {
oauthState = { state: uuid(), endpoint, resolve, reject }

View file

@ -130,7 +130,7 @@ export class ApiRepositoriesStore extends BaseStore {
this.emitUpdate()
}
private updateAccount<T, K extends keyof IAccountRepositories>(
private updateAccount<K extends keyof IAccountRepositories>(
account: Account,
repositories: Pick<IAccountRepositories, K>
) {

View file

@ -5205,7 +5205,6 @@ export class AppStore extends TypedBaseStore<IAppState> {
* resolve when `_completeOpenInDesktop` is called.
*/
public _startOpenInDesktop(fn: () => void): Promise<Repository | null> {
// tslint:disable-next-line:promise-must-complete
const p = new Promise<Repository | null>(
resolve => (this.resolveOpenInDesktop = resolve)
)

View file

@ -140,8 +140,6 @@ export class RepositoryIndicatorUpdater {
public pause() {
if (this.paused === false) {
// Disable the lint warning since we're storing the `resolve`
// tslint:disable-next-line:promise-must-complete
this.pausePromise = new Promise<void>(resolve => {
this.resolvePausePromise = resolve
})

View file

@ -35,14 +35,6 @@ export async function reportError(
}
}
const requestOptions: Electron.RequestOptions = {
method: 'POST',
url: nonFatal ? NonFatalErrorEndpoint : ErrorEndpoint,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
const body = [...data.entries()]
.map(
([key, value]) =>
@ -52,7 +44,10 @@ export async function reportError(
try {
await new Promise<void>((resolve, reject) => {
const request = net.request(requestOptions)
const url = nonFatal ? NonFatalErrorEndpoint : ErrorEndpoint
const request = net.request({ method: 'POST', url })
request.setHeader('Content-Type', 'application/x-www-form-urlencoded')
request.on('response', response => {
if (response.statusCode === 200) {

View file

@ -69,8 +69,8 @@ export class ModifiedImageDiff extends React.Component<
super(props)
this.resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
if (entry.target === this.container) {
for (const { target, contentRect } of entries) {
if (target === this.container && target instanceof HTMLElement) {
// We might end up causing a recursive update by updating the state
// when we're reacting to a resize so we'll defer it until after
// react is done with this frame.
@ -80,8 +80,8 @@ export class ModifiedImageDiff extends React.Component<
this.resizedTimeoutID = setImmediate(
this.onResized,
entry.target,
entry.contentRect
target,
contentRect
)
}
}

View file

@ -1,8 +1,5 @@
import * as React from 'react'
// broken for SFCs: https://github.com/Microsoft/tslint-microsoft-contrib/issues/339
/* tslint:disable react-unused-props-and-state */
interface IHighlightTextProps {
/** The text to render */
readonly text: string

View file

@ -288,8 +288,8 @@ export class List extends React.Component<IListProps, IListState> {
if (ResizeObserver || false) {
this.resizeObserver = new ResizeObserverClass(entries => {
for (const entry of entries) {
if (entry.target === this.list) {
for (const { target, contentRect } of entries) {
if (target === this.list && this.list !== null) {
// We might end up causing a recursive update by updating the state
// when we're reacting to a resize so we'll defer it until after
// react is done with this frame.
@ -299,8 +299,8 @@ export class List extends React.Component<IListProps, IListState> {
this.updateSizeTimeoutId = setImmediate(
this.onResized,
entry.target,
entry.contentRect
this.list,
contentRect
)
}
}

View file

@ -45,23 +45,13 @@ export interface IMultiCommitOperationProps {
export abstract class BaseMultiCommitOperation extends React.Component<
IMultiCommitOperationProps
> {
protected abstract onBeginOperation = () => {}
protected abstract onChooseBranch = (targetBranch: Branch) => {}
protected abstract onContinueAfterConflicts = async (): Promise<void> => {}
protected abstract onAbort = async (): Promise<void> => {}
protected abstract onConflictsDialogDismissed = () => {}
protected abstract renderChooseBranch = (): JSX.Element | null => {
return null
}
protected abstract renderCreateBranch = (): JSX.Element | null => {
return null
}
protected abstract onBeginOperation: () => void
protected abstract onChooseBranch: (targetBranch: Branch) => void
protected abstract onContinueAfterConflicts: () => Promise<void>
protected abstract onAbort: () => Promise<void>
protected abstract onConflictsDialogDismissed: () => void
protected abstract renderChooseBranch: () => JSX.Element | null
protected abstract renderCreateBranch: () => JSX.Element | null
protected onFlowEnded = () => {
this.props.dispatcher.closePopup(PopupType.MultiCommitOperation)

View file

@ -79,23 +79,17 @@ export abstract class BaseChooseBranchDialog extends React.Component<
IBaseChooseBranchDialogProps,
IBaseChooseBranchDialogState
> {
protected abstract start = () => {}
protected abstract start: () => void
protected abstract canStart = (): boolean => {
return false
}
protected abstract canStart: () => boolean
protected abstract updateStatus = async (branch: Branch) => {}
protected abstract updateStatus: (branch: Branch) => Promise<void>
protected abstract getDialogTitle = (
protected abstract getDialogTitle: (
branchName: string
): string | JSX.Element | undefined => {
return branchName
}
) => string | JSX.Element | undefined
protected abstract renderActionStatusIcon = (): JSX.Element | null => {
return null
}
protected abstract renderActionStatusIcon: () => JSX.Element | null
public constructor(props: IBaseChooseBranchDialogProps) {
super(props)

View file

@ -0,0 +1,68 @@
import * as React from 'react'
import classNames from 'classnames'
import { TabBarType } from './tab-bar-type'
interface ITabBarItemProps {
readonly index: number
readonly selected: boolean
readonly onClick: (index: number) => void
readonly onMouseEnter: (index: number) => void
readonly onMouseLeave: () => void
readonly onSelectAdjacent: (
direction: 'next' | 'previous',
index: number
) => void
readonly onButtonRef: (
index: number,
button: HTMLButtonElement | null
) => void
readonly type?: TabBarType
}
export class TabBarItem extends React.Component<ITabBarItemProps, {}> {
private onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
this.props.onClick(this.props.index)
}
private onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
const { type, index } = this.props
const previousKey = type === TabBarType.Vertical ? 'ArrowUp' : 'ArrowLeft'
const nextKey = type === TabBarType.Vertical ? 'ArrowDown' : 'ArrowRight'
if (event.key === previousKey) {
this.props.onSelectAdjacent('previous', index)
event.preventDefault()
} else if (event.key === nextKey) {
this.props.onSelectAdjacent('next', index)
event.preventDefault()
}
}
private onButtonRef = (buttonRef: HTMLButtonElement | null) => {
this.props.onButtonRef(this.props.index, buttonRef)
}
private onMouseEnter = () => {
this.props.onMouseEnter(this.props.index)
}
public render() {
const selected = this.props.selected
const className = classNames('tab-bar-item', { selected })
return (
<button
ref={this.onButtonRef}
className={className}
onClick={this.onClick}
role="tab"
aria-selected={selected}
tabIndex={selected ? undefined : -1}
onKeyDown={this.onKeyDown}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
type="button"
>
{this.props.children}
</button>
)
}
}

View file

@ -0,0 +1,11 @@
/** The tab bar type. */
export enum TabBarType {
/** Standard tabs */
Tabs,
/** Simpler switch appearance */
Switch,
/** Vertical tabs */
Vertical,
}

View file

@ -1,22 +1,12 @@
import * as React from 'react'
import classNames from 'classnames'
import { dragAndDropManager } from '../lib/drag-and-drop-manager'
import { TabBarItem } from './tab-bar-item'
import { TabBarType } from './tab-bar-type'
export { TabBarType } from './tab-bar-type'
/** Time to wait for drag element hover before switching tabs */
const dragTabSwitchWaitTime = 500
/** The tab bar type. */
export enum TabBarType {
/** Standard tabs */
Tabs,
/** Simpler switch appearance */
Switch,
/** Vertical tabs */
Vertical,
}
interface ITabBarProps {
/** The currently selected tab. */
readonly selectedIndex: number
@ -144,68 +134,3 @@ export class TabBar extends React.Component<ITabBarProps, {}> {
})
}
}
interface ITabBarItemProps {
readonly index: number
readonly selected: boolean
readonly onClick: (index: number) => void
readonly onMouseEnter: (index: number) => void
readonly onMouseLeave: () => void
readonly onSelectAdjacent: (
direction: 'next' | 'previous',
index: number
) => void
readonly onButtonRef: (
index: number,
button: HTMLButtonElement | null
) => void
readonly type?: TabBarType
}
class TabBarItem extends React.Component<ITabBarItemProps, {}> {
private onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
this.props.onClick(this.props.index)
}
private onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
const { type, index } = this.props
const previousKey = type === TabBarType.Vertical ? 'ArrowUp' : 'ArrowLeft'
const nextKey = type === TabBarType.Vertical ? 'ArrowDown' : 'ArrowRight'
if (event.key === previousKey) {
this.props.onSelectAdjacent('previous', index)
event.preventDefault()
} else if (event.key === nextKey) {
this.props.onSelectAdjacent('next', index)
event.preventDefault()
}
}
private onButtonRef = (buttonRef: HTMLButtonElement | null) => {
this.props.onButtonRef(this.props.index, buttonRef)
}
private onMouseEnter = () => {
this.props.onMouseEnter(this.props.index)
}
public render() {
const selected = this.props.selected
const className = classNames('tab-bar-item', { selected })
return (
<button
ref={this.onButtonRef}
className={className}
onClick={this.onClick}
role="tab"
aria-selected={selected}
tabIndex={selected ? undefined : -1}
onKeyDown={this.onKeyDown}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
type="button"
>
{this.props.children}
</button>
)
}
}

View file

@ -16,6 +16,7 @@ import { PopupType } from '../../models/popup'
import { PreferencesTab } from '../../models/preferences'
import { Ref } from '../lib/ref'
import { suggestedExternalEditor } from '../../lib/editors/shared'
import { TutorialStepInstructions } from './tutorial-step-instruction'
const TutorialPanelImage = encodePathAsUrl(
__dirname,
@ -310,93 +311,6 @@ export class TutorialPanel extends React.Component<
}
}
interface ITutorialStepInstructionsProps {
/** Text displayed to summarize this step */
readonly summaryText: string
/** Used to find out if this step has been completed */
readonly isComplete: (step: ValidTutorialStep) => boolean
/** The step for this section */
readonly sectionId: ValidTutorialStep
/** Used to find out if this is the next step for the user to complete */
readonly isNextStepTodo: (step: ValidTutorialStep) => boolean
/** ID of the currently expanded tutorial step
* (used to determine if this step is expanded)
*/
readonly currentlyOpenSectionId: ValidTutorialStep
/** Skip button (if possible for this step) */
readonly skipLinkButton?: JSX.Element
/** Handler to open and close section */
readonly onSummaryClick: (id: ValidTutorialStep) => void
}
/** A step (summary and expandable description) in the tutorial side panel */
class TutorialStepInstructions extends React.Component<
ITutorialStepInstructionsProps
> {
public render() {
return (
<li key={this.props.sectionId} onClick={this.onSummaryClick}>
<details
open={this.props.sectionId === this.props.currentlyOpenSectionId}
onClick={this.onSummaryClick}
>
{this.renderSummary()}
<div className="contents">{this.props.children}</div>
</details>
</li>
)
}
private renderSummary = () => {
const shouldShowSkipLink =
this.props.skipLinkButton !== undefined &&
this.props.currentlyOpenSectionId === this.props.sectionId &&
this.props.isNextStepTodo(this.props.sectionId)
return (
<summary>
{this.renderTutorialStepIcon()}
<span className="summary-text">{this.props.summaryText}</span>
<span className="hang-right">
{shouldShowSkipLink ? (
this.props.skipLinkButton
) : (
<Octicon symbol={OcticonSymbol.chevronDown} />
)}
</span>
</summary>
)
}
private renderTutorialStepIcon() {
if (this.props.isComplete(this.props.sectionId)) {
return (
<div className="green-circle">
<Octicon symbol={OcticonSymbol.check} />
</div>
)
}
// ugh zero-indexing
const stepNumber = orderedTutorialSteps.indexOf(this.props.sectionId) + 1
return this.props.isNextStepTodo(this.props.sectionId) ? (
<div className="blue-circle">{stepNumber}</div>
) : (
<div className="empty-circle">{stepNumber}</div>
)
}
private onSummaryClick = (e: React.MouseEvent<HTMLElement>) => {
// prevents the default behavior of toggling on a `details` html element
// so we don't have to fight it with our react state
// for more info see:
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#Events
e.preventDefault()
this.props.onSummaryClick(this.props.sectionId)
}
}
const SkipLinkButton: React.FunctionComponent<{
onClick: () => void
}> = props => <LinkButton onClick={props.onClick}>Skip</LinkButton>

View file

@ -0,0 +1,94 @@
import * as React from 'react'
import {
ValidTutorialStep,
orderedTutorialSteps,
} from '../../models/tutorial-step'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
interface ITutorialStepInstructionsProps {
/** Text displayed to summarize this step */
readonly summaryText: string
/** Used to find out if this step has been completed */
readonly isComplete: (step: ValidTutorialStep) => boolean
/** The step for this section */
readonly sectionId: ValidTutorialStep
/** Used to find out if this is the next step for the user to complete */
readonly isNextStepTodo: (step: ValidTutorialStep) => boolean
/** ID of the currently expanded tutorial step
* (used to determine if this step is expanded)
*/
readonly currentlyOpenSectionId: ValidTutorialStep
/** Skip button (if possible for this step) */
readonly skipLinkButton?: JSX.Element
/** Handler to open and close section */
readonly onSummaryClick: (id: ValidTutorialStep) => void
}
/** A step (summary and expandable description) in the tutorial side panel */
export class TutorialStepInstructions extends React.Component<
ITutorialStepInstructionsProps
> {
public render() {
return (
<li key={this.props.sectionId} onClick={this.onSummaryClick}>
<details
open={this.props.sectionId === this.props.currentlyOpenSectionId}
onClick={this.onSummaryClick}
>
{this.renderSummary()}
<div className="contents">{this.props.children}</div>
</details>
</li>
)
}
private renderSummary = () => {
const shouldShowSkipLink =
this.props.skipLinkButton !== undefined &&
this.props.currentlyOpenSectionId === this.props.sectionId &&
this.props.isNextStepTodo(this.props.sectionId)
return (
<summary>
{this.renderTutorialStepIcon()}
<span className="summary-text">{this.props.summaryText}</span>
<span className="hang-right">
{shouldShowSkipLink ? (
this.props.skipLinkButton
) : (
<Octicon symbol={OcticonSymbol.chevronDown} />
)}
</span>
</summary>
)
}
private renderTutorialStepIcon() {
if (this.props.isComplete(this.props.sectionId)) {
return (
<div className="green-circle">
<Octicon symbol={OcticonSymbol.check} />
</div>
)
}
// ugh zero-indexing
const stepNumber = orderedTutorialSteps.indexOf(this.props.sectionId) + 1
return this.props.isNextStepTodo(this.props.sectionId) ? (
<div className="blue-circle">{stepNumber}</div>
) : (
<div className="empty-circle">{stepNumber}</div>
)
}
private onSummaryClick = (e: React.MouseEvent<HTMLElement>) => {
// prevents the default behavior of toggling on a `details` html element
// so we don't have to fight it with our react state
// for more info see:
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#Events
e.preventDefault()
this.props.onSummaryClick(this.props.sectionId)
}
}

View file

@ -3,8 +3,6 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { WindowState } from '../../lib/window-state'
interface IFullScreenInfoProps {
// react-unused-props-and-state doesn't understand getDerivedStateFromProps
// tslint:disable-next-line:react-unused-props-and-state
readonly windowState: WindowState | null
}

View file

@ -33,11 +33,7 @@ const commonConfig: webpack.Configuration = {
include: path.resolve(__dirname, 'src'),
use: [
{
loader: 'awesome-typescript-loader',
options: {
useBabel: false,
useCache: true,
},
loader: 'ts-loader',
},
],
exclude: /node_modules/,
@ -220,14 +216,9 @@ highlighter.module!.rules = [
include: path.resolve(__dirname, 'src/highlighter'),
use: [
{
loader: 'awesome-typescript-loader',
loader: 'ts-loader',
options: {
useBabel: false,
useCache: true,
configFileName: path.resolve(
__dirname,
'src/highlighter/tsconfig.json'
),
configFile: path.resolve(__dirname, 'src/highlighter/tsconfig.json'),
},
},
],

View file

@ -8,7 +8,6 @@ Our linting tooling uses a combination of
* [Prettier](https://github.com/prettier/prettier)
* [ESLint](https://github.com/eslint/eslint)
* [TSLint](https://github.com/palantir/tslint).
Most (if not all) editors have integrations for these tools so that they will report errors and fix formatting in realtime. See [tooling](./tooling.md) for how to set these integrations up while developing for desktop.

View file

@ -1,11 +1,11 @@
# TypeScript Style Guide
Most of our preferred style when writing typescript is configured in our [`tslint.json`](../../tslint.json) and [`.eslintrc.yml`](../../.eslintrc.yml) files.
Most of our preferred style when writing typescript is configured in our [`.eslintrc.yml`](../../.eslintrc.yml) files.
## Do
- Use camelCase for methods
- Use PascalCase for class names
- Enable [TSLint](https://palantir.github.io/tslint/usage/third-party-tools/) and [ESLint](https://eslint.org/docs/user-guide/integrations) in your editor
- [ESLint](https://eslint.org/docs/user-guide/integrations) in your editor
# Documenting your code

View file

@ -9,8 +9,7 @@ Recommended packages:
* [build-npm-apm](https://atom.io/packages/build-npm-apm) - invoke
all npm scripts straight from the editor by pressing F7 (requires
[build](https://atom.io/packages/build))
* [linter](https://atom.io/packages/linter) and
[linter-tslint](https://atom.io/packages/linter-tslint) - shows linter errors and warning in the editor
* [linter](https://atom.io/packages/linter) - shows linter errors and warning in the editor
You can install them all at once with:

View file

@ -29,9 +29,8 @@
"lint": "yarn prettier && yarn lint:src",
"lint:fix": "yarn prettier --write && yarn lint:src:fix",
"prettier": "prettier --check \"./**/*.{ts,tsx,js,json,jsx,scss,html,yaml,yml}\"",
"lint:src": "yarn tslint && yarn eslint-check && yarn eslint",
"lint:src:fix": "yarn tslint --fix && yarn eslint --fix",
"tslint": "tslint -p .",
"lint:src": "yarn eslint-check && yarn eslint",
"lint:src:fix": "yarn eslint --fix",
"eslint": "eslint --cache --rulesdir ./eslint-rules \"./eslint-rules/**/*.js\" \"./script/**/*.ts{,x}\" \"./app/{src,typings,test}/**/*.{j,t}s{,x}\" \"./changelog.json\"",
"eslint-check": "eslint --print-config .eslintrc.* | eslint-config-prettier-check",
"publish": "ts-node -P script/tsconfig.json script/publish.ts",
@ -60,14 +59,13 @@
"@types/marked": "^4.0.1",
"@types/plist": "^3.0.2",
"@types/react-color": "^3.0.4",
"@typescript-eslint/eslint-plugin": "^3.5.0",
"@typescript-eslint/experimental-utils": "^3.5.0",
"@typescript-eslint/parser": "^3.5.0",
"@typescript-eslint/typescript-estree": "^3.5.0",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/experimental-utils": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"@typescript-eslint/typescript-estree": "^5.10.2",
"airbnb-browser-shims": "^3.0.0",
"ajv": "^6.4.0",
"awesome-node-loader": "^1.1.0",
"awesome-typescript-loader": "^5.2.1",
"azure-storage": "^2.10.4",
"chalk": "^2.2.0",
"clean-webpack-plugin": "^0.1.19",
@ -76,11 +74,10 @@
"css-loader": "^2.1.0",
"eslint": "^7.3.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-jsdoc": "^28.6.1",
"eslint-plugin-jsdoc": "^37.7.0",
"eslint-plugin-json": "^2.1.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react": "7.26.1",
"express": "^4.15.0",
"fake-indexeddb": "^2.0.4",
"file-loader": "^2.0.0",
@ -108,12 +105,9 @@
"style-loader": "^0.21.0",
"to-camel-case": "^1.0.0",
"ts-jest": "^26.4.4",
"ts-loader": "^8",
"ts-node": "^7.0.0",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.14.0",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "^3.9.5",
"typescript-tslint-plugin": "^0.0.6",
"typescript": "^4.5.5",
"webpack": "^4.8.3",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-middleware": "^3.1.3",

View file

@ -273,60 +273,27 @@ function moveAnalysisFiles() {
}
function copyDependencies() {
const originalPackage: Package = require(path.join(
projectRoot,
'app',
'package.json'
))
const pkg: Package = require(path.join(projectRoot, 'app', 'package.json'))
const oldDependencies = originalPackage.dependencies
const newDependencies: PackageLookup = {}
for (const name of Object.keys(oldDependencies)) {
const spec = oldDependencies[name]
if (externals.indexOf(name) !== -1) {
newDependencies[name] = spec
}
}
const oldDevDependencies = originalPackage.devDependencies
const newDevDependencies: PackageLookup = {}
if (isDevelopmentBuild) {
for (const name of Object.keys(oldDevDependencies)) {
const spec = oldDevDependencies[name]
if (externals.indexOf(name) !== -1) {
newDevDependencies[name] = spec
}
}
}
const filterExternals = (dependencies: Record<string, string>) =>
Object.fromEntries(
Object.entries(dependencies).filter(([k]) => externals.includes(k))
)
// The product name changes depending on whether it's a prod build or dev
// build, so that we can have them running side by side.
const updatedPackage = Object.assign({}, originalPackage, {
productName: getProductName(),
dependencies: newDependencies,
devDependencies: newDevDependencies,
})
if (!isDevelopmentBuild) {
delete updatedPackage.devDependencies
}
fs.writeFileSync(
path.join(outRoot, 'package.json'),
JSON.stringify(updatedPackage)
)
pkg.productName = getProductName()
pkg.dependencies = filterExternals(pkg.dependencies)
pkg.devDependencies =
isDevelopmentBuild && pkg.devDependencies
? filterExternals(pkg.devDependencies)
: {}
fs.writeFileSync(path.join(outRoot, 'package.json'), JSON.stringify(pkg))
fs.removeSync(path.resolve(outRoot, 'node_modules'))
if (
Object.keys(newDependencies).length ||
Object.keys(newDevDependencies).length
) {
console.log(' Installing dependencies via yarn…')
cp.execSync('yarn install', { cwd: outRoot, env: process.env })
}
console.log(' Installing dependencies via yarn…')
cp.execSync('yarn install', { cwd: outRoot, env: process.env })
console.log(' Copying desktop-trampoline…')
const desktopTrampolineDir = path.resolve(outRoot, 'desktop-trampoline')

View file

@ -120,7 +120,7 @@ export async function run(args: ReadonlyArray<string>): Promise<void> {
console.log(`Set!`)
} catch (e) {
console.warn(`Setting the app version failed 😿
(${e.message})
(${e instanceof Error ? e.message : e})
Please manually set it to ${nextVersion} in app/package.json.`)
}
@ -177,7 +177,11 @@ export async function run(args: ReadonlyArray<string>): Promise<void> {
console.log('Added!')
printInstructions(nextVersion, [])
} catch (e) {
console.warn(`Writing the changelog failed 😿\n(${e.message})`)
console.warn(
`Writing the changelog failed 😿\n(${
e instanceof Error ? e.message : e
})`
)
printInstructions(nextVersion, newEntries)
}
} else {

8
script/globals.d.ts vendored
View file

@ -1,9 +1,7 @@
// type annotations for package.json dependencies
type PackageLookup = { [key: string]: string }
type Package = {
dependencies: PackageLookup
devDependencies: PackageLookup
productName?: string
dependencies: Record<string, string>
devDependencies?: Record<string, string>
}
declare namespace NodeJS {

View file

@ -14,15 +14,10 @@
"sourceMap": true,
"jsx": "react",
"strict": true,
"noEmit": true,
"outDir": "./out",
"lib": ["ES2017", "DOM", "DOM.Iterable", "ES2018.Promise", "ES2020.string"]
"lib": ["ES2017", "DOM", "DOM.Iterable", "ES2018.Promise", "ES2020.string"],
"useUnknownInCatchVariables": false
},
"plugins": [
{
"name": "typescript-tslint-plugin"
}
],
"include": ["app/**/*.ts", "app/**/*.tsx", "app/**/*.d.tsx"],
"exclude": [
"node_modules",

View file

@ -1,15 +0,0 @@
{
"extends": ["tslint-config-prettier"],
"rulesDirectory": ["node_modules/tslint-microsoft-contrib/"],
"rules": {
"promise-must-complete": true,
"react-unused-props-and-state": [
true,
{
"props-interface-regex": "Props$",
"state-interface-regex": "State$"
}
],
"react-this-binding-issue": true
}
}

1012
yarn.lock

File diff suppressed because it is too large Load diff