mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 03:21:44 +03:00
Finish enabling ts-eslint (#5944)
- prefer `null`, `!= null` and `== null` instead of `undefined` - disallow `as`, add comments for the existing usages of `as` - make `tsconfig.json` a bit stricter - minor fixes to other files that were missed # Important Notes N/A
This commit is contained in:
parent
130fd803f0
commit
fa23e800e5
@ -1,4 +1,4 @@
|
||||
/** Helper module for running esbuild in watch mode. */
|
||||
/** @file A helper module for running esbuild in watch mode. */
|
||||
|
||||
import * as esbuild from 'esbuild'
|
||||
|
||||
@ -6,21 +6,20 @@ import * as esbuild from 'esbuild'
|
||||
* @param config - Configuration for the esbuild command.
|
||||
* @param onRebuild - Callback to be called after each rebuild.
|
||||
* @param inject - See [esbuild docs](https://esbuild.github.io/api/#inject).
|
||||
*
|
||||
**/
|
||||
export function toWatchOptions(
|
||||
config: esbuild.BuildOptions,
|
||||
*/
|
||||
export function toWatchOptions<T extends esbuild.BuildOptions>(
|
||||
config: T,
|
||||
onRebuild?: () => void,
|
||||
inject?: esbuild.BuildOptions['inject']
|
||||
): esbuild.BuildOptions {
|
||||
) {
|
||||
return {
|
||||
...config,
|
||||
inject: [...(config.inject ?? []), ...(inject ?? [])],
|
||||
watch: {
|
||||
onRebuild(error, result) {
|
||||
onRebuild(error) {
|
||||
if (error) console.error('watch build failed:', error)
|
||||
else onRebuild?.()
|
||||
},
|
||||
},
|
||||
}
|
||||
} satisfies esbuild.BuildOptions
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ const ALLOWED_DEFAULT_IMPORT_MODULES = `${DEFAULT_IMPORT_ONLY_MODULES}|react-hot
|
||||
const OUR_MODULES = 'enso-content-config|enso-common'
|
||||
const RELATIVE_MODULES =
|
||||
'bin\\u002Fproject-manager|bin\\u002Fserver|config\\u002Fparser|authentication|config|debug|index|ipc|naming|paths|preload|security'
|
||||
const STRING_LITERAL = 'Literal[raw=/^["\']/]'
|
||||
const STRING_LITERAL = ':matches(Literal[raw=/^["\']/], TemplateLiteral)'
|
||||
const JSX = ':matches(JSXElement, JSXFragment)'
|
||||
const NOT_PASCAL_CASE = '/^(?!_?([A-Z][a-z0-9]*)+$)/'
|
||||
const NOT_CAMEL_CASE = '/^(?!_?[a-z][a-z0-9*]*([A-Z0-9][a-z0-9]*)*$)/'
|
||||
@ -102,7 +102,7 @@ const RESTRICTED_SYNTAXES = [
|
||||
},
|
||||
{
|
||||
selector:
|
||||
':not(:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression, SwitchStatement, SwitchCase, IfStatement:has(.consequent > :matches(ReturnStatement, ThrowStatement)):has(.alternate :matches(ReturnStatement, ThrowStatement)))) > * > ReturnStatement',
|
||||
':not(:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression, SwitchStatement, SwitchCase, IfStatement:has(.consequent > :matches(ReturnStatement, ThrowStatement)):has(.alternate :matches(ReturnStatement, ThrowStatement)), TryStatement:has(.block > :matches(ReturnStatement, ThrowStatement)):has(:matches([handler=null], .handler :matches(ReturnStatement, ThrowStatement))):has(:matches([finalizer=null], .finalizer :matches(ReturnStatement, ThrowStatement))))) > * > ReturnStatement',
|
||||
message: 'No early returns',
|
||||
},
|
||||
{
|
||||
@ -139,8 +139,18 @@ const RESTRICTED_SYNTAXES = [
|
||||
message: 'Use `as const` for top-level object literals only containing string literals',
|
||||
},
|
||||
{
|
||||
selector: ':matches(TSNullKeyword, Literal[raw=null])',
|
||||
message: 'Use `undefined` instead of `null`',
|
||||
// Matches `as T` in either:
|
||||
// - anything other than a variable declaration
|
||||
// - a variable declaration that is not at the top level
|
||||
// - a top-level variable declaration that shouldn't be `as const`
|
||||
// - a top-level variable declaration that should be `as const`, but is `as SomeActualType` instead
|
||||
selector: `:matches(:not(VariableDeclarator) > TSAsExpression, :not(:matches(Program, ExportNamedDeclaration)) > VariableDeclaration > * > TSAsExpression, :matches(Program, ExportNamedDeclaration) > VariableDeclaration > * > TSAsExpression > .expression:not(ObjectExpression:has(Property > ${STRING_LITERAL}.value):not(:has(Property > .value:not(${STRING_LITERAL})))), :matches(Program, ExportNamedDeclaration) > VariableDeclaration > * > TsAsExpression:not(:has(TSTypeReference > Identifier[name=const])) > ObjectExpression.expression:has(Property > ${STRING_LITERAL}.value):not(:has(Property > .value:not(${STRING_LITERAL}))))`,
|
||||
message: 'Avoid `as T`. Consider using a type annotation instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
':matches(TSUndefinedKeyword, Identifier[name=undefined], UnaryExpression[operator=void]:not(:has(CallExpression.argument)), BinaryExpression[operator=/^===?$/]:has(UnaryExpression.left[operator=typeof]):has(Literal.right[value=undefined]))',
|
||||
message: 'Use `null` instead of `undefined`, `void 0`, or `typeof x === "undefined"`',
|
||||
},
|
||||
{
|
||||
selector: 'ExportNamedDeclaration > VariableDeclaration[kind=let]',
|
||||
@ -216,7 +226,7 @@ export default [
|
||||
...tsEslint.configs.recommended?.rules,
|
||||
...tsEslint.configs['recommended-requiring-type-checking']?.rules,
|
||||
...tsEslint.configs.strict?.rules,
|
||||
eqeqeq: 'error',
|
||||
eqeqeq: ['error', 'always', { null: 'never' }],
|
||||
'sort-imports': ['error', { allowSeparatedGroups: true }],
|
||||
'no-restricted-syntax': ['error', ...RESTRICTED_SYNTAXES],
|
||||
'prefer-arrow-callback': 'error',
|
||||
|
@ -18,7 +18,6 @@ import yargs from 'yargs'
|
||||
import * as common from 'enso-common'
|
||||
|
||||
import * as paths from './paths.js'
|
||||
import * as shared from './shared.js'
|
||||
import signArchivesMacOs from './tasks/signArchivesMacOs.js'
|
||||
|
||||
import BUILD_INFO from '../../build.json' assert { type: 'json' }
|
||||
@ -28,7 +27,9 @@ import BUILD_INFO from '../../build.json' assert { type: 'json' }
|
||||
* @see `args` definition below for fields description.
|
||||
*/
|
||||
export interface Arguments {
|
||||
target?: string
|
||||
// This is returned by a third-party library we do not control.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
target?: string | undefined
|
||||
iconsDist: string
|
||||
guiDist: string
|
||||
ideDist: string
|
||||
@ -81,7 +82,7 @@ export const args: Arguments = await yargs(process.argv.slice(2))
|
||||
export function createElectronBuilderConfig(passedArgs: Arguments): electronBuilder.Configuration {
|
||||
return {
|
||||
appId: 'org.enso',
|
||||
productName: shared.PRODUCT_NAME,
|
||||
productName: common.PRODUCT_NAME,
|
||||
extraMetadata: {
|
||||
version: BUILD_INFO.version,
|
||||
},
|
||||
@ -104,13 +105,16 @@ export function createElectronBuilderConfig(passedArgs: Arguments): electronBuil
|
||||
protocols: [
|
||||
/** Electron URL protocol scheme definition for deep links to authentication flow pages. */
|
||||
{
|
||||
name: `${shared.PRODUCT_NAME} url`,
|
||||
name: `${common.PRODUCT_NAME} url`,
|
||||
schemes: [common.DEEP_LINK_SCHEME],
|
||||
role: 'Editor',
|
||||
},
|
||||
],
|
||||
mac: {
|
||||
// We do not use compression as the build time is huge and file size saving is almost zero.
|
||||
// This type assertion is UNSAFE, and any users MUST verify that
|
||||
// they are passing a valid value to `target`.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
target: (passedArgs.target ?? 'dmg') as macOptions.MacOsTargetName,
|
||||
icon: `${passedArgs.iconsDist}/icon.icns`,
|
||||
category: 'public.app-category.developer-tools',
|
||||
@ -223,7 +227,9 @@ export function createElectronBuilderConfig(passedArgs: Arguments): electronBuil
|
||||
console.log(' • Notarizing.')
|
||||
await electronNotarize.notarize({
|
||||
// This will always be defined since we set it at the top of this object.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
// The type-cast is safe because this is only executes
|
||||
// when `platform === electronBuilder.Platform.MAC`.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, no-restricted-syntax
|
||||
appBundleId: (buildOptions as macOptions.MacConfiguration).appId!,
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
|
@ -1,9 +0,0 @@
|
||||
/** @file This module contains metadata about the product and distribution.
|
||||
* For example, it contains:
|
||||
* - the custom URL protocol scheme definitions.
|
||||
*
|
||||
* This metadata is used in both the code building the client resources and the packaged code
|
||||
* itself. */
|
||||
|
||||
/** Name of the product. */
|
||||
export const PRODUCT_NAME = 'Enso'
|
@ -134,8 +134,8 @@ function initOpenUrlListener(window: () => electron.BrowserWindow) {
|
||||
event.preventDefault()
|
||||
if (parsedUrl.protocol !== `${common.DEEP_LINK_SCHEME}:`) {
|
||||
logger.error(`${url} is not a deep link, ignoring.`)
|
||||
return
|
||||
} else {
|
||||
window().webContents.send(ipc.Channel.openDeepLink, url)
|
||||
}
|
||||
window().webContents.send(ipc.Channel.openDeepLink, url)
|
||||
})
|
||||
}
|
||||
|
@ -31,9 +31,7 @@ export function pathOrPanic(args: config.Args): string {
|
||||
/** Executes the Project Manager with given arguments. */
|
||||
async function exec(args: config.Args, processArgs: string[]) {
|
||||
const binPath = pathOrPanic(args)
|
||||
return await execFile(binPath, processArgs).catch(function (err) {
|
||||
throw err
|
||||
})
|
||||
return await execFile(binPath, processArgs)
|
||||
}
|
||||
|
||||
/** Spawn Project Manager process.
|
||||
@ -62,5 +60,7 @@ export function spawn(args: config.Args, processArgs: string[]): childProcess.Ch
|
||||
export async function version(args: config.Args) {
|
||||
if (args.options.engine.value) {
|
||||
return await exec(args, ['--version']).then(t => t.stdout)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -105,26 +105,26 @@ export class Server {
|
||||
|
||||
process(request: http.IncomingMessage, response: http.ServerResponse) {
|
||||
const requestUrl = request.url
|
||||
if (requestUrl === undefined) {
|
||||
if (requestUrl == null) {
|
||||
logger.error('Request URL is null.')
|
||||
return
|
||||
}
|
||||
const url = requestUrl.split('?')[0]
|
||||
const resource = url === '/' ? '/index.html' : requestUrl
|
||||
const resourceFile = `${this.config.dir}${resource}`
|
||||
fs.readFile(resourceFile, (err, data) => {
|
||||
if (err) {
|
||||
logger.error(`Resource '${resource}' not found.`)
|
||||
} else {
|
||||
const contentType = mime.contentType(path.extname(resourceFile))
|
||||
const contentLength = data.length
|
||||
if (contentType !== false) {
|
||||
response.setHeader('Content-Type', contentType)
|
||||
} else {
|
||||
const url = requestUrl.split('?')[0]
|
||||
const resource = url === '/' ? '/index.html' : requestUrl
|
||||
const resourceFile = `${this.config.dir}${resource}`
|
||||
fs.readFile(resourceFile, (err, data) => {
|
||||
if (err) {
|
||||
logger.error(`Resource '${resource}' not found.`)
|
||||
} else {
|
||||
const contentType = mime.contentType(path.extname(resourceFile))
|
||||
const contentLength = data.length
|
||||
if (contentType !== false) {
|
||||
response.setHeader('Content-Type', contentType)
|
||||
}
|
||||
response.setHeader('Content-Length', contentLength)
|
||||
response.writeHead(HTTP_STATUS_OK)
|
||||
response.end(data)
|
||||
}
|
||||
response.setHeader('Content-Length', contentLength)
|
||||
response.writeHead(HTTP_STATUS_OK)
|
||||
response.end(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ function printHelp(cfg: PrintHelpConfig) {
|
||||
|
||||
for (const [groupName, group] of Object.entries(cfg.args.groups)) {
|
||||
let section = sections[groupName]
|
||||
if (section === undefined) {
|
||||
if (section == null) {
|
||||
section = new Section()
|
||||
sections[groupName] = section
|
||||
}
|
||||
@ -95,7 +95,7 @@ function printHelp(cfg: PrintHelpConfig) {
|
||||
const cmdOption = naming.camelToKebabCase(optionName)
|
||||
maxOptionLength = Math.max(maxOptionLength, stringLength(cmdOption))
|
||||
const section = sections[option.name]
|
||||
if (section !== undefined) {
|
||||
if (section != null) {
|
||||
section.entries.unshift([cmdOption, option])
|
||||
} else {
|
||||
topLevelSection.entries.push([cmdOption, option])
|
||||
@ -127,12 +127,12 @@ function printHelp(cfg: PrintHelpConfig) {
|
||||
? option.description
|
||||
: option.description.slice(0, firstSentenceSplit + 1)
|
||||
const otherSentences = option.description.slice(firstSentence.length)
|
||||
const def =
|
||||
option.defaultDescription ??
|
||||
((option.default ?? undefined) as string | undefined)
|
||||
// We explicitly set the default for string options to be `null` in `parseArgs`.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const def = option.defaultDescription ?? (option.default as string | null)
|
||||
const defIsEmptyArray = Array.isArray(def) && def.length === 0
|
||||
let defaults = ''
|
||||
if (def !== undefined && def !== '' && !defIsEmptyArray) {
|
||||
if (def != null && def !== '' && !defIsEmptyArray) {
|
||||
defaults = ` Defaults to ${chalk.green(def)}.`
|
||||
}
|
||||
const description = firstSentence + defaults + chalk.gray(otherSentences)
|
||||
@ -152,49 +152,50 @@ function wordWrap(str: string, width: number): string[] {
|
||||
if (width <= 0) {
|
||||
logger.error(`Cannot perform word wrap. The output width is set to '${width}'.`)
|
||||
return []
|
||||
}
|
||||
let firstLine = true
|
||||
let line = ''
|
||||
const lines = []
|
||||
const inputLines = str.split('\n')
|
||||
for (const inputLine of inputLines) {
|
||||
if (!firstLine) {
|
||||
lines.push(line)
|
||||
line = ''
|
||||
}
|
||||
firstLine = false
|
||||
for (const originalWord of inputLine.split(' ')) {
|
||||
let word = originalWord
|
||||
if (stringLength(word) > width) {
|
||||
if (line.length > 0) {
|
||||
lines.push(line)
|
||||
line = ''
|
||||
} else {
|
||||
let firstLine = true
|
||||
let line = ''
|
||||
const lines = []
|
||||
const inputLines = str.split('\n')
|
||||
for (const inputLine of inputLines) {
|
||||
if (!firstLine) {
|
||||
lines.push(line)
|
||||
line = ''
|
||||
}
|
||||
firstLine = false
|
||||
for (const originalWord of inputLine.split(' ')) {
|
||||
let word = originalWord
|
||||
if (stringLength(word) > width) {
|
||||
if (line.length > 0) {
|
||||
lines.push(line)
|
||||
line = ''
|
||||
}
|
||||
const wordChunks = []
|
||||
while (stringLength(word) > width) {
|
||||
wordChunks.push(word.slice(0, width))
|
||||
word = word.slice(width)
|
||||
}
|
||||
wordChunks.push(word)
|
||||
for (const wordChunk of wordChunks) {
|
||||
lines.push(wordChunk)
|
||||
}
|
||||
} else {
|
||||
if (stringLength(line) + stringLength(word) >= width) {
|
||||
lines.push(line)
|
||||
line = ''
|
||||
}
|
||||
if (line.length !== 0) {
|
||||
line += ' '
|
||||
}
|
||||
line += word
|
||||
}
|
||||
const wordChunks = []
|
||||
while (stringLength(word) > width) {
|
||||
wordChunks.push(word.slice(0, width))
|
||||
word = word.slice(width)
|
||||
}
|
||||
wordChunks.push(word)
|
||||
for (const wordChunk of wordChunks) {
|
||||
lines.push(wordChunk)
|
||||
}
|
||||
} else {
|
||||
if (stringLength(line) + stringLength(word) >= width) {
|
||||
lines.push(line)
|
||||
line = ''
|
||||
}
|
||||
if (line.length !== 0) {
|
||||
line += ' '
|
||||
}
|
||||
line += word
|
||||
}
|
||||
}
|
||||
if (line) {
|
||||
lines.push(line)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
if (line) {
|
||||
lines.push(line)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// ======================
|
||||
@ -205,7 +206,7 @@ export class ChromeOption {
|
||||
constructor(public name: string, public value?: string) {}
|
||||
|
||||
display(): string {
|
||||
const value = this.value === undefined ? '' : `=${this.value}`
|
||||
const value = this.value == null ? '' : `=${this.value}`
|
||||
return `--${this.name}${value}`
|
||||
}
|
||||
}
|
||||
@ -237,20 +238,20 @@ function argvAndChromeOptions(processArgs: string[]): ArgvAndChromeOptions {
|
||||
const chromeOptions: ChromeOption[] = []
|
||||
for (let i = 0; i < processArgs.length; i++) {
|
||||
const processArg = processArgs[i]
|
||||
if (processArg !== undefined) {
|
||||
const match = processArg.match(chromeOptionRegex) ?? undefined
|
||||
if (match?.[1] !== undefined) {
|
||||
if (processArg != null) {
|
||||
const match = processArg.match(chromeOptionRegex)
|
||||
if (match?.[1] != null) {
|
||||
const optionName = match[1]
|
||||
const optionValue = match[2]
|
||||
if (optionValue !== undefined) {
|
||||
if (optionValue != null) {
|
||||
chromeOptions.push(new ChromeOption(optionName, optionValue))
|
||||
} else {
|
||||
const nextArgValue = processArgs[i + 1]
|
||||
if (nextArgValue !== undefined && !nextArgValue.startsWith('-')) {
|
||||
if (nextArgValue != null && !nextArgValue.startsWith('-')) {
|
||||
chromeOptions.push(new ChromeOption(optionName, nextArgValue))
|
||||
i++
|
||||
} else {
|
||||
chromeOptions.push(new ChromeOption(optionName, undefined))
|
||||
chromeOptions.push(new ChromeOption(optionName))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -274,19 +275,14 @@ export function parseArgs() {
|
||||
const yargsOptions = args
|
||||
.optionsRecursive()
|
||||
.reduce((opts: Record<string, yargsModule.Options>, option) => {
|
||||
const yargsParam = Object.assign(
|
||||
{},
|
||||
{
|
||||
...option,
|
||||
requiresArg: ['string', 'array'].includes(option.type),
|
||||
default: undefined,
|
||||
}
|
||||
)
|
||||
opts[naming.camelToKebabCase(option.qualifiedName())] = {
|
||||
// Required because ensogl-pack has `defaultDescription`
|
||||
// defined as `string | null` instead of `string | undefined` like in yargs
|
||||
...yargsParam,
|
||||
defaultDescription: yargsParam.defaultDescription ?? undefined,
|
||||
...option,
|
||||
requiresArg: ['string', 'array'].includes(option.type),
|
||||
default: null,
|
||||
// Required because yargs defines `defaultDescription`
|
||||
// as `string | undefined`, not `string | null`.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
defaultDescription: option.defaultDescription ?? undefined,
|
||||
}
|
||||
return opts
|
||||
}, {})
|
||||
@ -316,7 +312,7 @@ export function parseArgs() {
|
||||
interface YargsArgs {
|
||||
// We don't control the naming of this third-party API.
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
[key: string]: string[] | string | undefined
|
||||
[key: string]: string[] | string
|
||||
_: string[]
|
||||
// Exists only when the `populate--` option is enabled.
|
||||
'--'?: string[]
|
||||
@ -324,27 +320,29 @@ export function parseArgs() {
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}
|
||||
|
||||
let parseError: Error | undefined
|
||||
// Required otherwise TypeScript thinks it's always `null`.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
let parseError = null as Error | null
|
||||
// The type assertion is required since `parse` may return a `Promise`
|
||||
// when an async middleware has been registered, but we are not doing that.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const { '--': unexpectedArgs, ...parsedArgs } = optParser.parse(
|
||||
argv,
|
||||
{},
|
||||
// @ts-expect-error Yargs' typings are wrong.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
(err: Error | null) => {
|
||||
if (err) {
|
||||
if (err != null) {
|
||||
parseError = err
|
||||
}
|
||||
}
|
||||
) as YargsArgs
|
||||
// The type assertion above is required since `parse` is defined to potentially return a `Promise`.
|
||||
// This only happens when an async middleware has been registered though.
|
||||
|
||||
for (const option of args.optionsRecursive()) {
|
||||
const arg = parsedArgs[naming.camelToKebabCase(option.qualifiedName())]
|
||||
const isArray = Array.isArray(arg)
|
||||
// Yargs parses missing array options as `[undefined]`.
|
||||
const isInvalidArray = isArray && arg.length === 1 && arg[0] === undefined
|
||||
if (arg !== undefined && !isInvalidArray) {
|
||||
const isInvalidArray = isArray && arg.length === 1 && arg[0] == null
|
||||
if (arg != null && !isInvalidArray) {
|
||||
option.value = arg
|
||||
option.setByUser = true
|
||||
}
|
||||
@ -386,10 +384,10 @@ export function parseArgs() {
|
||||
const helpRequested = args.options.help.value || args.options.helpExtended.value
|
||||
if (helpRequested) {
|
||||
printHelpAndExit()
|
||||
} else if (parseError !== undefined) {
|
||||
} else if (parseError != null) {
|
||||
logger.error(parseError.message)
|
||||
printHelpAndExit(1)
|
||||
} else if (unexpectedArgs !== undefined) {
|
||||
} else if (unexpectedArgs != null) {
|
||||
const unexpectedArgsString = unexpectedArgs.map(arg => JSON.stringify(arg)).join(' ')
|
||||
logger.error(`Unexpected arguments found: '${unexpectedArgsString}'.`)
|
||||
printHelpAndExit(1)
|
||||
|
@ -50,5 +50,7 @@ async function getInfo() {
|
||||
/** Print the current system information. */
|
||||
export async function printInfo() {
|
||||
const info = await getInfo()
|
||||
// This function does not accept `null` as its second parameter.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
console.log(JSON.stringify(info, undefined, INDENT_SIZE))
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ const INDENT_SIZE = 4
|
||||
/** The Electron application. It is responsible for starting all the required services, and
|
||||
* displaying and managing the app window. */
|
||||
class App {
|
||||
window: electron.BrowserWindow | undefined = undefined
|
||||
server: server.Server | undefined = undefined
|
||||
window: electron.BrowserWindow | null = null
|
||||
server: server.Server | null = null
|
||||
args: config.Args = config.CONFIG
|
||||
isQuitting = false
|
||||
|
||||
@ -194,7 +194,7 @@ class App {
|
||||
frame: useFrame,
|
||||
transparent: false,
|
||||
titleBarStyle: useHiddenInsetTitleBar ? 'hiddenInset' : 'default',
|
||||
vibrancy: useVibrancy ? 'fullscreen-ui' : undefined,
|
||||
...(useVibrancy ? { vibrancy: 'fullscreen-ui' } : {}),
|
||||
}
|
||||
const window = new electron.BrowserWindow(windowPreferences)
|
||||
window.setMenuBarVisibility(false)
|
||||
@ -272,7 +272,7 @@ class App {
|
||||
|
||||
/** Redirect the web view to `localhost:<port>` to see the served website. */
|
||||
loadWindowContent() {
|
||||
if (this.window !== undefined) {
|
||||
if (this.window != null) {
|
||||
const urlCfg: Record<string, string> = {}
|
||||
for (const option of this.args.optionsRecursive()) {
|
||||
if (option.value !== option.default && option.passToWebApplication) {
|
||||
|
@ -34,7 +34,7 @@ electron.contextBridge.exposeInMainWorld('enso_lifecycle', {
|
||||
|
||||
// Save and load profile data.
|
||||
let onProfiles: ((profiles: string[]) => void)[] = []
|
||||
let profilesLoaded: string[] | undefined
|
||||
let profilesLoaded: string[] | null
|
||||
electron.ipcRenderer.on(ipc.Channel.profilesLoaded, (_event, profiles: string[]) => {
|
||||
for (const callback of onProfiles) {
|
||||
callback(profiles)
|
||||
@ -49,7 +49,7 @@ electron.contextBridge.exposeInMainWorld('enso_profiling_data', {
|
||||
},
|
||||
// Requests any loaded profiling logs.
|
||||
loadProfiles: (callback: (profiles: string[]) => void) => {
|
||||
if (profilesLoaded === undefined) {
|
||||
if (profilesLoaded == null) {
|
||||
electron.ipcRenderer.send('load-profiles')
|
||||
onProfiles.push(callback)
|
||||
} else {
|
||||
|
@ -25,14 +25,14 @@ function getChecksum(path, type) {
|
||||
return new Promise(
|
||||
// This JSDoc annotation is required for correct types that are also type-safe.
|
||||
/** @param {(value: string) => void} resolve - Fulfill the promise with the given value. */
|
||||
function (resolve, reject) {
|
||||
(resolve, reject) => {
|
||||
const hash = cryptoModule.createHash(type)
|
||||
const input = fs.createReadStream(path)
|
||||
input.on('error', reject)
|
||||
input.on('data', function (chunk) {
|
||||
input.on('data', chunk => {
|
||||
hash.update(chunk)
|
||||
})
|
||||
input.on('close', function () {
|
||||
input.on('close', () => {
|
||||
resolve(hash.digest('hex'))
|
||||
})
|
||||
}
|
||||
|
@ -9,3 +9,6 @@
|
||||
* For example: the deep link URL
|
||||
* `enso://authentication/register?code=...&state=...` uses this scheme. */
|
||||
export const DEEP_LINK_SCHEME = 'enso'
|
||||
|
||||
/** Name of the product. */
|
||||
export const PRODUCT_NAME = 'Enso'
|
||||
|
@ -106,12 +106,16 @@ export async function* filesToCopyProvider(wasmArtifacts: string, assetsPath: st
|
||||
/**
|
||||
* Generate the builder options.
|
||||
*/
|
||||
export function bundlerOptions(args: Arguments): esbuild.BuildOptions {
|
||||
export function bundlerOptions(args: Arguments) {
|
||||
const { outputPath, ensoglAppPath, wasmArtifacts, assetsPath } = args
|
||||
return {
|
||||
// This is required to make the `true` properties be typed as `boolean`.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
let trueBoolean = true as boolean
|
||||
const buildOptions = {
|
||||
// Disabling naming convention because these are third-party options.
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
absWorkingDir: THIS_PATH,
|
||||
bundle: true,
|
||||
bundle: trueBoolean,
|
||||
entryPoints: [path.resolve(THIS_PATH, 'src', 'index.ts')],
|
||||
outdir: outputPath,
|
||||
outbase: 'src',
|
||||
@ -119,25 +123,23 @@ export function bundlerOptions(args: Arguments): esbuild.BuildOptions {
|
||||
esbuildPluginYaml.yamlPlugin({}),
|
||||
esbuildPluginNodeModules.NodeModulesPolyfillPlugin(),
|
||||
esbuildPluginNodeGlobals.NodeGlobalsPolyfillPlugin({ buffer: true, process: true }),
|
||||
// We do not control naming of third-party options.
|
||||
esbuildPluginAlias({ ensogl_app: ensoglAppPath }),
|
||||
esbuildPluginTime(),
|
||||
esbuildPluginCopy.create(() => filesToCopyProvider(wasmArtifacts, assetsPath)),
|
||||
],
|
||||
define: {
|
||||
// Disabling naming convention because these are third-party options.
|
||||
GIT_HASH: JSON.stringify(git('rev-parse HEAD')),
|
||||
GIT_STATUS: JSON.stringify(git('status --short --porcelain')),
|
||||
BUILD_INFO: JSON.stringify(BUILD_INFO),
|
||||
},
|
||||
sourcemap: true,
|
||||
minify: true,
|
||||
metafile: true,
|
||||
sourcemap: trueBoolean,
|
||||
minify: trueBoolean,
|
||||
metafile: trueBoolean,
|
||||
format: 'esm',
|
||||
publicPath: '/assets',
|
||||
platform: 'browser',
|
||||
incremental: true,
|
||||
color: true,
|
||||
incremental: trueBoolean,
|
||||
color: trueBoolean,
|
||||
logOverride: {
|
||||
// Happens in ScalaJS-generated parser (scala-parser.js):
|
||||
// 6 │ "fileLevelThis": this
|
||||
@ -153,14 +155,18 @@ export function bundlerOptions(args: Arguments): esbuild.BuildOptions {
|
||||
'suspicious-boolean-not': 'silent',
|
||||
},
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}
|
||||
} satisfies esbuild.BuildOptions
|
||||
// The narrower type is required to avoid non-null assertions elsewhere.
|
||||
// The intersection with `esbuild.BuildOptions` is required to allow mutation.
|
||||
const correctlyTypedBuildOptions: esbuild.BuildOptions & typeof buildOptions = buildOptions
|
||||
return correctlyTypedBuildOptions
|
||||
}
|
||||
|
||||
/** The basic, common settings for the bundler, based on the environment variables.
|
||||
*
|
||||
* Note that they should be further customized as per the needs of the specific workflow (e.g. watch vs. build).
|
||||
*/
|
||||
export function bundlerOptionsFromEnv(): esbuild.BuildOptions {
|
||||
export function bundlerOptionsFromEnv() {
|
||||
return bundlerOptions(argumentsFromEnv())
|
||||
}
|
||||
|
||||
|
@ -59,9 +59,7 @@ async function checkMinSupportedVersion(config: typeof contentConfig.OPTIONS) {
|
||||
)
|
||||
if (
|
||||
typeof appConfig === 'object' &&
|
||||
// `typeof x === 'object'` narrows to `object | null`, not `object | undefined`
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
appConfig !== null &&
|
||||
appConfig != null &&
|
||||
'minimumSupportedVersion' in appConfig
|
||||
) {
|
||||
const minSupportedVersion = appConfig.minimumSupportedVersion
|
||||
@ -88,13 +86,13 @@ function displayDeprecatedVersionDialog() {
|
||||
'This version is no longer supported. Please download a new one.'
|
||||
)
|
||||
|
||||
const root = document.getElementById('root') ?? undefined
|
||||
const root = document.getElementById('root')
|
||||
const versionCheckDiv = document.createElement('div')
|
||||
versionCheckDiv.id = 'version-check'
|
||||
versionCheckDiv.className = 'auth-info'
|
||||
versionCheckDiv.style.display = 'block'
|
||||
versionCheckDiv.appendChild(versionCheckText)
|
||||
if (root === undefined) {
|
||||
if (root == null) {
|
||||
console.error('Cannot find the root DOM element.')
|
||||
} else {
|
||||
root.appendChild(versionCheckDiv)
|
||||
|
@ -49,7 +49,7 @@ export class ProjectManager {
|
||||
name: name,
|
||||
missingComponentAction: action,
|
||||
}
|
||||
if (template !== undefined) {
|
||||
if (template != null) {
|
||||
Object.assign(params, {
|
||||
projectTemplate: template,
|
||||
})
|
||||
|
@ -9,4 +9,4 @@ const OPTS = bundler.bundleOptions()
|
||||
const ROOT = OPTS.outdir
|
||||
const ASSETS = ROOT
|
||||
await esbuild.build(OPTS)
|
||||
await guiServer.start({ root: ROOT, assets: ASSETS })
|
||||
await guiServer.start({ root: ROOT, assets: ASSETS ?? null })
|
||||
|
@ -11,5 +11,5 @@ const OPTS = bundler.watchOptions(() => {
|
||||
await esbuild.build(OPTS)
|
||||
const LIVE_SERVER = await guiServer.start({
|
||||
root: OPTS.outdir,
|
||||
assets: OPTS.outdir,
|
||||
assets: OPTS.outdir ?? null,
|
||||
})
|
||||
|
@ -79,20 +79,21 @@ export function create(filesProvider) {
|
||||
}
|
||||
})
|
||||
build.onLoad({ filter: /.*/, namespace: PLUGIN_NAME }, async () => {
|
||||
if (build.initialOptions.outdir === undefined) {
|
||||
if (build.initialOptions.outdir == null) {
|
||||
console.error('`copy-plugin` requires `outdir` to be specified.')
|
||||
return
|
||||
}
|
||||
let watchFiles = []
|
||||
for await (const file of files) {
|
||||
const to = path.join(build.initialOptions.outdir, path.basename(file))
|
||||
await copy(file, to)
|
||||
watchFiles.push(file)
|
||||
}
|
||||
console.log('Copied files.', watchFiles)
|
||||
return {
|
||||
contents: '',
|
||||
watchFiles,
|
||||
} else {
|
||||
let watchFiles = []
|
||||
for await (const file of files) {
|
||||
const to = path.join(build.initialOptions.outdir, path.basename(file))
|
||||
await copy(file, to)
|
||||
watchFiles.push(file)
|
||||
}
|
||||
console.log('Copied files.', watchFiles)
|
||||
return {
|
||||
contents: '',
|
||||
watchFiles,
|
||||
}
|
||||
}
|
||||
})
|
||||
build.onEnd(() => {
|
||||
|
@ -107,6 +107,7 @@ function intoAmplifyErrorOrThrow(error: unknown): AmplifyError {
|
||||
* The caller can then handle them via pattern matching on the {@link results.Result} type. */
|
||||
export class Cognito {
|
||||
constructor(
|
||||
// @ts-expect-error This will be used in a future PR.
|
||||
private readonly logger: loggerProvider.Logger,
|
||||
private readonly platform: platformModule.Platform,
|
||||
amplifyConfig: config.AmplifyConfig
|
||||
|
@ -130,7 +130,7 @@ export function toNestedAmplifyConfig(config: AmplifyConfig): NestedAmplifyConfi
|
||||
userPoolWebClientId: config.userPoolWebClientId,
|
||||
oauth: {
|
||||
options: {
|
||||
urlOpener: config.urlOpener,
|
||||
...(config.urlOpener ? { urlOpener: config.urlOpener } : {}),
|
||||
},
|
||||
domain: config.domain,
|
||||
scope: config.scope,
|
||||
|
@ -31,7 +31,7 @@ export enum AuthEvent {
|
||||
|
||||
/** Returns `true` if the given `string` is an {@link AuthEvent}. */
|
||||
function isAuthEvent(value: string): value is AuthEvent {
|
||||
return Object.values(AuthEvent).includes(value as AuthEvent);
|
||||
return Object.values<string>(AuthEvent).includes(value);
|
||||
}
|
||||
|
||||
// =================================
|
||||
|
@ -81,8 +81,8 @@ interface AuthContextType {
|
||||
signInWithPassword: (email: string, password: string) => Promise<void>;
|
||||
/** Session containing the currently authenticated user's authentication information.
|
||||
*
|
||||
* If the user has not signed in, the session will be `undefined`. */
|
||||
session: UserSession | undefined;
|
||||
* If the user has not signed in, the session will be `null`. */
|
||||
session: UserSession | null;
|
||||
}
|
||||
|
||||
// Eslint doesn't like headings.
|
||||
@ -94,7 +94,7 @@ interface AuthContextType {
|
||||
* An `as ...` cast is unsafe. We use this cast when creating the context. So it appears that the
|
||||
* `AuthContextType` can be unsafely (i.e., only partially) initialized as a result of this.
|
||||
*
|
||||
* So it appears that we should remove the cast and initialize the context as `undefined` instead.
|
||||
* So it appears that we should remove the cast and initialize the context as `null` instead.
|
||||
*
|
||||
* **However**, initializing a context the existing way is the recommended way to initialize a
|
||||
* context in React. It is safe, for non-obvious reasons. It is safe because the `AuthContext` is
|
||||
@ -131,8 +131,8 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
const navigate = router.useNavigate();
|
||||
const onAuthenticated = react.useCallback(props.onAuthenticated, []);
|
||||
const [initialized, setInitialized] = react.useState(false);
|
||||
const [userSession, setUserSession] = react.useState<UserSession | undefined>(
|
||||
undefined
|
||||
const [userSession, setUserSession] = react.useState<UserSession | null>(
|
||||
null
|
||||
);
|
||||
|
||||
/** Fetch the JWT access token from the session via the AWS Amplify library.
|
||||
@ -144,7 +144,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
const fetchSession = async () => {
|
||||
if (session.none) {
|
||||
setInitialized(true);
|
||||
setUserSession(undefined);
|
||||
setUserSession(null);
|
||||
} else {
|
||||
const { accessToken, email } = session.val;
|
||||
|
||||
|
@ -148,7 +148,7 @@ function loadAmplifyConfig(
|
||||
return {
|
||||
...baseConfig,
|
||||
...platformConfig,
|
||||
urlOpener,
|
||||
...(urlOpener ? { urlOpener } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@ -225,13 +225,14 @@ function handleAuthResponse(url: string) {
|
||||
try {
|
||||
/** # Safety
|
||||
*
|
||||
* It is safe to disable the `no-unsafe-member-access` and `no-unsafe-call` lints here
|
||||
* It is safe to disable the `no-unsafe-call` lint here
|
||||
* because we know that the `Auth` object has the `_handleAuthResponse` method, and we
|
||||
* know that it is safe to call it with the `url` argument. There is no way to prove
|
||||
* this to the TypeScript compiler, because these methods are intentionally not part of
|
||||
* the public AWS Amplify API. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
|
||||
await (amplify.Auth as any)._handleAuthResponse(url);
|
||||
// @ts-expect-error `_handleAuthResponse` is a private method without typings.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
await amplify.Auth._handleAuthResponse(url);
|
||||
} finally {
|
||||
/** Restore the original `window.location.replaceState` function. */
|
||||
window.history.replaceState = replaceState;
|
||||
|
@ -11,13 +11,11 @@
|
||||
/** Path data for the SVG icons used in app. */
|
||||
export const PATHS = {
|
||||
/** Path data for the `@` icon SVG. */
|
||||
at:
|
||||
"M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 " +
|
||||
"8.959 0 01-4.5 1.207",
|
||||
at: `M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 \
|
||||
8.959 0 01-4.5 1.207`,
|
||||
/** Path data for the lock icon SVG. */
|
||||
lock:
|
||||
"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 " +
|
||||
"0 00-8 0v4h8z",
|
||||
lock: `M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 \
|
||||
0 00-8 0v4h8z`,
|
||||
/** Path data for the "right arrow" icon SVG. */
|
||||
rightArrow: "M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z",
|
||||
/** Path data for the "create account" icon SVG. */
|
||||
|
@ -8,6 +8,7 @@
|
||||
* included in the final bundle. */
|
||||
// It is safe to disable `no-restricted-syntax` because the `PascalCase` naming is required
|
||||
// as per the above comment.
|
||||
// @ts-expect-error See above comment for why this import is needed.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-restricted-syntax
|
||||
import * as React from "react";
|
||||
import * as reactDOM from "react-dom/client";
|
||||
|
@ -21,6 +21,8 @@ export interface Logger {
|
||||
// === LoggerContext ===
|
||||
// =====================
|
||||
|
||||
/** See {@link AuthContext} for safety details. */
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const LoggerContext = react.createContext<Logger>({} as Logger);
|
||||
|
||||
// ======================
|
||||
|
@ -29,7 +29,7 @@ export function brand<T extends Brand<string>>(s: NoBrand & Omit<T, '$brand'>):
|
||||
// branded type, even if that value is not an instance of the branded type. For example, the
|
||||
// string "foo" could be cast to the `UserPoolId` branded type, although this string is clearly
|
||||
// not a valid `UserPoolId`. This is acceptable because the branded type is only used to prevent
|
||||
// accidental misuse of values, and not to enforce correctness. That is, it is up to the
|
||||
// accidental misuse of values, and not to enforce correctness. That is, it is up to the
|
||||
// programmer to declare the correct type of a value. After that point, it is up to the branded
|
||||
// type to keep that guarantee by preventing accidental misuse of the value.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
|
@ -146,67 +146,67 @@ async function genIcons(outputDir) {
|
||||
return
|
||||
} else {
|
||||
console.log(`Generating icons to ${outputDir}`)
|
||||
}
|
||||
console.log('Generating SVG icons.')
|
||||
await fs.mkdir(path.resolve(outputDir, 'svg'), { recursive: true })
|
||||
await fs.mkdir(path.resolve(outputDir, 'png'), { recursive: true })
|
||||
for (const size of sizes) {
|
||||
let name = `icon_${size}x${size}.svg`
|
||||
let logo = new Logo(size, true).generate()
|
||||
await fs.writeFile(`${outputDir}/svg/${name}`, logo)
|
||||
}
|
||||
|
||||
console.log('Generating SVG icons.')
|
||||
await fs.mkdir(path.resolve(outputDir, 'svg'), { recursive: true })
|
||||
await fs.mkdir(path.resolve(outputDir, 'png'), { recursive: true })
|
||||
for (const size of sizes) {
|
||||
let name = `icon_${size}x${size}.svg`
|
||||
let logo = new Logo(size, true).generate()
|
||||
await fs.writeFile(`${outputDir}/svg/${name}`, logo)
|
||||
}
|
||||
/// Please note that this function converts the SVG to PNG
|
||||
/// AND KEEPS THE METADATA INFORMATION ABOUT DPI OF 144.
|
||||
/// It is required to properly display png images on MacOS.
|
||||
/// There is currently no other way in `sharp` to do it.
|
||||
console.log('Generating PNG icons.')
|
||||
for (const size of sizes) {
|
||||
let inName = `icon_${size}x${size}.svg`
|
||||
let outName = `icon_${size}x${size}.png`
|
||||
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
||||
.png()
|
||||
.resize({
|
||||
width: size,
|
||||
kernel: sharp.kernel.mitchell,
|
||||
})
|
||||
.toFile(`${outputDir}/png/${outName}`)
|
||||
}
|
||||
|
||||
/// Please note that this function converts the SVG to PNG
|
||||
/// AND KEEPS THE METADATA INFORMATION ABOUT DPI OF 144.
|
||||
/// It is required to properly display png images on MacOS.
|
||||
/// There is currently no other way in `sharp` to do it.
|
||||
console.log('Generating PNG icons.')
|
||||
for (const size of sizes) {
|
||||
let inName = `icon_${size}x${size}.svg`
|
||||
let outName = `icon_${size}x${size}.png`
|
||||
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
||||
.png()
|
||||
.resize({
|
||||
width: size,
|
||||
kernel: sharp.kernel.mitchell,
|
||||
})
|
||||
.toFile(`${outputDir}/png/${outName}`)
|
||||
}
|
||||
for (const size of sizes.slice(1)) {
|
||||
let size2 = size / 2
|
||||
let inName = `icon_${size}x${size}.svg`
|
||||
let outName = `icon_${size2}x${size2}@2x.png`
|
||||
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
||||
.png()
|
||||
.resize({
|
||||
width: size,
|
||||
kernel: sharp.kernel.mitchell,
|
||||
})
|
||||
.toFile(`${outputDir}/png/${outName}`)
|
||||
}
|
||||
|
||||
for (const size of sizes.slice(1)) {
|
||||
let size2 = size / 2
|
||||
let inName = `icon_${size}x${size}.svg`
|
||||
let outName = `icon_${size2}x${size2}@2x.png`
|
||||
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
||||
.png()
|
||||
.resize({
|
||||
width: size,
|
||||
kernel: sharp.kernel.mitchell,
|
||||
})
|
||||
.toFile(`${outputDir}/png/${outName}`)
|
||||
}
|
||||
if (os.platform() === 'darwin') {
|
||||
console.log('Generating ICNS.')
|
||||
childProcess.execSync(`cp -R ${outputDir}/png ${outputDir}/png.iconset`)
|
||||
childProcess.execSync(
|
||||
`iconutil --convert icns --output ${outputDir}/icon.icns ${outputDir}/png.iconset`
|
||||
)
|
||||
}
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
console.log('Generating ICNS.')
|
||||
childProcess.execSync(`cp -R ${outputDir}/png ${outputDir}/png.iconset`)
|
||||
childProcess.execSync(
|
||||
`iconutil --convert icns --output ${outputDir}/icon.icns ${outputDir}/png.iconset`
|
||||
)
|
||||
}
|
||||
console.log('Generating ICO.')
|
||||
let files = []
|
||||
for (const size of winSizes) {
|
||||
let inName = `icon_${size}x${size}.png`
|
||||
let data = await fs.readFile(`${outputDir}/png/${inName}`)
|
||||
files.push(data)
|
||||
}
|
||||
const icoBuffer = await toIco(files)
|
||||
fsSync.writeFileSync(`${outputDir}/icon.ico`, icoBuffer)
|
||||
|
||||
console.log('Generating ICO.')
|
||||
let files = []
|
||||
for (const size of winSizes) {
|
||||
let inName = `icon_${size}x${size}.png`
|
||||
let data = await fs.readFile(`${outputDir}/png/${inName}`)
|
||||
files.push(data)
|
||||
let handle = await fs.open(donePath, 'w')
|
||||
await handle.close()
|
||||
return
|
||||
}
|
||||
const icoBuffer = await toIco(files)
|
||||
fsSync.writeFileSync(`${outputDir}/icon.ico`, icoBuffer)
|
||||
|
||||
let handle = await fs.open(donePath, 'w')
|
||||
await handle.close()
|
||||
}
|
||||
|
||||
/** Main entry function. */
|
||||
|
@ -22,7 +22,7 @@ export const LIVE_RELOAD_LISTENER_PATH = path.join(DIR_NAME, 'live-reload.js')
|
||||
|
||||
/** Start the server.
|
||||
*
|
||||
* @param {{ root: string; assets?: string; port?: number; }} options - Configuration options for this server.
|
||||
* @param {{ root: string; assets?: string | null; port?: number; }} options - Configuration options for this server.
|
||||
*/
|
||||
export async function start({ root, assets, port }) {
|
||||
assets = assets ?? path.join(root, 'assets')
|
||||
|
@ -4,8 +4,9 @@ declare module 'enso-gui-server' {
|
||||
export const LIVE_RELOAD_LISTENER_PATH: string
|
||||
|
||||
interface StartParams {
|
||||
root?: string
|
||||
assets?: string
|
||||
// These are not values we explicitly supply
|
||||
root: string
|
||||
assets?: string | null
|
||||
port?: number
|
||||
}
|
||||
interface ExectionInfo {
|
||||
|
2
app/ide-desktop/lib/types/modules.d.ts
vendored
2
app/ide-desktop/lib/types/modules.d.ts
vendored
@ -48,6 +48,8 @@ declare module 'create-servers' {
|
||||
}
|
||||
export default function (
|
||||
option: CreateServersOptions,
|
||||
// This is a third-party module which we have no control over.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
errorHandler: (err: HttpError | undefined) => void
|
||||
): unknown
|
||||
}
|
||||
|
@ -7,16 +7,18 @@
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"checkJs": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"removeComments": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"target": "ES2019",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
|
@ -39,6 +39,9 @@ export function requireEnvResolvedPath(name: string) {
|
||||
*/
|
||||
export function requireEnvPathExist(name: string) {
|
||||
const value = requireEnv(name)
|
||||
if (fs.existsSync(value)) return value
|
||||
else throw Error(`File with path ${value} read from environment variable ${name} is missing.`)
|
||||
if (fs.existsSync(value)) {
|
||||
return value
|
||||
} else {
|
||||
throw Error(`File with path ${value} read from environment variable ${name} is missing.`)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user