mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 08:21:49 +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'
|
import * as esbuild from 'esbuild'
|
||||||
|
|
||||||
@ -6,21 +6,20 @@ import * as esbuild from 'esbuild'
|
|||||||
* @param config - Configuration for the esbuild command.
|
* @param config - Configuration for the esbuild command.
|
||||||
* @param onRebuild - Callback to be called after each rebuild.
|
* @param onRebuild - Callback to be called after each rebuild.
|
||||||
* @param inject - See [esbuild docs](https://esbuild.github.io/api/#inject).
|
* @param inject - See [esbuild docs](https://esbuild.github.io/api/#inject).
|
||||||
*
|
*/
|
||||||
**/
|
export function toWatchOptions<T extends esbuild.BuildOptions>(
|
||||||
export function toWatchOptions(
|
config: T,
|
||||||
config: esbuild.BuildOptions,
|
|
||||||
onRebuild?: () => void,
|
onRebuild?: () => void,
|
||||||
inject?: esbuild.BuildOptions['inject']
|
inject?: esbuild.BuildOptions['inject']
|
||||||
): esbuild.BuildOptions {
|
) {
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
inject: [...(config.inject ?? []), ...(inject ?? [])],
|
inject: [...(config.inject ?? []), ...(inject ?? [])],
|
||||||
watch: {
|
watch: {
|
||||||
onRebuild(error, result) {
|
onRebuild(error) {
|
||||||
if (error) console.error('watch build failed:', error)
|
if (error) console.error('watch build failed:', error)
|
||||||
else onRebuild?.()
|
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 OUR_MODULES = 'enso-content-config|enso-common'
|
||||||
const RELATIVE_MODULES =
|
const RELATIVE_MODULES =
|
||||||
'bin\\u002Fproject-manager|bin\\u002Fserver|config\\u002Fparser|authentication|config|debug|index|ipc|naming|paths|preload|security'
|
'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 JSX = ':matches(JSXElement, JSXFragment)'
|
||||||
const NOT_PASCAL_CASE = '/^(?!_?([A-Z][a-z0-9]*)+$)/'
|
const NOT_PASCAL_CASE = '/^(?!_?([A-Z][a-z0-9]*)+$)/'
|
||||||
const NOT_CAMEL_CASE = '/^(?!_?[a-z][a-z0-9*]*([A-Z0-9][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:
|
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',
|
message: 'No early returns',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -139,8 +139,18 @@ const RESTRICTED_SYNTAXES = [
|
|||||||
message: 'Use `as const` for top-level object literals only containing string literals',
|
message: 'Use `as const` for top-level object literals only containing string literals',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: ':matches(TSNullKeyword, Literal[raw=null])',
|
// Matches `as T` in either:
|
||||||
message: 'Use `undefined` instead of `null`',
|
// - 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]',
|
selector: 'ExportNamedDeclaration > VariableDeclaration[kind=let]',
|
||||||
@ -216,7 +226,7 @@ export default [
|
|||||||
...tsEslint.configs.recommended?.rules,
|
...tsEslint.configs.recommended?.rules,
|
||||||
...tsEslint.configs['recommended-requiring-type-checking']?.rules,
|
...tsEslint.configs['recommended-requiring-type-checking']?.rules,
|
||||||
...tsEslint.configs.strict?.rules,
|
...tsEslint.configs.strict?.rules,
|
||||||
eqeqeq: 'error',
|
eqeqeq: ['error', 'always', { null: 'never' }],
|
||||||
'sort-imports': ['error', { allowSeparatedGroups: true }],
|
'sort-imports': ['error', { allowSeparatedGroups: true }],
|
||||||
'no-restricted-syntax': ['error', ...RESTRICTED_SYNTAXES],
|
'no-restricted-syntax': ['error', ...RESTRICTED_SYNTAXES],
|
||||||
'prefer-arrow-callback': 'error',
|
'prefer-arrow-callback': 'error',
|
||||||
|
@ -18,7 +18,6 @@ import yargs from 'yargs'
|
|||||||
import * as common from 'enso-common'
|
import * as common from 'enso-common'
|
||||||
|
|
||||||
import * as paths from './paths.js'
|
import * as paths from './paths.js'
|
||||||
import * as shared from './shared.js'
|
|
||||||
import signArchivesMacOs from './tasks/signArchivesMacOs.js'
|
import signArchivesMacOs from './tasks/signArchivesMacOs.js'
|
||||||
|
|
||||||
import BUILD_INFO from '../../build.json' assert { type: 'json' }
|
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.
|
* @see `args` definition below for fields description.
|
||||||
*/
|
*/
|
||||||
export interface Arguments {
|
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
|
iconsDist: string
|
||||||
guiDist: string
|
guiDist: string
|
||||||
ideDist: string
|
ideDist: string
|
||||||
@ -81,7 +82,7 @@ export const args: Arguments = await yargs(process.argv.slice(2))
|
|||||||
export function createElectronBuilderConfig(passedArgs: Arguments): electronBuilder.Configuration {
|
export function createElectronBuilderConfig(passedArgs: Arguments): electronBuilder.Configuration {
|
||||||
return {
|
return {
|
||||||
appId: 'org.enso',
|
appId: 'org.enso',
|
||||||
productName: shared.PRODUCT_NAME,
|
productName: common.PRODUCT_NAME,
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: BUILD_INFO.version,
|
version: BUILD_INFO.version,
|
||||||
},
|
},
|
||||||
@ -104,13 +105,16 @@ export function createElectronBuilderConfig(passedArgs: Arguments): electronBuil
|
|||||||
protocols: [
|
protocols: [
|
||||||
/** Electron URL protocol scheme definition for deep links to authentication flow pages. */
|
/** 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],
|
schemes: [common.DEEP_LINK_SCHEME],
|
||||||
role: 'Editor',
|
role: 'Editor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
mac: {
|
mac: {
|
||||||
// We do not use compression as the build time is huge and file size saving is almost zero.
|
// 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,
|
target: (passedArgs.target ?? 'dmg') as macOptions.MacOsTargetName,
|
||||||
icon: `${passedArgs.iconsDist}/icon.icns`,
|
icon: `${passedArgs.iconsDist}/icon.icns`,
|
||||||
category: 'public.app-category.developer-tools',
|
category: 'public.app-category.developer-tools',
|
||||||
@ -223,7 +227,9 @@ export function createElectronBuilderConfig(passedArgs: Arguments): electronBuil
|
|||||||
console.log(' • Notarizing.')
|
console.log(' • Notarizing.')
|
||||||
await electronNotarize.notarize({
|
await electronNotarize.notarize({
|
||||||
// This will always be defined since we set it at the top of this object.
|
// 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!,
|
appBundleId: (buildOptions as macOptions.MacConfiguration).appId!,
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId: process.env.APPLEID,
|
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()
|
event.preventDefault()
|
||||||
if (parsedUrl.protocol !== `${common.DEEP_LINK_SCHEME}:`) {
|
if (parsedUrl.protocol !== `${common.DEEP_LINK_SCHEME}:`) {
|
||||||
logger.error(`${url} is not a deep link, ignoring.`)
|
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. */
|
/** Executes the Project Manager with given arguments. */
|
||||||
async function exec(args: config.Args, processArgs: string[]) {
|
async function exec(args: config.Args, processArgs: string[]) {
|
||||||
const binPath = pathOrPanic(args)
|
const binPath = pathOrPanic(args)
|
||||||
return await execFile(binPath, processArgs).catch(function (err) {
|
return await execFile(binPath, processArgs)
|
||||||
throw err
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Spawn Project Manager process.
|
/** 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) {
|
export async function version(args: config.Args) {
|
||||||
if (args.options.engine.value) {
|
if (args.options.engine.value) {
|
||||||
return await exec(args, ['--version']).then(t => t.stdout)
|
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) {
|
process(request: http.IncomingMessage, response: http.ServerResponse) {
|
||||||
const requestUrl = request.url
|
const requestUrl = request.url
|
||||||
if (requestUrl === undefined) {
|
if (requestUrl == null) {
|
||||||
logger.error('Request URL is null.')
|
logger.error('Request URL is null.')
|
||||||
return
|
} else {
|
||||||
}
|
const url = requestUrl.split('?')[0]
|
||||||
const url = requestUrl.split('?')[0]
|
const resource = url === '/' ? '/index.html' : requestUrl
|
||||||
const resource = url === '/' ? '/index.html' : requestUrl
|
const resourceFile = `${this.config.dir}${resource}`
|
||||||
const resourceFile = `${this.config.dir}${resource}`
|
fs.readFile(resourceFile, (err, data) => {
|
||||||
fs.readFile(resourceFile, (err, data) => {
|
if (err) {
|
||||||
if (err) {
|
logger.error(`Resource '${resource}' not found.`)
|
||||||
logger.error(`Resource '${resource}' not found.`)
|
} else {
|
||||||
} else {
|
const contentType = mime.contentType(path.extname(resourceFile))
|
||||||
const contentType = mime.contentType(path.extname(resourceFile))
|
const contentLength = data.length
|
||||||
const contentLength = data.length
|
if (contentType !== false) {
|
||||||
if (contentType !== false) {
|
response.setHeader('Content-Type', contentType)
|
||||||
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)) {
|
for (const [groupName, group] of Object.entries(cfg.args.groups)) {
|
||||||
let section = sections[groupName]
|
let section = sections[groupName]
|
||||||
if (section === undefined) {
|
if (section == null) {
|
||||||
section = new Section()
|
section = new Section()
|
||||||
sections[groupName] = section
|
sections[groupName] = section
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ function printHelp(cfg: PrintHelpConfig) {
|
|||||||
const cmdOption = naming.camelToKebabCase(optionName)
|
const cmdOption = naming.camelToKebabCase(optionName)
|
||||||
maxOptionLength = Math.max(maxOptionLength, stringLength(cmdOption))
|
maxOptionLength = Math.max(maxOptionLength, stringLength(cmdOption))
|
||||||
const section = sections[option.name]
|
const section = sections[option.name]
|
||||||
if (section !== undefined) {
|
if (section != null) {
|
||||||
section.entries.unshift([cmdOption, option])
|
section.entries.unshift([cmdOption, option])
|
||||||
} else {
|
} else {
|
||||||
topLevelSection.entries.push([cmdOption, option])
|
topLevelSection.entries.push([cmdOption, option])
|
||||||
@ -127,12 +127,12 @@ function printHelp(cfg: PrintHelpConfig) {
|
|||||||
? option.description
|
? option.description
|
||||||
: option.description.slice(0, firstSentenceSplit + 1)
|
: option.description.slice(0, firstSentenceSplit + 1)
|
||||||
const otherSentences = option.description.slice(firstSentence.length)
|
const otherSentences = option.description.slice(firstSentence.length)
|
||||||
const def =
|
// We explicitly set the default for string options to be `null` in `parseArgs`.
|
||||||
option.defaultDescription ??
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
((option.default ?? undefined) as string | undefined)
|
const def = option.defaultDescription ?? (option.default as string | null)
|
||||||
const defIsEmptyArray = Array.isArray(def) && def.length === 0
|
const defIsEmptyArray = Array.isArray(def) && def.length === 0
|
||||||
let defaults = ''
|
let defaults = ''
|
||||||
if (def !== undefined && def !== '' && !defIsEmptyArray) {
|
if (def != null && def !== '' && !defIsEmptyArray) {
|
||||||
defaults = ` Defaults to ${chalk.green(def)}.`
|
defaults = ` Defaults to ${chalk.green(def)}.`
|
||||||
}
|
}
|
||||||
const description = firstSentence + defaults + chalk.gray(otherSentences)
|
const description = firstSentence + defaults + chalk.gray(otherSentences)
|
||||||
@ -152,49 +152,50 @@ function wordWrap(str: string, width: number): string[] {
|
|||||||
if (width <= 0) {
|
if (width <= 0) {
|
||||||
logger.error(`Cannot perform word wrap. The output width is set to '${width}'.`)
|
logger.error(`Cannot perform word wrap. The output width is set to '${width}'.`)
|
||||||
return []
|
return []
|
||||||
}
|
} else {
|
||||||
let firstLine = true
|
let firstLine = true
|
||||||
let line = ''
|
let line = ''
|
||||||
const lines = []
|
const lines = []
|
||||||
const inputLines = str.split('\n')
|
const inputLines = str.split('\n')
|
||||||
for (const inputLine of inputLines) {
|
for (const inputLine of inputLines) {
|
||||||
if (!firstLine) {
|
if (!firstLine) {
|
||||||
lines.push(line)
|
lines.push(line)
|
||||||
line = ''
|
line = ''
|
||||||
}
|
}
|
||||||
firstLine = false
|
firstLine = false
|
||||||
for (const originalWord of inputLine.split(' ')) {
|
for (const originalWord of inputLine.split(' ')) {
|
||||||
let word = originalWord
|
let word = originalWord
|
||||||
if (stringLength(word) > width) {
|
if (stringLength(word) > width) {
|
||||||
if (line.length > 0) {
|
if (line.length > 0) {
|
||||||
lines.push(line)
|
lines.push(line)
|
||||||
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) {}
|
constructor(public name: string, public value?: string) {}
|
||||||
|
|
||||||
display(): string {
|
display(): string {
|
||||||
const value = this.value === undefined ? '' : `=${this.value}`
|
const value = this.value == null ? '' : `=${this.value}`
|
||||||
return `--${this.name}${value}`
|
return `--${this.name}${value}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,20 +238,20 @@ function argvAndChromeOptions(processArgs: string[]): ArgvAndChromeOptions {
|
|||||||
const chromeOptions: ChromeOption[] = []
|
const chromeOptions: ChromeOption[] = []
|
||||||
for (let i = 0; i < processArgs.length; i++) {
|
for (let i = 0; i < processArgs.length; i++) {
|
||||||
const processArg = processArgs[i]
|
const processArg = processArgs[i]
|
||||||
if (processArg !== undefined) {
|
if (processArg != null) {
|
||||||
const match = processArg.match(chromeOptionRegex) ?? undefined
|
const match = processArg.match(chromeOptionRegex)
|
||||||
if (match?.[1] !== undefined) {
|
if (match?.[1] != null) {
|
||||||
const optionName = match[1]
|
const optionName = match[1]
|
||||||
const optionValue = match[2]
|
const optionValue = match[2]
|
||||||
if (optionValue !== undefined) {
|
if (optionValue != null) {
|
||||||
chromeOptions.push(new ChromeOption(optionName, optionValue))
|
chromeOptions.push(new ChromeOption(optionName, optionValue))
|
||||||
} else {
|
} else {
|
||||||
const nextArgValue = processArgs[i + 1]
|
const nextArgValue = processArgs[i + 1]
|
||||||
if (nextArgValue !== undefined && !nextArgValue.startsWith('-')) {
|
if (nextArgValue != null && !nextArgValue.startsWith('-')) {
|
||||||
chromeOptions.push(new ChromeOption(optionName, nextArgValue))
|
chromeOptions.push(new ChromeOption(optionName, nextArgValue))
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
chromeOptions.push(new ChromeOption(optionName, undefined))
|
chromeOptions.push(new ChromeOption(optionName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -274,19 +275,14 @@ export function parseArgs() {
|
|||||||
const yargsOptions = args
|
const yargsOptions = args
|
||||||
.optionsRecursive()
|
.optionsRecursive()
|
||||||
.reduce((opts: Record<string, yargsModule.Options>, option) => {
|
.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())] = {
|
opts[naming.camelToKebabCase(option.qualifiedName())] = {
|
||||||
// Required because ensogl-pack has `defaultDescription`
|
...option,
|
||||||
// defined as `string | null` instead of `string | undefined` like in yargs
|
requiresArg: ['string', 'array'].includes(option.type),
|
||||||
...yargsParam,
|
default: null,
|
||||||
defaultDescription: yargsParam.defaultDescription ?? undefined,
|
// Required because yargs defines `defaultDescription`
|
||||||
|
// as `string | undefined`, not `string | null`.
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
defaultDescription: option.defaultDescription ?? undefined,
|
||||||
}
|
}
|
||||||
return opts
|
return opts
|
||||||
}, {})
|
}, {})
|
||||||
@ -316,7 +312,7 @@ export function parseArgs() {
|
|||||||
interface YargsArgs {
|
interface YargsArgs {
|
||||||
// We don't control the naming of this third-party API.
|
// We don't control the naming of this third-party API.
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
[key: string]: string[] | string | undefined
|
[key: string]: string[] | string
|
||||||
_: string[]
|
_: string[]
|
||||||
// Exists only when the `populate--` option is enabled.
|
// Exists only when the `populate--` option is enabled.
|
||||||
'--'?: string[]
|
'--'?: string[]
|
||||||
@ -324,27 +320,29 @@ export function parseArgs() {
|
|||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* 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(
|
const { '--': unexpectedArgs, ...parsedArgs } = optParser.parse(
|
||||||
argv,
|
argv,
|
||||||
{},
|
{},
|
||||||
// @ts-expect-error Yargs' typings are wrong.
|
// @ts-expect-error Yargs' typings are wrong.
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
(err: Error | null) => {
|
(err: Error | null) => {
|
||||||
if (err) {
|
if (err != null) {
|
||||||
parseError = err
|
parseError = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) as YargsArgs
|
) 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()) {
|
for (const option of args.optionsRecursive()) {
|
||||||
const arg = parsedArgs[naming.camelToKebabCase(option.qualifiedName())]
|
const arg = parsedArgs[naming.camelToKebabCase(option.qualifiedName())]
|
||||||
const isArray = Array.isArray(arg)
|
const isArray = Array.isArray(arg)
|
||||||
// Yargs parses missing array options as `[undefined]`.
|
// Yargs parses missing array options as `[undefined]`.
|
||||||
const isInvalidArray = isArray && arg.length === 1 && arg[0] === undefined
|
const isInvalidArray = isArray && arg.length === 1 && arg[0] == null
|
||||||
if (arg !== undefined && !isInvalidArray) {
|
if (arg != null && !isInvalidArray) {
|
||||||
option.value = arg
|
option.value = arg
|
||||||
option.setByUser = true
|
option.setByUser = true
|
||||||
}
|
}
|
||||||
@ -386,10 +384,10 @@ export function parseArgs() {
|
|||||||
const helpRequested = args.options.help.value || args.options.helpExtended.value
|
const helpRequested = args.options.help.value || args.options.helpExtended.value
|
||||||
if (helpRequested) {
|
if (helpRequested) {
|
||||||
printHelpAndExit()
|
printHelpAndExit()
|
||||||
} else if (parseError !== undefined) {
|
} else if (parseError != null) {
|
||||||
logger.error(parseError.message)
|
logger.error(parseError.message)
|
||||||
printHelpAndExit(1)
|
printHelpAndExit(1)
|
||||||
} else if (unexpectedArgs !== undefined) {
|
} else if (unexpectedArgs != null) {
|
||||||
const unexpectedArgsString = unexpectedArgs.map(arg => JSON.stringify(arg)).join(' ')
|
const unexpectedArgsString = unexpectedArgs.map(arg => JSON.stringify(arg)).join(' ')
|
||||||
logger.error(`Unexpected arguments found: '${unexpectedArgsString}'.`)
|
logger.error(`Unexpected arguments found: '${unexpectedArgsString}'.`)
|
||||||
printHelpAndExit(1)
|
printHelpAndExit(1)
|
||||||
|
@ -50,5 +50,7 @@ async function getInfo() {
|
|||||||
/** Print the current system information. */
|
/** Print the current system information. */
|
||||||
export async function printInfo() {
|
export async function printInfo() {
|
||||||
const info = await getInfo()
|
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))
|
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
|
/** The Electron application. It is responsible for starting all the required services, and
|
||||||
* displaying and managing the app window. */
|
* displaying and managing the app window. */
|
||||||
class App {
|
class App {
|
||||||
window: electron.BrowserWindow | undefined = undefined
|
window: electron.BrowserWindow | null = null
|
||||||
server: server.Server | undefined = undefined
|
server: server.Server | null = null
|
||||||
args: config.Args = config.CONFIG
|
args: config.Args = config.CONFIG
|
||||||
isQuitting = false
|
isQuitting = false
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ class App {
|
|||||||
frame: useFrame,
|
frame: useFrame,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
titleBarStyle: useHiddenInsetTitleBar ? 'hiddenInset' : 'default',
|
titleBarStyle: useHiddenInsetTitleBar ? 'hiddenInset' : 'default',
|
||||||
vibrancy: useVibrancy ? 'fullscreen-ui' : undefined,
|
...(useVibrancy ? { vibrancy: 'fullscreen-ui' } : {}),
|
||||||
}
|
}
|
||||||
const window = new electron.BrowserWindow(windowPreferences)
|
const window = new electron.BrowserWindow(windowPreferences)
|
||||||
window.setMenuBarVisibility(false)
|
window.setMenuBarVisibility(false)
|
||||||
@ -272,7 +272,7 @@ class App {
|
|||||||
|
|
||||||
/** Redirect the web view to `localhost:<port>` to see the served website. */
|
/** Redirect the web view to `localhost:<port>` to see the served website. */
|
||||||
loadWindowContent() {
|
loadWindowContent() {
|
||||||
if (this.window !== undefined) {
|
if (this.window != null) {
|
||||||
const urlCfg: Record<string, string> = {}
|
const urlCfg: Record<string, string> = {}
|
||||||
for (const option of this.args.optionsRecursive()) {
|
for (const option of this.args.optionsRecursive()) {
|
||||||
if (option.value !== option.default && option.passToWebApplication) {
|
if (option.value !== option.default && option.passToWebApplication) {
|
||||||
|
@ -34,7 +34,7 @@ electron.contextBridge.exposeInMainWorld('enso_lifecycle', {
|
|||||||
|
|
||||||
// Save and load profile data.
|
// Save and load profile data.
|
||||||
let onProfiles: ((profiles: string[]) => void)[] = []
|
let onProfiles: ((profiles: string[]) => void)[] = []
|
||||||
let profilesLoaded: string[] | undefined
|
let profilesLoaded: string[] | null
|
||||||
electron.ipcRenderer.on(ipc.Channel.profilesLoaded, (_event, profiles: string[]) => {
|
electron.ipcRenderer.on(ipc.Channel.profilesLoaded, (_event, profiles: string[]) => {
|
||||||
for (const callback of onProfiles) {
|
for (const callback of onProfiles) {
|
||||||
callback(profiles)
|
callback(profiles)
|
||||||
@ -49,7 +49,7 @@ electron.contextBridge.exposeInMainWorld('enso_profiling_data', {
|
|||||||
},
|
},
|
||||||
// Requests any loaded profiling logs.
|
// Requests any loaded profiling logs.
|
||||||
loadProfiles: (callback: (profiles: string[]) => void) => {
|
loadProfiles: (callback: (profiles: string[]) => void) => {
|
||||||
if (profilesLoaded === undefined) {
|
if (profilesLoaded == null) {
|
||||||
electron.ipcRenderer.send('load-profiles')
|
electron.ipcRenderer.send('load-profiles')
|
||||||
onProfiles.push(callback)
|
onProfiles.push(callback)
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,14 +25,14 @@ function getChecksum(path, type) {
|
|||||||
return new Promise(
|
return new Promise(
|
||||||
// This JSDoc annotation is required for correct types that are also type-safe.
|
// 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. */
|
/** @param {(value: string) => void} resolve - Fulfill the promise with the given value. */
|
||||||
function (resolve, reject) {
|
(resolve, reject) => {
|
||||||
const hash = cryptoModule.createHash(type)
|
const hash = cryptoModule.createHash(type)
|
||||||
const input = fs.createReadStream(path)
|
const input = fs.createReadStream(path)
|
||||||
input.on('error', reject)
|
input.on('error', reject)
|
||||||
input.on('data', function (chunk) {
|
input.on('data', chunk => {
|
||||||
hash.update(chunk)
|
hash.update(chunk)
|
||||||
})
|
})
|
||||||
input.on('close', function () {
|
input.on('close', () => {
|
||||||
resolve(hash.digest('hex'))
|
resolve(hash.digest('hex'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,3 +9,6 @@
|
|||||||
* For example: the deep link URL
|
* For example: the deep link URL
|
||||||
* `enso://authentication/register?code=...&state=...` uses this scheme. */
|
* `enso://authentication/register?code=...&state=...` uses this scheme. */
|
||||||
export const DEEP_LINK_SCHEME = 'enso'
|
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.
|
* Generate the builder options.
|
||||||
*/
|
*/
|
||||||
export function bundlerOptions(args: Arguments): esbuild.BuildOptions {
|
export function bundlerOptions(args: Arguments) {
|
||||||
const { outputPath, ensoglAppPath, wasmArtifacts, assetsPath } = args
|
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 */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
absWorkingDir: THIS_PATH,
|
absWorkingDir: THIS_PATH,
|
||||||
bundle: true,
|
bundle: trueBoolean,
|
||||||
entryPoints: [path.resolve(THIS_PATH, 'src', 'index.ts')],
|
entryPoints: [path.resolve(THIS_PATH, 'src', 'index.ts')],
|
||||||
outdir: outputPath,
|
outdir: outputPath,
|
||||||
outbase: 'src',
|
outbase: 'src',
|
||||||
@ -119,25 +123,23 @@ export function bundlerOptions(args: Arguments): esbuild.BuildOptions {
|
|||||||
esbuildPluginYaml.yamlPlugin({}),
|
esbuildPluginYaml.yamlPlugin({}),
|
||||||
esbuildPluginNodeModules.NodeModulesPolyfillPlugin(),
|
esbuildPluginNodeModules.NodeModulesPolyfillPlugin(),
|
||||||
esbuildPluginNodeGlobals.NodeGlobalsPolyfillPlugin({ buffer: true, process: true }),
|
esbuildPluginNodeGlobals.NodeGlobalsPolyfillPlugin({ buffer: true, process: true }),
|
||||||
// We do not control naming of third-party options.
|
|
||||||
esbuildPluginAlias({ ensogl_app: ensoglAppPath }),
|
esbuildPluginAlias({ ensogl_app: ensoglAppPath }),
|
||||||
esbuildPluginTime(),
|
esbuildPluginTime(),
|
||||||
esbuildPluginCopy.create(() => filesToCopyProvider(wasmArtifacts, assetsPath)),
|
esbuildPluginCopy.create(() => filesToCopyProvider(wasmArtifacts, assetsPath)),
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
// Disabling naming convention because these are third-party options.
|
|
||||||
GIT_HASH: JSON.stringify(git('rev-parse HEAD')),
|
GIT_HASH: JSON.stringify(git('rev-parse HEAD')),
|
||||||
GIT_STATUS: JSON.stringify(git('status --short --porcelain')),
|
GIT_STATUS: JSON.stringify(git('status --short --porcelain')),
|
||||||
BUILD_INFO: JSON.stringify(BUILD_INFO),
|
BUILD_INFO: JSON.stringify(BUILD_INFO),
|
||||||
},
|
},
|
||||||
sourcemap: true,
|
sourcemap: trueBoolean,
|
||||||
minify: true,
|
minify: trueBoolean,
|
||||||
metafile: true,
|
metafile: trueBoolean,
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
publicPath: '/assets',
|
publicPath: '/assets',
|
||||||
platform: 'browser',
|
platform: 'browser',
|
||||||
incremental: true,
|
incremental: trueBoolean,
|
||||||
color: true,
|
color: trueBoolean,
|
||||||
logOverride: {
|
logOverride: {
|
||||||
// Happens in ScalaJS-generated parser (scala-parser.js):
|
// Happens in ScalaJS-generated parser (scala-parser.js):
|
||||||
// 6 │ "fileLevelThis": this
|
// 6 │ "fileLevelThis": this
|
||||||
@ -153,14 +155,18 @@ export function bundlerOptions(args: Arguments): esbuild.BuildOptions {
|
|||||||
'suspicious-boolean-not': 'silent',
|
'suspicious-boolean-not': 'silent',
|
||||||
},
|
},
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* 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.
|
/** 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).
|
* 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())
|
return bundlerOptions(argumentsFromEnv())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,9 +59,7 @@ async function checkMinSupportedVersion(config: typeof contentConfig.OPTIONS) {
|
|||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
typeof appConfig === 'object' &&
|
typeof appConfig === 'object' &&
|
||||||
// `typeof x === 'object'` narrows to `object | null`, not `object | undefined`
|
appConfig != null &&
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
appConfig !== null &&
|
|
||||||
'minimumSupportedVersion' in appConfig
|
'minimumSupportedVersion' in appConfig
|
||||||
) {
|
) {
|
||||||
const minSupportedVersion = appConfig.minimumSupportedVersion
|
const minSupportedVersion = appConfig.minimumSupportedVersion
|
||||||
@ -88,13 +86,13 @@ function displayDeprecatedVersionDialog() {
|
|||||||
'This version is no longer supported. Please download a new one.'
|
'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')
|
const versionCheckDiv = document.createElement('div')
|
||||||
versionCheckDiv.id = 'version-check'
|
versionCheckDiv.id = 'version-check'
|
||||||
versionCheckDiv.className = 'auth-info'
|
versionCheckDiv.className = 'auth-info'
|
||||||
versionCheckDiv.style.display = 'block'
|
versionCheckDiv.style.display = 'block'
|
||||||
versionCheckDiv.appendChild(versionCheckText)
|
versionCheckDiv.appendChild(versionCheckText)
|
||||||
if (root === undefined) {
|
if (root == null) {
|
||||||
console.error('Cannot find the root DOM element.')
|
console.error('Cannot find the root DOM element.')
|
||||||
} else {
|
} else {
|
||||||
root.appendChild(versionCheckDiv)
|
root.appendChild(versionCheckDiv)
|
||||||
|
@ -49,7 +49,7 @@ export class ProjectManager {
|
|||||||
name: name,
|
name: name,
|
||||||
missingComponentAction: action,
|
missingComponentAction: action,
|
||||||
}
|
}
|
||||||
if (template !== undefined) {
|
if (template != null) {
|
||||||
Object.assign(params, {
|
Object.assign(params, {
|
||||||
projectTemplate: template,
|
projectTemplate: template,
|
||||||
})
|
})
|
||||||
|
@ -9,4 +9,4 @@ const OPTS = bundler.bundleOptions()
|
|||||||
const ROOT = OPTS.outdir
|
const ROOT = OPTS.outdir
|
||||||
const ASSETS = ROOT
|
const ASSETS = ROOT
|
||||||
await esbuild.build(OPTS)
|
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)
|
await esbuild.build(OPTS)
|
||||||
const LIVE_SERVER = await guiServer.start({
|
const LIVE_SERVER = await guiServer.start({
|
||||||
root: OPTS.outdir,
|
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 () => {
|
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.')
|
console.error('`copy-plugin` requires `outdir` to be specified.')
|
||||||
return
|
return
|
||||||
}
|
} else {
|
||||||
let watchFiles = []
|
let watchFiles = []
|
||||||
for await (const file of files) {
|
for await (const file of files) {
|
||||||
const to = path.join(build.initialOptions.outdir, path.basename(file))
|
const to = path.join(build.initialOptions.outdir, path.basename(file))
|
||||||
await copy(file, to)
|
await copy(file, to)
|
||||||
watchFiles.push(file)
|
watchFiles.push(file)
|
||||||
}
|
}
|
||||||
console.log('Copied files.', watchFiles)
|
console.log('Copied files.', watchFiles)
|
||||||
return {
|
return {
|
||||||
contents: '',
|
contents: '',
|
||||||
watchFiles,
|
watchFiles,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
build.onEnd(() => {
|
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. */
|
* The caller can then handle them via pattern matching on the {@link results.Result} type. */
|
||||||
export class Cognito {
|
export class Cognito {
|
||||||
constructor(
|
constructor(
|
||||||
|
// @ts-expect-error This will be used in a future PR.
|
||||||
private readonly logger: loggerProvider.Logger,
|
private readonly logger: loggerProvider.Logger,
|
||||||
private readonly platform: platformModule.Platform,
|
private readonly platform: platformModule.Platform,
|
||||||
amplifyConfig: config.AmplifyConfig
|
amplifyConfig: config.AmplifyConfig
|
||||||
|
@ -130,7 +130,7 @@ export function toNestedAmplifyConfig(config: AmplifyConfig): NestedAmplifyConfi
|
|||||||
userPoolWebClientId: config.userPoolWebClientId,
|
userPoolWebClientId: config.userPoolWebClientId,
|
||||||
oauth: {
|
oauth: {
|
||||||
options: {
|
options: {
|
||||||
urlOpener: config.urlOpener,
|
...(config.urlOpener ? { urlOpener: config.urlOpener } : {}),
|
||||||
},
|
},
|
||||||
domain: config.domain,
|
domain: config.domain,
|
||||||
scope: config.scope,
|
scope: config.scope,
|
||||||
|
@ -31,7 +31,7 @@ export enum AuthEvent {
|
|||||||
|
|
||||||
/** Returns `true` if the given `string` is an {@link AuthEvent}. */
|
/** Returns `true` if the given `string` is an {@link AuthEvent}. */
|
||||||
function isAuthEvent(value: string): value is 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>;
|
signInWithPassword: (email: string, password: string) => Promise<void>;
|
||||||
/** Session containing the currently authenticated user's authentication information.
|
/** Session containing the currently authenticated user's authentication information.
|
||||||
*
|
*
|
||||||
* If the user has not signed in, the session will be `undefined`. */
|
* If the user has not signed in, the session will be `null`. */
|
||||||
session: UserSession | undefined;
|
session: UserSession | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eslint doesn't like headings.
|
// 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
|
* 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.
|
* `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
|
* **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
|
* 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 navigate = router.useNavigate();
|
||||||
const onAuthenticated = react.useCallback(props.onAuthenticated, []);
|
const onAuthenticated = react.useCallback(props.onAuthenticated, []);
|
||||||
const [initialized, setInitialized] = react.useState(false);
|
const [initialized, setInitialized] = react.useState(false);
|
||||||
const [userSession, setUserSession] = react.useState<UserSession | undefined>(
|
const [userSession, setUserSession] = react.useState<UserSession | null>(
|
||||||
undefined
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Fetch the JWT access token from the session via the AWS Amplify library.
|
/** 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 () => {
|
const fetchSession = async () => {
|
||||||
if (session.none) {
|
if (session.none) {
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
setUserSession(undefined);
|
setUserSession(null);
|
||||||
} else {
|
} else {
|
||||||
const { accessToken, email } = session.val;
|
const { accessToken, email } = session.val;
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ function loadAmplifyConfig(
|
|||||||
return {
|
return {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
...platformConfig,
|
...platformConfig,
|
||||||
urlOpener,
|
...(urlOpener ? { urlOpener } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,13 +225,14 @@ function handleAuthResponse(url: string) {
|
|||||||
try {
|
try {
|
||||||
/** # Safety
|
/** # 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
|
* 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
|
* 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
|
* this to the TypeScript compiler, because these methods are intentionally not part of
|
||||||
* the public AWS Amplify API. */
|
* 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
|
// @ts-expect-error `_handleAuthResponse` is a private method without typings.
|
||||||
await (amplify.Auth as any)._handleAuthResponse(url);
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
await amplify.Auth._handleAuthResponse(url);
|
||||||
} finally {
|
} finally {
|
||||||
/** Restore the original `window.location.replaceState` function. */
|
/** Restore the original `window.location.replaceState` function. */
|
||||||
window.history.replaceState = replaceState;
|
window.history.replaceState = replaceState;
|
||||||
|
@ -11,13 +11,11 @@
|
|||||||
/** Path data for the SVG icons used in app. */
|
/** Path data for the SVG icons used in app. */
|
||||||
export const PATHS = {
|
export const PATHS = {
|
||||||
/** Path data for the `@` icon SVG. */
|
/** Path data for the `@` icon SVG. */
|
||||||
at:
|
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 \
|
||||||
"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`,
|
||||||
"8.959 0 01-4.5 1.207",
|
|
||||||
/** Path data for the lock icon SVG. */
|
/** Path data for the lock icon SVG. */
|
||||||
lock:
|
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 \
|
||||||
"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`,
|
||||||
"0 00-8 0v4h8z",
|
|
||||||
/** Path data for the "right arrow" icon SVG. */
|
/** 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",
|
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. */
|
/** Path data for the "create account" icon SVG. */
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* included in the final bundle. */
|
* included in the final bundle. */
|
||||||
// It is safe to disable `no-restricted-syntax` because the `PascalCase` naming is required
|
// It is safe to disable `no-restricted-syntax` because the `PascalCase` naming is required
|
||||||
// as per the above comment.
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-restricted-syntax
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as reactDOM from "react-dom/client";
|
import * as reactDOM from "react-dom/client";
|
||||||
|
@ -21,6 +21,8 @@ export interface Logger {
|
|||||||
// === LoggerContext ===
|
// === LoggerContext ===
|
||||||
// =====================
|
// =====================
|
||||||
|
|
||||||
|
/** See {@link AuthContext} for safety details. */
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
const LoggerContext = react.createContext<Logger>({} as Logger);
|
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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// type to keep that guarantee by preventing accidental misuse of the value.
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
@ -146,67 +146,67 @@ async function genIcons(outputDir) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
console.log(`Generating icons to ${outputDir}`)
|
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.')
|
/// Please note that this function converts the SVG to PNG
|
||||||
await fs.mkdir(path.resolve(outputDir, 'svg'), { recursive: true })
|
/// AND KEEPS THE METADATA INFORMATION ABOUT DPI OF 144.
|
||||||
await fs.mkdir(path.resolve(outputDir, 'png'), { recursive: true })
|
/// It is required to properly display png images on MacOS.
|
||||||
for (const size of sizes) {
|
/// There is currently no other way in `sharp` to do it.
|
||||||
let name = `icon_${size}x${size}.svg`
|
console.log('Generating PNG icons.')
|
||||||
let logo = new Logo(size, true).generate()
|
for (const size of sizes) {
|
||||||
await fs.writeFile(`${outputDir}/svg/${name}`, logo)
|
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
|
for (const size of sizes.slice(1)) {
|
||||||
/// AND KEEPS THE METADATA INFORMATION ABOUT DPI OF 144.
|
let size2 = size / 2
|
||||||
/// It is required to properly display png images on MacOS.
|
let inName = `icon_${size}x${size}.svg`
|
||||||
/// There is currently no other way in `sharp` to do it.
|
let outName = `icon_${size2}x${size2}@2x.png`
|
||||||
console.log('Generating PNG icons.')
|
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
||||||
for (const size of sizes) {
|
.png()
|
||||||
let inName = `icon_${size}x${size}.svg`
|
.resize({
|
||||||
let outName = `icon_${size}x${size}.png`
|
width: size,
|
||||||
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
kernel: sharp.kernel.mitchell,
|
||||||
.png()
|
})
|
||||||
.resize({
|
.toFile(`${outputDir}/png/${outName}`)
|
||||||
width: size,
|
}
|
||||||
kernel: sharp.kernel.mitchell,
|
|
||||||
})
|
|
||||||
.toFile(`${outputDir}/png/${outName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const size of sizes.slice(1)) {
|
if (os.platform() === 'darwin') {
|
||||||
let size2 = size / 2
|
console.log('Generating ICNS.')
|
||||||
let inName = `icon_${size}x${size}.svg`
|
childProcess.execSync(`cp -R ${outputDir}/png ${outputDir}/png.iconset`)
|
||||||
let outName = `icon_${size2}x${size2}@2x.png`
|
childProcess.execSync(
|
||||||
await sharp(`${outputDir}/svg/${inName}`, { density: MACOS_DPI })
|
`iconutil --convert icns --output ${outputDir}/icon.icns ${outputDir}/png.iconset`
|
||||||
.png()
|
)
|
||||||
.resize({
|
}
|
||||||
width: size,
|
|
||||||
kernel: sharp.kernel.mitchell,
|
|
||||||
})
|
|
||||||
.toFile(`${outputDir}/png/${outName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (os.platform() === 'darwin') {
|
console.log('Generating ICO.')
|
||||||
console.log('Generating ICNS.')
|
let files = []
|
||||||
childProcess.execSync(`cp -R ${outputDir}/png ${outputDir}/png.iconset`)
|
for (const size of winSizes) {
|
||||||
childProcess.execSync(
|
let inName = `icon_${size}x${size}.png`
|
||||||
`iconutil --convert icns --output ${outputDir}/icon.icns ${outputDir}/png.iconset`
|
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 handle = await fs.open(donePath, 'w')
|
||||||
let files = []
|
await handle.close()
|
||||||
for (const size of winSizes) {
|
return
|
||||||
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)
|
|
||||||
|
|
||||||
let handle = await fs.open(donePath, 'w')
|
|
||||||
await handle.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Main entry function. */
|
/** Main entry function. */
|
||||||
|
@ -22,7 +22,7 @@ export const LIVE_RELOAD_LISTENER_PATH = path.join(DIR_NAME, 'live-reload.js')
|
|||||||
|
|
||||||
/** Start the server.
|
/** 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 }) {
|
export async function start({ root, assets, port }) {
|
||||||
assets = assets ?? path.join(root, 'assets')
|
assets = assets ?? path.join(root, 'assets')
|
||||||
|
@ -4,8 +4,9 @@ declare module 'enso-gui-server' {
|
|||||||
export const LIVE_RELOAD_LISTENER_PATH: string
|
export const LIVE_RELOAD_LISTENER_PATH: string
|
||||||
|
|
||||||
interface StartParams {
|
interface StartParams {
|
||||||
root?: string
|
// These are not values we explicitly supply
|
||||||
assets?: string
|
root: string
|
||||||
|
assets?: string | null
|
||||||
port?: number
|
port?: number
|
||||||
}
|
}
|
||||||
interface ExectionInfo {
|
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 (
|
export default function (
|
||||||
option: CreateServersOptions,
|
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
|
errorHandler: (err: HttpError | undefined) => void
|
||||||
): unknown
|
): unknown
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,18 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"noImplicitOverride": true,
|
"strict": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictPropertyInitialization": true,
|
|
||||||
"target": "ES2019",
|
"target": "ES2019",
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
|
@ -39,6 +39,9 @@ export function requireEnvResolvedPath(name: string) {
|
|||||||
*/
|
*/
|
||||||
export function requireEnvPathExist(name: string) {
|
export function requireEnvPathExist(name: string) {
|
||||||
const value = requireEnv(name)
|
const value = requireEnv(name)
|
||||||
if (fs.existsSync(value)) return value
|
if (fs.existsSync(value)) {
|
||||||
else throw Error(`File with path ${value} read from environment variable ${name} is missing.`)
|
return value
|
||||||
|
} else {
|
||||||
|
throw Error(`File with path ${value} read from environment variable ${name} is missing.`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user