mirror of
https://github.com/enso-org/enso.git
synced 2024-10-05 17:17:50 +03:00
Remove ensogl-pack
(#9407)
This PR removes enso-pack (ensogl-pack) crate. It still keeps the `enso-runner` JS package, as it is used for CLI argument parser and logger. The runner should be probably refactored (and possible removed altogether). # Important Notes I've temporarily extracted the `enso-runner` to `lib/js` directory, as I wanted to avoid keeping pure JS library under `lib/rust`. Attempts at integrating this with `app/ide-desktop` and family caused too much trouble for this PR. The expectation is that the package will be removed or moved elsewhere soon anyway.
This commit is contained in:
parent
703cafa6d9
commit
de9f2764f9
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -1199,7 +1199,6 @@ dependencies = [
|
|||||||
"enso-build-macros-lib",
|
"enso-build-macros-lib",
|
||||||
"enso-enso-font",
|
"enso-enso-font",
|
||||||
"enso-font",
|
"enso-font",
|
||||||
"enso-pack",
|
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"glob",
|
"glob",
|
||||||
@ -1414,20 +1413,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "enso-pack"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"enso-prelude",
|
|
||||||
"futures",
|
|
||||||
"ide-ci",
|
|
||||||
"manifest-dir-macros",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enso-parser"
|
name = "enso-parser"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2513,18 +2498,6 @@ dependencies = [
|
|||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "manifest-dir-macros"
|
|
||||||
version = "0.1.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f08150cf2bab1fc47c2196f4f41173a27fcd0f684165e5458c0046b53a472e2f"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.107",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -21,7 +21,6 @@ members = [
|
|||||||
"lib/rust/parser/generate-java",
|
"lib/rust/parser/generate-java",
|
||||||
"lib/rust/parser/schema",
|
"lib/rust/parser/schema",
|
||||||
"lib/rust/parser/debug",
|
"lib/rust/parser/debug",
|
||||||
"lib/rust/enso-pack",
|
|
||||||
"tools/language-server/logstat",
|
"tools/language-server/logstat",
|
||||||
"tools/language-server/wstest",
|
"tools/language-server/wstest",
|
||||||
]
|
]
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
"fast-glob": "^3.2.12",
|
"fast-glob": "^3.2.12",
|
||||||
"portfinder": "^1.0.32",
|
"portfinder": "^1.0.32",
|
||||||
|
"sharp": "^0.31.2",
|
||||||
|
"to-ico": "^1.1.5",
|
||||||
"tsx": "^4.7.1"
|
"tsx": "^4.7.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
@ -53,7 +55,6 @@
|
|||||||
"typecheck": "npm run --workspace=enso-gui2 compile-server && tsc --build",
|
"typecheck": "npm run --workspace=enso-gui2 compile-server && tsc --build",
|
||||||
"start": "tsx start.ts",
|
"start": "tsx start.ts",
|
||||||
"build": "tsx bundle.ts",
|
"build": "tsx bundle.ts",
|
||||||
"dist": "tsx dist.ts",
|
"dist": "tsx dist.ts"
|
||||||
"watch": "tsx watch.ts"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
/** @file This script is for watching the whole IDE and spawning the electron process.
|
|
||||||
*
|
|
||||||
* It sets up watchers for the client and content, and spawns the electron process with the IDE.
|
|
||||||
* The spawned electron process can then use its refresh capability to pull the latest changes
|
|
||||||
* from the watchers.
|
|
||||||
*
|
|
||||||
* If the electron app is closed, the script will restart it, allowing to test the IDE setup.
|
|
||||||
* To stop, use Ctrl+C. */
|
|
||||||
|
|
||||||
import * as childProcess from 'node:child_process'
|
|
||||||
import * as fs from 'node:fs/promises'
|
|
||||||
import * as path from 'node:path'
|
|
||||||
import process from 'node:process'
|
|
||||||
|
|
||||||
import * as esbuild from 'esbuild'
|
|
||||||
|
|
||||||
import * as clientBundler from './esbuild-config'
|
|
||||||
import * as contentBundler from '../content/esbuild-config'
|
|
||||||
import * as dashboardBundler from '../dashboard/esbuild-config'
|
|
||||||
import * as paths from './paths'
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Types ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/** Set of esbuild watches for the client and content. */
|
|
||||||
interface Watches {
|
|
||||||
readonly client: esbuild.BuildResult
|
|
||||||
readonly dashboard: esbuild.BuildResult
|
|
||||||
readonly content: esbuild.BuildResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
const IDE_DIR_PATH = paths.getIdeDirectory()
|
|
||||||
const PROJECT_MANAGER_BUNDLE_PATH = paths.getProjectManagerBundlePath()
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Watch ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
console.log('Cleaning IDE dist directory.')
|
|
||||||
await fs.rm(IDE_DIR_PATH, { recursive: true, force: true })
|
|
||||||
await fs.mkdir(IDE_DIR_PATH, { recursive: true })
|
|
||||||
|
|
||||||
const ALL_BUNDLES_READY = new Promise<Watches>((resolve, reject) => {
|
|
||||||
void (async () => {
|
|
||||||
console.log('Bundling client.')
|
|
||||||
const clientBundlerOpts = clientBundler.bundlerOptionsFromEnv()
|
|
||||||
clientBundlerOpts.outdir = path.resolve(IDE_DIR_PATH)
|
|
||||||
// Eslint is wrong here; `clientBundlerOpts.plugins` is actually `undefined`.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
;(clientBundlerOpts.plugins ??= []).push({
|
|
||||||
name: 'enso-on-rebuild',
|
|
||||||
setup: build => {
|
|
||||||
build.onEnd(result => {
|
|
||||||
if (result.errors.length) {
|
|
||||||
// We cannot carry on if the client failed to build, because electron
|
|
||||||
// would immediately exit with an error.
|
|
||||||
console.error('Client watch bundle failed:', result.errors[0])
|
|
||||||
reject(result.errors[0])
|
|
||||||
} else {
|
|
||||||
console.log('Client bundle updated.')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const clientBuilder = await esbuild.context(clientBundlerOpts)
|
|
||||||
const client = await clientBuilder.rebuild()
|
|
||||||
console.log('Result of client bundling: ', client)
|
|
||||||
void clientBuilder.watch()
|
|
||||||
|
|
||||||
console.log('Bundling dashboard.')
|
|
||||||
const dashboardOpts = dashboardBundler.bundleOptions()
|
|
||||||
dashboardOpts.plugins.push({
|
|
||||||
name: 'enso-on-rebuild',
|
|
||||||
setup: build => {
|
|
||||||
build.onEnd(() => {
|
|
||||||
console.log('Dashboard bundle updated.')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
dashboardOpts.outdir = path.resolve(IDE_DIR_PATH, 'assets')
|
|
||||||
const dashboardBuilder = await esbuild.context(dashboardOpts)
|
|
||||||
const dashboard = await dashboardBuilder.rebuild()
|
|
||||||
console.log('Result of dashboard bundling: ', dashboard)
|
|
||||||
// We do not need to serve the dashboard as it outputs to the same directory.
|
|
||||||
// It will not rebuild on request, but it is not intended to rebuild on request anyway.
|
|
||||||
// This MUST be called before `builder.watch()` as `tailwind.css` must be generated
|
|
||||||
// before the copy plugin runs.
|
|
||||||
void dashboardBuilder.watch()
|
|
||||||
|
|
||||||
console.log('Bundling content.')
|
|
||||||
const contentOpts = contentBundler.bundlerOptionsFromEnv({
|
|
||||||
supportsLocalBackend: true,
|
|
||||||
supportsDeepLinks: false,
|
|
||||||
})
|
|
||||||
contentOpts.plugins.push({
|
|
||||||
name: 'enso-on-rebuild',
|
|
||||||
setup: build => {
|
|
||||||
build.onEnd(() => {
|
|
||||||
console.log('Content bundle updated.')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
contentOpts.pure.splice(contentOpts.pure.indexOf('assert'), 1)
|
|
||||||
contentOpts.outdir = path.resolve(IDE_DIR_PATH, 'assets')
|
|
||||||
contentOpts.define.REDIRECT_OVERRIDE = JSON.stringify('http://localhost:8080')
|
|
||||||
const contentBuilder = await esbuild.context(contentOpts)
|
|
||||||
const content = await contentBuilder.rebuild()
|
|
||||||
console.log('Result of content bundling: ', content)
|
|
||||||
void contentBuilder.watch()
|
|
||||||
|
|
||||||
resolve({ client, dashboard, content })
|
|
||||||
})()
|
|
||||||
})
|
|
||||||
|
|
||||||
await ALL_BUNDLES_READY
|
|
||||||
console.log('Exposing Project Manager bundle.')
|
|
||||||
await fs.symlink(
|
|
||||||
PROJECT_MANAGER_BUNDLE_PATH,
|
|
||||||
path.join(IDE_DIR_PATH, paths.PROJECT_MANAGER_BUNDLE),
|
|
||||||
'dir'
|
|
||||||
)
|
|
||||||
|
|
||||||
const ELECTRON_ARGS = [path.join(IDE_DIR_PATH, 'index.cjs'), '--', ...process.argv.slice(2)]
|
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
|
||||||
console.log('SIGINT received. Exiting.')
|
|
||||||
// The `esbuild` process seems to remain alive at this point and will keep our process
|
|
||||||
// from ending. Thus, we exit manually. It seems to terminate the child `esbuild` process
|
|
||||||
// as well.
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
console.log('Spawning Electron process.')
|
|
||||||
const electronProcess = childProcess.spawn('electron', ELECTRON_ARGS, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
shell: true,
|
|
||||||
})
|
|
||||||
console.log('Waiting for Electron process to finish.')
|
|
||||||
const result = await new Promise((resolve, reject) => {
|
|
||||||
electronProcess.on('close', resolve)
|
|
||||||
electronProcess.on('error', reject)
|
|
||||||
})
|
|
||||||
console.log('Electron process finished. Exit code: ', result)
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
/** @file Entry point for the bundler. */
|
|
||||||
import * as esbuild from 'esbuild'
|
|
||||||
|
|
||||||
import * as bundler from './esbuild-config'
|
|
||||||
|
|
||||||
// =======================
|
|
||||||
// === Generate bundle ===
|
|
||||||
// =======================
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = bundler.bundleOptions({ supportsLocalBackend: true, supportsDeepLinks: true })
|
|
||||||
void esbuild.build(options)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
throw error
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
/** @file Configuration for the esbuild bundler and build/watch commands.
|
|
||||||
*
|
|
||||||
* The bundler processes each entry point into a single file, each with no external dependencies and
|
|
||||||
* minified. This primarily involves resolving all imports, along with some other transformations
|
|
||||||
* (like TypeScript compilation).
|
|
||||||
*
|
|
||||||
* See the bundlers documentation for more information:
|
|
||||||
* https://esbuild.github.io/getting-started/#bundling-for-node. */
|
|
||||||
|
|
||||||
import * as childProcess from 'node:child_process'
|
|
||||||
import * as fs from 'node:fs/promises'
|
|
||||||
import * as fsSync from 'node:fs'
|
|
||||||
import * as pathModule from 'node:path'
|
|
||||||
import * as url from 'node:url'
|
|
||||||
|
|
||||||
import type * as esbuild from 'esbuild'
|
|
||||||
import * as esbuildPluginNodeGlobals from '@esbuild-plugins/node-globals-polyfill'
|
|
||||||
import * as esbuildPluginNodeModules from '@esbuild-plugins/node-modules-polyfill'
|
|
||||||
import esbuildPluginCopyDirectories from 'esbuild-plugin-copy-directories'
|
|
||||||
import esbuildPluginTime from 'esbuild-plugin-time'
|
|
||||||
import esbuildPluginYaml from 'esbuild-plugin-yaml'
|
|
||||||
|
|
||||||
import * as appConfig from 'enso-common/src/appConfig'
|
|
||||||
import * as buildUtils from 'enso-common/src/buildUtils'
|
|
||||||
import BUILD_INFO from '../../../../build.json' assert { type: 'json' }
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
const THIS_PATH = pathModule.resolve(pathModule.dirname(url.fileURLToPath(import.meta.url)))
|
|
||||||
|
|
||||||
// ====================
|
|
||||||
// === Global setup ===
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
await appConfig.readEnvironmentFromFile()
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// === Environment variables ===
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
/** Arguments that must always be supplied, because they are not defined as
|
|
||||||
* environment variables. */
|
|
||||||
export interface PassthroughArguments {
|
|
||||||
/** Whether the application may have the local backend running. */
|
|
||||||
readonly supportsLocalBackend: boolean
|
|
||||||
/** Whether the application supports deep links. This is only true when using
|
|
||||||
* the installed app on macOS and Windows. */
|
|
||||||
readonly supportsDeepLinks: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Mandatory build options. */
|
|
||||||
export interface Arguments extends PassthroughArguments {
|
|
||||||
/** List of files to be copied from WASM artifacts. */
|
|
||||||
readonly wasmArtifacts: string
|
|
||||||
/** Directory with assets. Its contents are to be copied. */
|
|
||||||
readonly assetsPath: string
|
|
||||||
/** Path where bundled files are output. */
|
|
||||||
readonly outputPath: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get arguments from the environment. */
|
|
||||||
export function argumentsFromEnv(passthroughArguments: PassthroughArguments): Arguments {
|
|
||||||
const wasmArtifacts = buildUtils.requireEnv('ENSO_BUILD_GUI_WASM_ARTIFACTS')
|
|
||||||
const assetsPath = buildUtils.requireEnv('ENSO_BUILD_GUI_ASSETS')
|
|
||||||
const outputPath = pathModule.resolve(buildUtils.requireEnv('ENSO_BUILD_GUI'), 'assets')
|
|
||||||
return { ...passthroughArguments, wasmArtifacts, assetsPath, outputPath }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================
|
|
||||||
// === Git process ===
|
|
||||||
// ===================
|
|
||||||
|
|
||||||
/** Get output of a git command.
|
|
||||||
* @param command - Command line following the `git` program.
|
|
||||||
* @returns Output of the command. */
|
|
||||||
function git(command: string): string {
|
|
||||||
// TODO [mwu] Eventually this should be removed, data should be provided by the build script
|
|
||||||
// through `BUILD_INFO`. The bundler configuration should not invoke git,
|
|
||||||
// it is not its responsibility.
|
|
||||||
return childProcess.execSync(`git ${command}`, { encoding: 'utf8' }).trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================
|
|
||||||
// === Bundling ===
|
|
||||||
// ================
|
|
||||||
|
|
||||||
/** Generate the builder options. */
|
|
||||||
export function bundlerOptions(args: Arguments) {
|
|
||||||
const { outputPath, wasmArtifacts, assetsPath, supportsLocalBackend, supportsDeepLinks } = args
|
|
||||||
const buildOptions = {
|
|
||||||
// The names come from a third-party API and cannot be changed.
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
absWorkingDir: THIS_PATH,
|
|
||||||
bundle: true,
|
|
||||||
loader: {
|
|
||||||
'.html': 'copy',
|
|
||||||
'.css': 'copy',
|
|
||||||
'.map': 'copy',
|
|
||||||
'.wasm': 'copy',
|
|
||||||
'.svg': 'dataurl',
|
|
||||||
'.png': 'file',
|
|
||||||
'.jpg': 'file',
|
|
||||||
'.ttf': 'copy',
|
|
||||||
},
|
|
||||||
entryPoints: [
|
|
||||||
pathModule.resolve(THIS_PATH, 'src', 'entrypoint.ts'),
|
|
||||||
pathModule.resolve(THIS_PATH, 'src', 'index.html'),
|
|
||||||
pathModule.resolve(THIS_PATH, 'src', 'run.js'),
|
|
||||||
pathModule.resolve(THIS_PATH, 'src', 'style.css'),
|
|
||||||
pathModule.resolve(THIS_PATH, 'src', 'serviceWorker.ts'),
|
|
||||||
...wasmArtifacts.split(pathModule.delimiter),
|
|
||||||
...fsSync
|
|
||||||
.readdirSync(assetsPath)
|
|
||||||
.map(fileName => pathModule.resolve(assetsPath, fileName)),
|
|
||||||
].map(path => ({ in: path, out: pathModule.basename(path, pathModule.extname(path)) })),
|
|
||||||
outdir: outputPath,
|
|
||||||
outbase: 'src',
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
name: 'override-loaders',
|
|
||||||
setup: build => {
|
|
||||||
// This file MUST be in CommonJS format because it is loaded using `Function()`
|
|
||||||
// in `ensogl/pack/js/src/runner/index.ts`.
|
|
||||||
// All other files are ESM because of `"type": "module"` in `package.json`.
|
|
||||||
build.onLoad({ filter: /[/\\]pkg\.js$/ }, async info => {
|
|
||||||
const { path } = info
|
|
||||||
return {
|
|
||||||
contents: await fs.readFile(path),
|
|
||||||
loader: 'copy',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// `.png` and `.svg` files not in the `assets` module should not use the `file`
|
|
||||||
// loader.
|
|
||||||
build.onLoad({ filter: /(?:\.png|\.svg)$/ }, async info => {
|
|
||||||
const { path } = info
|
|
||||||
if (!/[/\\]assets[/\\][^/\\]*(?:\.png|\.svg)$/.test(path)) {
|
|
||||||
return {
|
|
||||||
contents: await fs.readFile(path),
|
|
||||||
loader: 'copy',
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
esbuildPluginCopyDirectories(),
|
|
||||||
esbuildPluginYaml.yamlPlugin({}),
|
|
||||||
esbuildPluginNodeModules.NodeModulesPolyfillPlugin(),
|
|
||||||
esbuildPluginNodeGlobals.NodeGlobalsPolyfillPlugin({ buffer: true, process: true }),
|
|
||||||
esbuildPluginTime(),
|
|
||||||
],
|
|
||||||
define: {
|
|
||||||
GIT_HASH: JSON.stringify(git('rev-parse HEAD')),
|
|
||||||
GIT_STATUS: JSON.stringify(git('status --short --porcelain')),
|
|
||||||
BUILD_INFO: JSON.stringify(BUILD_INFO),
|
|
||||||
SUPPORTS_LOCAL_BACKEND: JSON.stringify(supportsLocalBackend),
|
|
||||||
SUPPORTS_DEEP_LINKS: JSON.stringify(supportsDeepLinks),
|
|
||||||
...appConfig.getDefines(),
|
|
||||||
},
|
|
||||||
pure: ['assert'],
|
|
||||||
sourcemap: true,
|
|
||||||
metafile: true,
|
|
||||||
format: 'esm',
|
|
||||||
platform: 'browser',
|
|
||||||
color: true,
|
|
||||||
logOverride: {
|
|
||||||
// Happens in Emscripten-generated MSDF (msdfgen_wasm.js):
|
|
||||||
// 1 │ ...typeof module!=="undefined"){module["exports"]=Module}process["o...
|
|
||||||
'commonjs-variable-in-esm': 'silent',
|
|
||||||
// Happens in Emscripten-generated MSDF (msdfgen_wasm.js):
|
|
||||||
// 1 │ ...y{table.grow(1)}catch(err){if(!err instanceof RangeError){throw ...
|
|
||||||
'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(passthroughArguments: PassthroughArguments) {
|
|
||||||
return bundlerOptions(argumentsFromEnv(passthroughArguments))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** esbuild options for bundling the package for a one-off build.
|
|
||||||
*
|
|
||||||
* Relies on the environment variables to be set. */
|
|
||||||
export function bundleOptions(passthroughArguments: PassthroughArguments) {
|
|
||||||
return bundlerOptionsFromEnv(passthroughArguments)
|
|
||||||
}
|
|
19
app/ide-desktop/lib/content/globals.d.ts
vendored
19
app/ide-desktop/lib/content/globals.d.ts
vendored
@ -1,19 +0,0 @@
|
|||||||
/** @file Globals defined only in this module. */
|
|
||||||
|
|
||||||
// =====================================
|
|
||||||
// === Global namespace augmentation ===
|
|
||||||
// =====================================
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// These are top-level constants, and therefore should be `CONSTANT_CASE`.
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
/** Whether the */
|
|
||||||
/** Whether the application may have the local backend running. */
|
|
||||||
const SUPPORTS_LOCAL_BACKEND: boolean
|
|
||||||
/** Whether the application supports deep links. This is only true when using
|
|
||||||
* the installed app on macOS and Windows. */
|
|
||||||
const SUPPORTS_DEEP_LINKS: boolean
|
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
|
||||||
}
|
|
||||||
|
|
||||||
export {}
|
|
@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "enso-content",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"author": {
|
|
||||||
"name": "Enso Team",
|
|
||||||
"email": "contact@enso.org"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/enso-org/ide",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git@github.com:enso-org/ide.git"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/enso-org/ide/issues"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"typecheck": "tsc --build",
|
|
||||||
"build": "tsx bundle.ts",
|
|
||||||
"watch": "tsx watch.ts",
|
|
||||||
"start": "tsx start.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/semver": "^7.3.9",
|
|
||||||
"enso-content-config": "^1.0.0",
|
|
||||||
"react-toastify": "^9.1.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
|
||||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
|
||||||
"@eslint/js": "^8.49.0",
|
|
||||||
"@types/connect": "^3.4.35",
|
|
||||||
"@types/morgan": "^1.9.4",
|
|
||||||
"@types/serve-static": "^1.15.1",
|
|
||||||
"@types/sharp": "^0.31.1",
|
|
||||||
"@types/to-ico": "^1.1.1",
|
|
||||||
"@types/ws": "^8.5.4",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
|
||||||
"@typescript-eslint/parser": "^6.7.2",
|
|
||||||
"enso-dashboard": "^0.1.0",
|
|
||||||
"esbuild": "^0.19.3",
|
|
||||||
"esbuild-plugin-copy-directories": "^1.0.0",
|
|
||||||
"esbuild-plugin-time": "^1.0.0",
|
|
||||||
"esbuild-plugin-yaml": "^0.0.1",
|
|
||||||
"eslint": "^8.49.0",
|
|
||||||
"eslint-plugin-jsdoc": "^46.8.1",
|
|
||||||
"globals": "^13.20.0",
|
|
||||||
"portfinder": "^1.0.32",
|
|
||||||
"tsx": "^4.7.1",
|
|
||||||
"typescript": "~5.2.2"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@esbuild/darwin-x64": "^0.17.15",
|
|
||||||
"@esbuild/linux-x64": "^0.17.15",
|
|
||||||
"@esbuild/windows-x64": "^0.17.15"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
/** @file A service worker that redirects paths without extensions to `/index.html`.
|
|
||||||
* This is required for paths like `/login`, which are handled by client-side routing,
|
|
||||||
* to work when developing locally on `localhost:8080`. */
|
|
||||||
// Bring globals and interfaces specific to Web Workers into scope.
|
|
||||||
/// <reference lib="WebWorker" />
|
|
||||||
import * as common from 'enso-common'
|
|
||||||
|
|
||||||
import * as constants from './serviceWorkerConstants'
|
|
||||||
|
|
||||||
// =====================
|
|
||||||
// === Fetch handler ===
|
|
||||||
// =====================
|
|
||||||
|
|
||||||
// We `declare` a variable here because Service Workers have a different global scope.
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
declare const self: ServiceWorkerGlobalScope
|
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.open(constants.CACHE_NAME).then(cache => {
|
|
||||||
void cache.addAll(constants.DEPENDENCIES)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addEventListener('fetch', event => {
|
|
||||||
const url = new URL(event.request.url)
|
|
||||||
if (
|
|
||||||
(url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&
|
|
||||||
url.pathname === '/esbuild'
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
} else if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
||||||
const responsePromise = caches
|
|
||||||
.open(constants.CACHE_NAME)
|
|
||||||
.then(cache => cache.match(event.request))
|
|
||||||
.then(
|
|
||||||
response =>
|
|
||||||
response ??
|
|
||||||
(/\/[^.]+$/.test(url.pathname)
|
|
||||||
? fetch('/index.html')
|
|
||||||
: fetch(event.request.url))
|
|
||||||
)
|
|
||||||
event.respondWith(
|
|
||||||
responsePromise.then(response => {
|
|
||||||
const clonedResponse = new Response(response.body, response)
|
|
||||||
for (const [header, value] of common.COOP_COEP_CORP_HEADERS) {
|
|
||||||
clonedResponse.headers.set(header, value)
|
|
||||||
}
|
|
||||||
return clonedResponse
|
|
||||||
})
|
|
||||||
)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
event.respondWith(
|
|
||||||
caches
|
|
||||||
.open(constants.CACHE_NAME)
|
|
||||||
.then(cache => cache.match(event.request))
|
|
||||||
.then(response => response ?? fetch(event.request))
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,348 +0,0 @@
|
|||||||
/** @file This module is responsible for loading the WASM binary, its dependencies, and providing
|
|
||||||
* the user with a visual representation of this process (welcome screen). It also implements a view
|
|
||||||
* allowing to choose a debug rendering test from. */
|
|
||||||
|
|
||||||
import * as semver from 'semver'
|
|
||||||
import * as toastify from 'react-toastify'
|
|
||||||
|
|
||||||
import * as app from 'enso-runner/src/runner'
|
|
||||||
import * as common from 'enso-common'
|
|
||||||
import * as contentConfig from 'enso-content-config'
|
|
||||||
import * as dashboard from 'enso-dashboard'
|
|
||||||
import * as detect from 'enso-common/src/detect'
|
|
||||||
import * as gtag from 'enso-common/src/gtag'
|
|
||||||
|
|
||||||
import * as remoteLog from './remoteLog'
|
|
||||||
import GLOBAL_CONFIG from '../../../../gui2/config.yaml' assert { type: 'yaml' }
|
|
||||||
|
|
||||||
const logger = app.log.logger
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/** The name of the `localStorage` key storing the initial URL of the app. */
|
|
||||||
const INITIAL_URL_KEY = `${common.PRODUCT_NAME.toLowerCase()}-initial-url`
|
|
||||||
/** Path to the SSE endpoint over which esbuild sends events. */
|
|
||||||
const ESBUILD_PATH = './esbuild'
|
|
||||||
/** SSE event indicating a build has finished. */
|
|
||||||
const ESBUILD_EVENT_NAME = 'change'
|
|
||||||
/** Path to the serice worker that caches assets for offline usage.
|
|
||||||
* In development, it also resolves all extensionless paths to `/index.html`.
|
|
||||||
* This is required for client-side routing to work when doing `./run gui watch`.
|
|
||||||
*/
|
|
||||||
const SERVICE_WORKER_PATH = './serviceWorker.js'
|
|
||||||
/** One second in milliseconds. */
|
|
||||||
const SECOND = 1000
|
|
||||||
/** Time in seconds after which a `fetchTimeout` ends. */
|
|
||||||
const FETCH_TIMEOUT = 300
|
|
||||||
|
|
||||||
// ===================
|
|
||||||
// === Live reload ===
|
|
||||||
// ===================
|
|
||||||
|
|
||||||
if (detect.IS_DEV_MODE && !detect.isOnElectron()) {
|
|
||||||
new EventSource(ESBUILD_PATH).addEventListener(ESBUILD_EVENT_NAME, () => {
|
|
||||||
// This acts like `location.reload`, but it preserves the query-string.
|
|
||||||
// The `toString()` is to bypass a lint without using a comment.
|
|
||||||
location.href = location.href.toString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
void (async () => {
|
|
||||||
// `navigator.serviceWorker` may be disabled in certain situations, for example in Private mode
|
|
||||||
// on Safari.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
const registration = await navigator.serviceWorker?.getRegistration()
|
|
||||||
await registration?.unregister()
|
|
||||||
await navigator.serviceWorker.register(SERVICE_WORKER_PATH)
|
|
||||||
})()
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Fetch ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/** Returns an `AbortController` that aborts after the specified number of seconds. */
|
|
||||||
function timeout(timeSeconds: number) {
|
|
||||||
const controller = new AbortController()
|
|
||||||
setTimeout(() => {
|
|
||||||
controller.abort()
|
|
||||||
}, timeSeconds * SECOND)
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A version of `fetch` which times out after the provided time. */
|
|
||||||
async function fetchTimeout(url: string, timeoutSeconds: number): Promise<unknown> {
|
|
||||||
return fetch(url, { signal: timeout(timeoutSeconds).signal }).then(response => {
|
|
||||||
const statusCodeOK = 200
|
|
||||||
if (response.status === statusCodeOK) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
throw new Error(`Failed to fetch '${url}'. Response status: ${response.status}.`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Return `true` if the current application version is still supported and `false` otherwise.
|
|
||||||
*
|
|
||||||
* Function downloads the application config containing the minimum supported version from GitHub
|
|
||||||
* and compares it with the version of the `client` js package. When the function is unable to
|
|
||||||
* download the application config, or one of the compared versions does not match the semver
|
|
||||||
* scheme, it returns `true`. */
|
|
||||||
async function checkMinSupportedVersion(config: typeof contentConfig.OPTIONS) {
|
|
||||||
let supported = false
|
|
||||||
if (config.groups.engine.options.skipMinVersionCheck.value) {
|
|
||||||
supported = true
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const appConfig = await fetchTimeout(
|
|
||||||
config.groups.engine.options.configUrl.value,
|
|
||||||
FETCH_TIMEOUT
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
typeof appConfig === 'object' &&
|
|
||||||
appConfig != null &&
|
|
||||||
'minimumSupportedVersion' in appConfig
|
|
||||||
) {
|
|
||||||
const minSupportedVersion = appConfig.minimumSupportedVersion
|
|
||||||
if (typeof minSupportedVersion !== 'string') {
|
|
||||||
logger.error('The minimum supported version is not a string.')
|
|
||||||
} else {
|
|
||||||
const comparator = new semver.Comparator(`>=${minSupportedVersion}`)
|
|
||||||
supported = comparator.test(contentConfig.VERSION.ide)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error('The application config is not an object.')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Minimum version check failed.', e)
|
|
||||||
supported = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return supported
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Display information that the current app version is deprecated. */
|
|
||||||
function displayDeprecatedVersionDialog() {
|
|
||||||
const versionCheckText = document.createTextNode(
|
|
||||||
'This version is no longer supported. Please download a new one.'
|
|
||||||
)
|
|
||||||
|
|
||||||
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 == null) {
|
|
||||||
console.error('Cannot find the root DOM element.')
|
|
||||||
} else {
|
|
||||||
root.appendChild(versionCheckDiv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================
|
|
||||||
// === Main entry point ===
|
|
||||||
// ========================
|
|
||||||
|
|
||||||
/** Nested configuration options with `string` values. */
|
|
||||||
export interface StringConfig {
|
|
||||||
[key: string]: StringConfig | string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Configuration options for the authentication flow and dashboard. */
|
|
||||||
interface AuthenticationConfig {
|
|
||||||
readonly supportsVibrancy: boolean
|
|
||||||
readonly projectManagerUrl: string | null
|
|
||||||
readonly isInAuthenticationFlow: boolean
|
|
||||||
readonly shouldUseAuthentication: boolean
|
|
||||||
readonly initialProjectName: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Contains the entrypoint into the IDE. */
|
|
||||||
class Main implements AppRunner {
|
|
||||||
app: app.App | null = null
|
|
||||||
toast = toastify.toast
|
|
||||||
|
|
||||||
/** Stop an app instance, if one is running. */
|
|
||||||
stopApp() {
|
|
||||||
this.app?.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Run an app instance with the specified configuration.
|
|
||||||
* This includes the scene to run and the WebSocket endpoints to the backend. */
|
|
||||||
async runApp(
|
|
||||||
inputConfig: StringConfig | null,
|
|
||||||
accessToken: string | null,
|
|
||||||
loggingMetadata?: object
|
|
||||||
) {
|
|
||||||
this.stopApp()
|
|
||||||
|
|
||||||
/** FIXME: https://github.com/enso-org/enso/issues/6475
|
|
||||||
* Default values names are out of sync with values used in code.
|
|
||||||
* Rather than setting fixed values here we need to fix default values in config. */
|
|
||||||
const config = Object.assign(
|
|
||||||
{
|
|
||||||
loader: {
|
|
||||||
wasmUrl: 'pkg-opt.wasm',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
const configOptions = contentConfig.OPTIONS.clone()
|
|
||||||
|
|
||||||
const newApp = new app.App({
|
|
||||||
config,
|
|
||||||
configOptions,
|
|
||||||
packageInfo: {
|
|
||||||
version: BUILD_INFO.version,
|
|
||||||
engineVersion: BUILD_INFO.engineVersion,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// We override the remote logger stub with the "real" one. Eventually the runner should not
|
|
||||||
// be aware of the remote logger at all, and it should be integrated with our logging infrastructure.
|
|
||||||
const remoteLogger = accessToken != null ? new remoteLog.RemoteLogger(accessToken) : null
|
|
||||||
newApp.remoteLog = (message: string, metadata: unknown) => {
|
|
||||||
const metadataObject =
|
|
||||||
typeof metadata === 'object' && metadata != null ? metadata : { metadata }
|
|
||||||
const actualMetadata =
|
|
||||||
loggingMetadata == null ? metadata : { ...loggingMetadata, ...metadataObject }
|
|
||||||
if (newApp.config.options.dataCollection.value && remoteLogger != null) {
|
|
||||||
// FIXME [sb]: https://github.com/enso-org/cloud-v2/issues/735
|
|
||||||
// The current GUI sends a lot of logs (over 300) every time a project is opened.
|
|
||||||
// This severely degrades performance, and the logs generated do not appear to be
|
|
||||||
// useful. This should be re-enabled when the GUI no longer sends a large amount
|
|
||||||
// of logs.
|
|
||||||
// await remoteLogger.remoteLog(message, actualMetadata)
|
|
||||||
} else {
|
|
||||||
const logMessage = [
|
|
||||||
'Not sending log to remote server. Data collection is disabled.',
|
|
||||||
`Message: "${message}"`,
|
|
||||||
`Metadata: ${JSON.stringify(actualMetadata)}`,
|
|
||||||
].join(' ')
|
|
||||||
|
|
||||||
logger.log(logMessage)
|
|
||||||
}
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
this.app = newApp
|
|
||||||
|
|
||||||
if (!this.app.initialized) {
|
|
||||||
console.error('Failed to initialize the application.')
|
|
||||||
} else {
|
|
||||||
if (!(await checkMinSupportedVersion(configOptions))) {
|
|
||||||
displayDeprecatedVersionDialog()
|
|
||||||
} else {
|
|
||||||
const email = configOptions.groups.authentication.options.email.value
|
|
||||||
// The default value is `""`, so a truthiness check is most appropriate here.
|
|
||||||
if (email) {
|
|
||||||
logger.log(`User identified as '${email}'.`)
|
|
||||||
}
|
|
||||||
void this.app.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The entrypoint into the IDE. */
|
|
||||||
main(inputConfig?: StringConfig) {
|
|
||||||
/** Note: Signing out always redirects to `/`. It is impossible to make this work,
|
|
||||||
* as it is not possible to distinguish between having just logged out, and explicitly
|
|
||||||
* opening a page with no URL parameters set.
|
|
||||||
*
|
|
||||||
* Client-side routing endpoints are explicitly not supported for live-reload, as they are
|
|
||||||
* transitional pages that should not need live-reload when running `gui watch`. */
|
|
||||||
const url = new URL(location.href)
|
|
||||||
const isInAuthenticationFlow = url.searchParams.has('code') && url.searchParams.has('state')
|
|
||||||
const authenticationUrl = location.href
|
|
||||||
if (isInAuthenticationFlow) {
|
|
||||||
gtag.gtag('event', 'cloud_sign_in_redirect')
|
|
||||||
history.replaceState(null, '', localStorage.getItem(INITIAL_URL_KEY))
|
|
||||||
}
|
|
||||||
const configOptions = contentConfig.OPTIONS.clone()
|
|
||||||
const parseOk = configOptions.loadAllAndDisplayHelpIfUnsuccessful([app.urlParams()])
|
|
||||||
if (isInAuthenticationFlow) {
|
|
||||||
history.replaceState(null, '', authenticationUrl)
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(INITIAL_URL_KEY, location.href)
|
|
||||||
}
|
|
||||||
if (parseOk) {
|
|
||||||
const supportsVibrancy = configOptions.groups.window.options.vibrancy.value
|
|
||||||
const shouldUseAuthentication = configOptions.options.authentication.value
|
|
||||||
const isOpeningMainEntryPoint =
|
|
||||||
configOptions.groups.startup.options.entry.value ===
|
|
||||||
configOptions.groups.startup.options.entry.default
|
|
||||||
const initialProjectName = configOptions.groups.startup.options.project.value || null
|
|
||||||
// This does not need to be removed from the URL, but only because local projects
|
|
||||||
// also use the Project Manager URL, and remote (cloud) projects remove the URL
|
|
||||||
// completely.
|
|
||||||
const projectManagerUrl =
|
|
||||||
configOptions.groups.engine.options.projectManagerUrl.value || null
|
|
||||||
// This MUST be removed as it would otherwise override the `startup.project` passed
|
|
||||||
// explicitly in `ide.tsx`.
|
|
||||||
if (isOpeningMainEntryPoint && url.searchParams.has('startup.project')) {
|
|
||||||
url.searchParams.delete('startup.project')
|
|
||||||
history.replaceState(null, '', url.toString())
|
|
||||||
}
|
|
||||||
if (shouldUseAuthentication && isOpeningMainEntryPoint) {
|
|
||||||
this.runAuthentication({
|
|
||||||
supportsVibrancy,
|
|
||||||
isInAuthenticationFlow,
|
|
||||||
projectManagerUrl,
|
|
||||||
shouldUseAuthentication,
|
|
||||||
initialProjectName,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
void this.runApp(inputConfig ?? null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Begins the authentication UI flow. */
|
|
||||||
runAuthentication(config: AuthenticationConfig) {
|
|
||||||
const ideElement = document.getElementById('root')
|
|
||||||
if (ideElement) {
|
|
||||||
ideElement.style.top = '-100vh'
|
|
||||||
ideElement.style.position = 'fixed'
|
|
||||||
}
|
|
||||||
const ide2Element = document.getElementById('app')
|
|
||||||
if (ide2Element) {
|
|
||||||
ide2Element.style.display = 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
/** TODO [NP]: https://github.com/enso-org/cloud-v2/issues/345
|
|
||||||
* `content` and `dashboard` packages **MUST BE MERGED INTO ONE**. The IDE
|
|
||||||
* should only have one entry point. Right now, we have two. One for the cloud
|
|
||||||
* and one for the desktop. */
|
|
||||||
/** FIXME [PB]: https://github.com/enso-org/cloud-v2/issues/366
|
|
||||||
* React hooks rerender themselves multiple times. It is resulting in multiple
|
|
||||||
* Enso main scene being initialized. As a temporary workaround we check whether
|
|
||||||
* appInstance was already ran. Target solution should move running appInstance
|
|
||||||
* where it will be called only once. */
|
|
||||||
dashboard.run({
|
|
||||||
appRunner: this,
|
|
||||||
logger,
|
|
||||||
vibrancy: config.supportsVibrancy,
|
|
||||||
supportsLocalBackend: SUPPORTS_LOCAL_BACKEND,
|
|
||||||
supportsDeepLinks: SUPPORTS_DEEP_LINKS,
|
|
||||||
projectManagerUrl: config.projectManagerUrl,
|
|
||||||
isAuthenticationDisabled: !config.shouldUseAuthentication,
|
|
||||||
shouldShowDashboard: true,
|
|
||||||
initialProjectName: config.initialProjectName,
|
|
||||||
onAuthenticated: () => {
|
|
||||||
if (config.isInAuthenticationFlow) {
|
|
||||||
const initialUrl = localStorage.getItem(INITIAL_URL_KEY)
|
|
||||||
if (initialUrl != null) {
|
|
||||||
// This is not used past this point, however it is set to the initial URL
|
|
||||||
// to make refreshing work as expected.
|
|
||||||
history.replaceState(null, '', initialUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error `globalConfig.windowAppScopeName` is not known at typecheck time.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
window[GLOBAL_CONFIG.windowAppScopeName] = new Main()
|
|
@ -1,62 +0,0 @@
|
|||||||
<!--
|
|
||||||
FIXME [NP]: https://github.com/enso-org/cloud-v2/issues/345
|
|
||||||
This file is used by both the `content` and `dashboard` packages. The `dashboard` package uses it
|
|
||||||
via a symlink. This is temporary, while the `content` and `dashboard` have separate entrypoints
|
|
||||||
for cloud and desktop. Once they are merged, the symlink must be removed.
|
|
||||||
-->
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
|
||||||
<!-- FIXME https://github.com/validator/validator/issues/917 -->
|
|
||||||
<!-- FIXME Security Vulnerabilities: https://github.com/enso-org/ide/issues/226 -->
|
|
||||||
<!-- NOTE `frame-src` section of `http-equiv` required only for authorization -->
|
|
||||||
<!-- NOTE [NP]: https://stripe.com/docs/security/guide#content-security-policy for Stripe.js -->
|
|
||||||
<meta
|
|
||||||
http-equiv="Content-Security-Policy"
|
|
||||||
content="
|
|
||||||
default-src 'self';
|
|
||||||
frame-src 'self' data: https://js.stripe.com;
|
|
||||||
script-src 'self' 'unsafe-eval' data: https://*;
|
|
||||||
style-src 'self' 'unsafe-inline' data: https://*;
|
|
||||||
connect-src 'self' data: ws://localhost:* ws://127.0.0.1:* http://localhost:* https://* wss://*;
|
|
||||||
worker-src 'self' blob:;
|
|
||||||
img-src 'self' blob: data: https://*;
|
|
||||||
font-src 'self' data: https://*"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="
|
|
||||||
width=device-width,
|
|
||||||
initial-scale = 1.0,
|
|
||||||
maximum-scale = 1.0,
|
|
||||||
user-scalable = no"
|
|
||||||
/>
|
|
||||||
<title>Enso</title>
|
|
||||||
<link rel="stylesheet" href="./tailwind.css" />
|
|
||||||
<link rel="stylesheet" href="./style.css" />
|
|
||||||
<!-- Generated by the build script based on the Enso Font package. -->
|
|
||||||
<link rel="stylesheet" href="./ensoFont.css" />
|
|
||||||
<script type="module" src="./entrypoint.js" defer></script>
|
|
||||||
<script type="module" src="./run.js" defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<div id="enso-dashboard" class="enso-dashboard"></div>
|
|
||||||
<div id="enso-chat" class="enso-chat"></div>
|
|
||||||
<noscript>
|
|
||||||
This page requires JavaScript to run. Please enable it in your browser.
|
|
||||||
</noscript>
|
|
||||||
<script
|
|
||||||
src="https://cdn.jsdelivr.net/npm/@twemoji/api@14.1.2/dist/twemoji.min.js"
|
|
||||||
integrity="sha384-D6GSzpW7fMH86ilu73eB95ipkfeXcMPoOGVst/L04yqSSe+RTUY0jXcuEIZk0wrT"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
<!-- Google tag (gtag.js) -->
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-CLTBJ37MDM"
|
|
||||||
></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,77 +0,0 @@
|
|||||||
/** @file Defines the {@link RemoteLogger} class and {@link remoteLog} function for sending logs to a remote server.
|
|
||||||
* {@link RemoteLogger} provides a convenient way to manage remote logging with access token authorization. */
|
|
||||||
|
|
||||||
import * as app from 'enso-runner/src/runner'
|
|
||||||
|
|
||||||
const logger = app.log.logger
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/** URL address where remote logs should be sent. */
|
|
||||||
const REMOTE_LOG_URL =
|
|
||||||
process.env.ENSO_CLOUD_API_URL == null
|
|
||||||
? null
|
|
||||||
: new URL(`${process.env.ENSO_CLOUD_API_URL}/logs`)
|
|
||||||
|
|
||||||
// ====================
|
|
||||||
// === RemoteLogger ===
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
// === Class ===
|
|
||||||
|
|
||||||
/** Helper class facilitating sending logs to a remote. */
|
|
||||||
export class RemoteLogger {
|
|
||||||
/** Initialize a new instance.
|
|
||||||
* @param accessToken - JWT token used to authenticate within the cloud. */
|
|
||||||
constructor(public accessToken: string) {
|
|
||||||
this.accessToken = accessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sends a log message to a remote.
|
|
||||||
* @param message - The log message to send.
|
|
||||||
* @param metadata - Additional metadata to send along with the log.
|
|
||||||
* @returns Promise which resolves when the log message has been sent. */
|
|
||||||
async remoteLog(message: string, metadata: unknown): Promise<void> {
|
|
||||||
await remoteLog(this.accessToken, message, metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Underlying logic ===
|
|
||||||
|
|
||||||
/** Sends a log message to a remote server using the provided access token.
|
|
||||||
* @param accessToken - The access token for authentication.
|
|
||||||
* @param message - The message to be logged on the server.
|
|
||||||
* @param metadata - Additional metadata to include in the log.
|
|
||||||
* @throws Will throw an error if the response from the server is not okay (response status is not 200).
|
|
||||||
* @returns Returns a promise that resolves when the log message is successfully sent. */
|
|
||||||
export async function remoteLog(
|
|
||||||
accessToken: string,
|
|
||||||
message: string,
|
|
||||||
metadata: unknown
|
|
||||||
): Promise<void> {
|
|
||||||
if (REMOTE_LOG_URL != null) {
|
|
||||||
try {
|
|
||||||
const headers: HeadersInit = [
|
|
||||||
['Content-Type', 'application/json'],
|
|
||||||
['Authorization', `Bearer ${accessToken}`],
|
|
||||||
]
|
|
||||||
const body = JSON.stringify({ message, metadata })
|
|
||||||
const response = await fetch(REMOTE_LOG_URL, { method: 'POST', headers, body })
|
|
||||||
if (response.ok) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
const errorMessage = `Error while sending log to a remote: Status ${response.status}.`
|
|
||||||
const text = await response.text().catch(error => {
|
|
||||||
throw new Error(`${errorMessage} Failed to read response: ${String(error)}.`)
|
|
||||||
})
|
|
||||||
throw new Error(`${errorMessage} Response: ${text}.`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error)
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
/** @file This file is used to simply run the IDE. It can be not invoked if the IDE needs to be used
|
|
||||||
* as a library. */
|
|
||||||
|
|
||||||
// ===============
|
|
||||||
// === Run IDE ===
|
|
||||||
// ===============
|
|
||||||
|
|
||||||
// This `void` is used to explicitly not `await` a promise, not to produce an `undefined`.
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
void window.enso?.main()
|
|
@ -1,38 +0,0 @@
|
|||||||
/** @file A service worker that redirects paths without extensions to `/index.html`.
|
|
||||||
* This is required for paths like `/login`, which are handled by client-side routing,
|
|
||||||
* to work when developing locally on `localhost:8080`. */
|
|
||||||
// Bring globals and interfaces specific to Web Workers into scope.
|
|
||||||
/// <reference lib="WebWorker" />
|
|
||||||
import * as constants from './serviceWorkerConstants'
|
|
||||||
|
|
||||||
// =====================
|
|
||||||
// === Fetch handler ===
|
|
||||||
// =====================
|
|
||||||
|
|
||||||
// We `declare` a variable here because Service Workers have a different global scope.
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
declare const self: ServiceWorkerGlobalScope
|
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.open(constants.CACHE_NAME).then(cache => {
|
|
||||||
void cache.addAll(constants.DEPENDENCIES)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addEventListener('fetch', event => {
|
|
||||||
const url = new URL(event.request.url)
|
|
||||||
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
event.respondWith(
|
|
||||||
caches
|
|
||||||
.open(constants.CACHE_NAME)
|
|
||||||
.then(cache => cache.match(event.request))
|
|
||||||
.then(response => response ?? fetch(event.request))
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,59 +0,0 @@
|
|||||||
/** @file Constants shared between all service workers (development and production). */
|
|
||||||
import * as common from 'enso-common'
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/** The name of the cache under which offline assets are stored. */
|
|
||||||
export const CACHE_NAME = common.PRODUCT_NAME.toLowerCase()
|
|
||||||
|
|
||||||
/** The numbers after each font loaded by the "M PLUS 1" font. */
|
|
||||||
const M_PLUS_1_SECTIONS = [
|
|
||||||
/* eslint-disable @typescript-eslint/no-magic-numbers */
|
|
||||||
0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
|
||||||
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 53,
|
|
||||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
|
|
||||||
78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
|
|
||||||
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
|
|
||||||
/* eslint-enable @typescript-eslint/no-magic-numbers */
|
|
||||||
]
|
|
||||||
|
|
||||||
/** The complete list of assets to cache for offline use. */
|
|
||||||
export const DEPENDENCIES = [
|
|
||||||
// app/gui/view/graph-editor/src/builtin/visualization/java_script/heatmap.js
|
|
||||||
// app/gui/view/graph-editor/src/builtin/visualization/java_script/histogram.js
|
|
||||||
// app/gui/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js
|
|
||||||
'https://d3js.org/d3.v4.min.js',
|
|
||||||
'https://fonts.cdnfonts.com/css/dejavu-sans-mono',
|
|
||||||
// Loaded by https://fonts.cdnfonts.com/css/dejavu-sans-mono
|
|
||||||
'https://fonts.cdnfonts.com/s/108/DejaVuSansMono.woff',
|
|
||||||
'https://fonts.cdnfonts.com/s/108/DejaVuSansMono-Oblique.woff',
|
|
||||||
'https://fonts.cdnfonts.com/s/108/DejaVuSansMono-Bold.woff',
|
|
||||||
'https://fonts.cdnfonts.com/s/108/DejaVuSansMono-BoldOblique.woff',
|
|
||||||
// app/gui/view/graph-editor/src/builtin/visualization/java_script/geoMap.js
|
|
||||||
'https://unpkg.com/deck.gl@8.4/dist.min.js',
|
|
||||||
'https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js',
|
|
||||||
'https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css',
|
|
||||||
// Loaded by https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js
|
|
||||||
'https://api.mapbox.com/styles/v1/mapbox/light-v9?access_token=pk.' +
|
|
||||||
'eyJ1IjoiZW5zby1vcmciLCJhIjoiY2tmNnh5MXh2MGlyOTJ5cWdubnFxbXo4ZSJ9.3KdAcCiiXJcSM18nwk09-Q',
|
|
||||||
'https://api.mapbox.com/styles/v1/mapbox/light-v9/sprite.json?access_token=pk.' +
|
|
||||||
'eyJ1IjoiZW5zby1vcmciLCJhIjoiY2tmNnh5MXh2MGlyOTJ5cWdubnFxbXo4ZSJ9.3KdAcCiiXJcSM18nwk09-Q',
|
|
||||||
'https://api.mapbox.com/styles/v1/mapbox/light-v9/sprite.png?access_token=pk.' +
|
|
||||||
'eyJ1IjoiZW5zby1vcmciLCJhIjoiY2tmNnh5MXh2MGlyOTJ5cWdubnFxbXo4ZSJ9.3KdAcCiiXJcSM18nwk09-Q',
|
|
||||||
// app/gui/view/graph-editor/src/builtin/visualization/java_script/sql.js
|
|
||||||
'https://cdnjs.cloudflare.com/ajax/libs/sql-formatter/4.0.2/sql-formatter.min.js',
|
|
||||||
// app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js
|
|
||||||
'https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js',
|
|
||||||
'https://cdn.jsdelivr.net/npm/ag-grid-community/styles/ag-grid.css',
|
|
||||||
'https://cdn.jsdelivr.net/npm/ag-grid-community/styles/ag-theme-alpine.css',
|
|
||||||
// app/ide-desktop/lib/dashboard/src/tailwind.css
|
|
||||||
'https://fonts.googleapis.com/css2?family=M+PLUS+1:wght@500;700&display=swap',
|
|
||||||
// Loaded by https://fonts.googleapis.com/css2?family=M+PLUS+1:wght@500;700&display=swap
|
|
||||||
...M_PLUS_1_SECTIONS.map(
|
|
||||||
number =>
|
|
||||||
'https://fonts.gstatic.com/s/mplus1/v6/' +
|
|
||||||
`R70ZjygA28ymD4HgBVu92j6eR1mYP_TX-Bb-rTg93gHfHe9F4Q.${number}.woff2`
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,262 +0,0 @@
|
|||||||
/* Fonts */
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-weight: 100;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 200;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 800;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
src: url("/MPLUS1[wght].ttf") format("truetype");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* End of fonts */
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
overscroll-behavior: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visualization {
|
|
||||||
z-index: 2;
|
|
||||||
border-radius: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-header {
|
|
||||||
font-family: sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
margin: 24px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-text {
|
|
||||||
text-align: justify;
|
|
||||||
font-family: sans-serif;
|
|
||||||
color: #454545;
|
|
||||||
width: 50%;
|
|
||||||
margin: 12px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-info {
|
|
||||||
text-align: center;
|
|
||||||
font-family: sans-serif;
|
|
||||||
color: #454545;
|
|
||||||
margin: 24px auto;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#crash-banner {
|
|
||||||
background: DarkSalmon;
|
|
||||||
color: #2c1007;
|
|
||||||
|
|
||||||
font-family: sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
/* Put the banner in front of the "root" node which has index 1 */
|
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
/* Center the banner horizontally */
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: auto;
|
|
||||||
|
|
||||||
width: fit-content;
|
|
||||||
padding: 1em;
|
|
||||||
|
|
||||||
border-bottom-left-radius: 8px;
|
|
||||||
border-bottom-right-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#crash-banner button {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
font: inherit;
|
|
||||||
|
|
||||||
/* Balance padding with negative margin to make the label fit with other text */
|
|
||||||
padding: 2px;
|
|
||||||
margin: -2px;
|
|
||||||
|
|
||||||
padding-left: 0.5em;
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
#crash-banner button:focus {
|
|
||||||
/* Show a 2px outline, following the button's shape, instead of the standard
|
|
||||||
rectangular outline */
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px #fbeee9;
|
|
||||||
}
|
|
||||||
|
|
||||||
#crash-banner #crash-banner-close-button {
|
|
||||||
float: right;
|
|
||||||
margin-left: 0.75em;
|
|
||||||
|
|
||||||
color: #2c1007;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
#crash-banner #crash-banner-close-button:hover {
|
|
||||||
color: #58210e;
|
|
||||||
}
|
|
||||||
#crash-banner #crash-banner-close-button:active {
|
|
||||||
color: #843115;
|
|
||||||
}
|
|
||||||
|
|
||||||
#crash-banner #crash-report-button {
|
|
||||||
float: right;
|
|
||||||
margin-left: 1em;
|
|
||||||
|
|
||||||
color: DarkSalmon;
|
|
||||||
background: #2c1007;
|
|
||||||
}
|
|
||||||
#crash-banner #crash-report-button:hover {
|
|
||||||
background-color: #58210e;
|
|
||||||
}
|
|
||||||
#crash-banner #crash-report-button:active {
|
|
||||||
background-color: #843115;
|
|
||||||
}
|
|
||||||
|
|
||||||
#crash-banner-content {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
#crash-banner hr {
|
|
||||||
height: 1px;
|
|
||||||
border: none;
|
|
||||||
background: #b96a50;
|
|
||||||
margin: 0.8em -1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-enable-checkbox {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0px;
|
|
||||||
right: 0px;
|
|
||||||
color: white;
|
|
||||||
font-size: 12px;
|
|
||||||
font-family: "M PLUS 1";
|
|
||||||
padding: 3px 6px;
|
|
||||||
background: rgba(15, 0, 77, 0.7);
|
|
||||||
cursor: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-enable-checkbox:has(input:checked) + #debug-root {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root > .debug-layer {
|
|
||||||
position: absolute;
|
|
||||||
top: 50vh;
|
|
||||||
left: 50vw;
|
|
||||||
width: 0px;
|
|
||||||
height: 0px;
|
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root > .debug-layer * {
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root > .debug-layer [data-name="Text"] > div {
|
|
||||||
background: rgba(0, 160, 60, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root > .debug-layer [data-name*="compound::rectangle::shape"] {
|
|
||||||
background: rgba(0, 20, 180, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root > .debug-layer .hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug-root > .debug-layer[data-layer-name="DETACHED"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
/** @file This module exports wasm Rust glue code generated by EnsoGL Pack. */
|
|
||||||
|
|
||||||
import * as wasmRustGlue from 'wasm_rust_glue'
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// === Export WASM Rust glue ===
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
// Eslint is not (and should not be) set up to check CommonJS.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
exports.init = wasmRustGlue.default
|
|
@ -1,36 +0,0 @@
|
|||||||
/** @file Start the file watch service. */
|
|
||||||
import * as esbuild from 'esbuild'
|
|
||||||
import * as portfinder from 'portfinder'
|
|
||||||
|
|
||||||
import * as bundler from './esbuild-config.js'
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
const PORT = 8080
|
|
||||||
const HTTP_STATUS_OK = 200
|
|
||||||
|
|
||||||
// ===============
|
|
||||||
// === Watcher ===
|
|
||||||
// ===============
|
|
||||||
|
|
||||||
/** Start the esbuild watcher. */
|
|
||||||
async function watch() {
|
|
||||||
const options = bundler.bundleOptions({ supportsLocalBackend: true, supportsDeepLinks: false })
|
|
||||||
const builder = await esbuild.context(options)
|
|
||||||
await builder.watch()
|
|
||||||
await builder.serve({
|
|
||||||
port: await portfinder.getPortPromise({ port: PORT }),
|
|
||||||
servedir: options.outdir,
|
|
||||||
/** This function is called on every request.
|
|
||||||
* It is used here to show an error if the file to serve was not found. */
|
|
||||||
onRequest(args) {
|
|
||||||
if (args.status !== HTTP_STATUS_OK) {
|
|
||||||
console.error(`HTTP error ${args.status} when serving path '${args.path}'.`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
void watch()
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": ["../types", "../../../../build.json", "."],
|
|
||||||
"references": [{ "path": "../dashboard" }]
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/** @file File watch and compile service. */
|
|
||||||
import * as path from 'node:path'
|
|
||||||
import * as url from 'node:url'
|
|
||||||
|
|
||||||
import * as esbuild from 'esbuild'
|
|
||||||
import * as portfinder from 'portfinder'
|
|
||||||
import chalk from 'chalk'
|
|
||||||
|
|
||||||
import * as bundler from './esbuild-config'
|
|
||||||
import * as dashboardBundler from '../dashboard/esbuild-config'
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/** The path of this file. */
|
|
||||||
const THIS_PATH = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)))
|
|
||||||
const PORT = 8080
|
|
||||||
const HTTP_STATUS_OK = 200
|
|
||||||
|
|
||||||
// ===============
|
|
||||||
// === Watcher ===
|
|
||||||
// ===============
|
|
||||||
|
|
||||||
/** Starts the esbuild watcher. */
|
|
||||||
async function watch() {
|
|
||||||
const dashboardOpts = dashboardBundler.bundleOptions()
|
|
||||||
const dashboardBuilder = await esbuild.context(dashboardOpts)
|
|
||||||
// We do not need to serve the dashboard as it outputs to the same directory.
|
|
||||||
// It will not rebuild on request, but it is not intended to rebuild on request anyway.
|
|
||||||
// This MUST be called before `builder.watch()` as `tailwind.css` must be generated
|
|
||||||
// before the copy plugin runs.
|
|
||||||
await dashboardBuilder.watch()
|
|
||||||
const args = bundler.argumentsFromEnv({ supportsLocalBackend: true, supportsDeepLinks: false })
|
|
||||||
const options = bundler.bundlerOptions(args)
|
|
||||||
options.pure.splice(options.pure.indexOf('assert'), 1)
|
|
||||||
options.define.REDIRECT_OVERRIDE = JSON.stringify('http://localhost:8080')
|
|
||||||
// This is safe as this entry point is statically known.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const serviceWorkerEntryPoint = options.entryPoints.find(
|
|
||||||
entryPoint => entryPoint.out === 'serviceWorker'
|
|
||||||
)!
|
|
||||||
serviceWorkerEntryPoint.in = path.resolve(THIS_PATH, 'src', 'devServiceWorker.ts')
|
|
||||||
const builder = await esbuild.context(options)
|
|
||||||
await builder.watch()
|
|
||||||
await builder.serve({
|
|
||||||
port: await portfinder.getPortPromise({ port: PORT }),
|
|
||||||
servedir: options.outdir,
|
|
||||||
/** This function is called on every request.
|
|
||||||
* It is used here to show an error if the file to serve was not found. */
|
|
||||||
onRequest(request) {
|
|
||||||
if (request.status !== HTTP_STATUS_OK) {
|
|
||||||
console.error(
|
|
||||||
chalk.red(`HTTP error ${request.status} when serving path '${request.path}'.`)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
void watch()
|
|
@ -66,6 +66,7 @@
|
|||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
"esbuild-plugin-inline-image": "^0.0.9",
|
"esbuild-plugin-inline-image": "^0.0.9",
|
||||||
"esbuild-plugin-time": "^1.0.0",
|
"esbuild-plugin-time": "^1.0.0",
|
||||||
|
"esbuild-plugin-yaml": "^0.0.1",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.49.0",
|
||||||
"eslint-plugin-jsdoc": "^46.8.1",
|
"eslint-plugin-jsdoc": "^46.8.1",
|
||||||
"eslint-plugin-react": "^7.32.1",
|
"eslint-plugin-react": "^7.32.1",
|
||||||
|
@ -18,7 +18,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"sharp": "^0.31.2",
|
"sharp": "^0.31.2",
|
||||||
"to-ico": "^1.1.5"
|
"to-ico": "^1.1.5",
|
||||||
|
"@types/sharp": "^0.31.1",
|
||||||
|
"@types/to-ico": "^1.1.1"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ heck = "0.4.0"
|
|||||||
enso-build-base = { path = "../base" }
|
enso-build-base = { path = "../base" }
|
||||||
enso-enso-font = { path = "../../lib/rust/enso-font" }
|
enso-enso-font = { path = "../../lib/rust/enso-font" }
|
||||||
enso-font = { path = "../../lib/rust/font" }
|
enso-font = { path = "../../lib/rust/font" }
|
||||||
enso-pack = { path = "../../lib/rust/enso-pack" }
|
|
||||||
ide-ci = { path = "../ci_utils" }
|
ide-ci = { path = "../ci_utils" }
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
new_mime_guess = "4.0.1"
|
new_mime_guess = "4.0.1"
|
||||||
|
7
lib/rust/enso-pack/Cargo.lock
generated
7
lib/rust/enso-pack/Cargo.lock
generated
@ -1,7 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "enso-pack"
|
|
||||||
version = "0.1.0"
|
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "enso-pack"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Enso Team <contact@enso.org>"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
futures = { version = "0.3" }
|
|
||||||
ide-ci = { path = "../../../build/ci_utils" }
|
|
||||||
manifest-dir-macros = "0.1.16"
|
|
||||||
serde = { workspace = true }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
tokio = { workspace = true }
|
|
||||||
walkdir = "2"
|
|
||||||
enso-prelude = { path = "../prelude" }
|
|
@ -1,117 +0,0 @@
|
|||||||
/** @file A simple argument parser. */
|
|
||||||
|
|
||||||
import * as util from 'node:util'
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// === Naming Conversions ===
|
|
||||||
// ==========================
|
|
||||||
|
|
||||||
/** Converts a camel case string to a kebab case string. */
|
|
||||||
function camelToKebabCase(name: string) {
|
|
||||||
return name
|
|
||||||
.split('')
|
|
||||||
.map((letter, idx) => {
|
|
||||||
return letter.toUpperCase() === letter
|
|
||||||
? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`
|
|
||||||
: letter
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// === Option ===
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
class Option<T> {
|
|
||||||
'default': T | undefined
|
|
||||||
value: T | undefined
|
|
||||||
description: string
|
|
||||||
type: 'string' | 'boolean'
|
|
||||||
constructor(description: string, def?: T) {
|
|
||||||
this.default = def
|
|
||||||
this.description = description
|
|
||||||
if (def === true || def === false) {
|
|
||||||
this.type = 'boolean'
|
|
||||||
} else {
|
|
||||||
this.type = 'string'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === ArgParser ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
interface ParseArgsOptionConfig {
|
|
||||||
type: 'string' | 'boolean'
|
|
||||||
multiple?: boolean | undefined
|
|
||||||
short?: string | undefined
|
|
||||||
default?: string | boolean | string[] | boolean[] | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Args {
|
|
||||||
[key: string]: Option<string | boolean>
|
|
||||||
help = new Option('Print help message.', false)
|
|
||||||
outDir = new Option<string>('The directory the extracted asset sources will be written to.')
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArgParser {
|
|
||||||
args = new Args()
|
|
||||||
|
|
||||||
parse() {
|
|
||||||
const optionToFieldNameMap = new Map<string, string>()
|
|
||||||
const options: Record<string, ParseArgsOptionConfig> = {}
|
|
||||||
for (const [fieldName, option] of Object.entries(this.args)) {
|
|
||||||
const optionName = camelToKebabCase(fieldName)
|
|
||||||
optionToFieldNameMap.set(optionName, fieldName)
|
|
||||||
options[optionName] = { type: option.type, default: option.default }
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const out = util.parseArgs({ options })
|
|
||||||
for (const [optionName, optionValue] of Object.entries(out.values)) {
|
|
||||||
const fieldName = optionToFieldNameMap.get(optionName)
|
|
||||||
if (fieldName) {
|
|
||||||
// @ts-expect-error
|
|
||||||
this.args[fieldName].value = optionValue
|
|
||||||
} else {
|
|
||||||
console.error(`Unknown option: ${optionName}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const msg = error instanceof Error ? `${error.message}. ` : ''
|
|
||||||
console.error(`${msg}Use --help to learn about possible options.`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
if (this.args.help.value) {
|
|
||||||
this.printHelpAndExit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printHelp() {
|
|
||||||
console.log(`Options:`)
|
|
||||||
for (const [fieldName, option] of Object.entries(this.args)) {
|
|
||||||
const optionName = camelToKebabCase(fieldName)
|
|
||||||
let header = `--${optionName}`
|
|
||||||
if (option.type == 'string') {
|
|
||||||
const def = option.default != null ? `[${option.default}]` : '<value>'
|
|
||||||
header += `=${def}`
|
|
||||||
}
|
|
||||||
console.log()
|
|
||||||
console.log(header)
|
|
||||||
console.log(option.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printHelpAndExit(exitCode: number) {
|
|
||||||
this.printHelp()
|
|
||||||
process.exit(exitCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parse the command line arguments. */
|
|
||||||
export function parse(): ArgParser {
|
|
||||||
const argParser = new ArgParser()
|
|
||||||
argParser.parse()
|
|
||||||
return argParser
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/** @file Tool for extracting sources of dynamic assets from compiled WASM binaries. */
|
|
||||||
|
|
||||||
import path from 'path'
|
|
||||||
import * as args from './args'
|
|
||||||
import * as fs from './fs'
|
|
||||||
import * as log from '../runner/log'
|
|
||||||
import * as runner from '../runner/index'
|
|
||||||
|
|
||||||
// ===========
|
|
||||||
// === App ===
|
|
||||||
// ===========
|
|
||||||
|
|
||||||
/** The main application. It loads the WASM file from disk, runs before main entry points, extract
|
|
||||||
* asset sources and saves them to files. */
|
|
||||||
class App extends runner.App {
|
|
||||||
override async loadWasm() {
|
|
||||||
const mainJsUrl = path.join(__dirname, this.config.groups.loader.options.jsUrl.value)
|
|
||||||
const mainWasmUrl = path.join(__dirname, this.config.groups.loader.options.wasmUrl.value)
|
|
||||||
const mainJs = await fs.readFile(mainJsUrl, 'utf8')
|
|
||||||
const mainWasm = await fs.readFile(mainWasmUrl)
|
|
||||||
this.wasm = await this.compileAndRunWasm(mainJs, mainWasm)
|
|
||||||
}
|
|
||||||
|
|
||||||
async extractAssets(outDir: string) {
|
|
||||||
await log.Task.asyncRun('Extracting dynamic assets source code.', async () => {
|
|
||||||
// Clear the extracted-sources directory before getting new sources.
|
|
||||||
// If getting sources fails we leave the directory empty, not outdated.
|
|
||||||
await fs.rm(outDir, { recursive: true, force: true })
|
|
||||||
await fs.mkdir(outDir)
|
|
||||||
const assetsMap = this.getAssetSources()
|
|
||||||
if (assetsMap) {
|
|
||||||
await log.Task.asyncRun(`Writing assets to '${outDir}'.`, async () => {
|
|
||||||
for (const [builder, asset] of assetsMap) {
|
|
||||||
for (const [key, files] of asset) {
|
|
||||||
const dirPath = path.join(outDir, builder, key)
|
|
||||||
await fs.mkdir(dirPath, { recursive: true })
|
|
||||||
for (const [name, data] of files) {
|
|
||||||
const filePath = path.join(dirPath, name)
|
|
||||||
await fs.writeFile(`${filePath}`, Buffer.from(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override async run(): Promise<void> {
|
|
||||||
const parser = args.parse()
|
|
||||||
const outDir = parser.args.outDir.value
|
|
||||||
if (outDir) {
|
|
||||||
await log.Task.asyncRun('Running the program.', async () => {
|
|
||||||
await app.loadAndInitWasm()
|
|
||||||
const r = app.runBeforeMainEntryPoints().then(() => {
|
|
||||||
return app.extractAssets(outDir)
|
|
||||||
})
|
|
||||||
await r
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
parser.printHelpAndExit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============
|
|
||||||
// === Main ===
|
|
||||||
// ============
|
|
||||||
|
|
||||||
const app = new App()
|
|
||||||
void app.run()
|
|
@ -1,130 +0,0 @@
|
|||||||
/** @file This module redefines some `node:fs` functions with embedded logging, so it is easy to
|
|
||||||
* track what they do. */
|
|
||||||
|
|
||||||
import {
|
|
||||||
MakeDirectoryOptions,
|
|
||||||
Mode,
|
|
||||||
ObjectEncodingOptions,
|
|
||||||
OpenMode,
|
|
||||||
PathLike,
|
|
||||||
RmDirOptions,
|
|
||||||
RmOptions,
|
|
||||||
} from 'node:fs'
|
|
||||||
import { FileHandle } from 'fs/promises'
|
|
||||||
import { Abortable } from 'node:events'
|
|
||||||
import { promises as fs } from 'fs'
|
|
||||||
import * as log from '../runner/log'
|
|
||||||
import { Stream } from 'node:stream'
|
|
||||||
|
|
||||||
// ================
|
|
||||||
// === readFile ===
|
|
||||||
// ================
|
|
||||||
|
|
||||||
export async function readFile(
|
|
||||||
path: PathLike | FileHandle,
|
|
||||||
options?:
|
|
||||||
| ({
|
|
||||||
encoding?: null | undefined
|
|
||||||
flag?: OpenMode | undefined
|
|
||||||
} & Abortable)
|
|
||||||
| null
|
|
||||||
): Promise<Buffer>
|
|
||||||
|
|
||||||
export async function readFile(
|
|
||||||
path: PathLike | FileHandle,
|
|
||||||
options:
|
|
||||||
| ({
|
|
||||||
encoding: BufferEncoding
|
|
||||||
flag?: OpenMode | undefined
|
|
||||||
} & Abortable)
|
|
||||||
| BufferEncoding
|
|
||||||
): Promise<string>
|
|
||||||
|
|
||||||
/** Read a file and log the operation. */
|
|
||||||
export async function readFile(
|
|
||||||
path: PathLike | FileHandle,
|
|
||||||
options?:
|
|
||||||
| (ObjectEncodingOptions &
|
|
||||||
Abortable & {
|
|
||||||
flag?: OpenMode | undefined
|
|
||||||
})
|
|
||||||
| BufferEncoding
|
|
||||||
| null
|
|
||||||
): Promise<string | Buffer> {
|
|
||||||
return log.Task.asyncRun(`Reading file '${String(path)}'.`, async () => {
|
|
||||||
return await fs.readFile(path, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// === unlink ===
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
/** Unlink a file and log the operation. */
|
|
||||||
export async function unlink(path: PathLike): Promise<void> {
|
|
||||||
return log.Task.asyncRun(`Removing file '${String(path)}'.`, async () => {
|
|
||||||
return await fs.unlink(path)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === rmdir ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/** Remove a directory and log the operation. */
|
|
||||||
export async function rmdir(path: PathLike, options?: RmDirOptions): Promise<void> {
|
|
||||||
return log.Task.asyncRun(`Removing directory '${String(path)}'.`, async () => {
|
|
||||||
return await fs.rmdir(path, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === mkdir ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/** Make a directory and log the operation. */
|
|
||||||
export async function mkdir(
|
|
||||||
path: PathLike,
|
|
||||||
options?: Mode | MakeDirectoryOptions | null
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
return log.Task.asyncRun(`Creating directory '${String(path)}'.`, async () => {
|
|
||||||
return await fs.mkdir(path, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========
|
|
||||||
// === rm ===
|
|
||||||
// ==========
|
|
||||||
|
|
||||||
/** Remove a file or directory and log the operation. */
|
|
||||||
export async function rm(path: PathLike, options?: RmOptions): Promise<void> {
|
|
||||||
return log.Task.asyncRun(`Removing '${String(path)}'.`, async () => {
|
|
||||||
return await fs.rm(path, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === writeFile ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/** Write a file and log the operation. */
|
|
||||||
export async function writeFile(
|
|
||||||
file: PathLike | FileHandle,
|
|
||||||
data:
|
|
||||||
| string
|
|
||||||
| NodeJS.ArrayBufferView
|
|
||||||
| Iterable<string | NodeJS.ArrayBufferView>
|
|
||||||
| AsyncIterable<string | NodeJS.ArrayBufferView>
|
|
||||||
| Stream,
|
|
||||||
options?:
|
|
||||||
| (ObjectEncodingOptions & {
|
|
||||||
mode?: Mode | undefined
|
|
||||||
flag?: OpenMode | undefined
|
|
||||||
} & Abortable)
|
|
||||||
| BufferEncoding
|
|
||||||
| null
|
|
||||||
): Promise<void> {
|
|
||||||
return log.Task.asyncRun(`Writing file '${String(file)}'.`, async () => {
|
|
||||||
return await fs.writeFile(file, data, options)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
/** @file Contains {@link spector} function which is a wrapper for
|
|
||||||
* [spectorjs]{@link https://github.com/BabylonJS/Spector.js}. */
|
|
||||||
|
|
||||||
/* eslint @typescript-eslint/no-unsafe-return: "off" */
|
|
||||||
/** Spectorjs function wrapper. */
|
|
||||||
export function spector() {
|
|
||||||
return require('spectorjs')
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
/** @file @{link pkg} init export. */
|
|
||||||
|
|
||||||
// Following imports are only valid in context of the built package. Here, those are guaranteed to
|
|
||||||
// not resolve correctly, so we need to disable the type checking.
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
import init from './pkg.js'
|
|
||||||
// @ts-expect-error
|
|
||||||
export * from './runtime-libs'
|
|
||||||
|
|
||||||
export { init }
|
|
@ -1,401 +0,0 @@
|
|||||||
//! Building dynamic assets (assets which require the application to be run to generate their
|
|
||||||
//! sources).
|
|
||||||
//!
|
|
||||||
//! The essential operation, producing a directory of outputs from a directory of inputs, is
|
|
||||||
//! implemented by each builder (e.g. [`Builder::Shader`], [`Builder::Font`]).
|
|
||||||
//!
|
|
||||||
//! As builders can take some time to run, a caching mechanism is used to avoid unnecessary
|
|
||||||
//! rebuilds. Caching is achieved by making populating-the-output-directory an idempotent process:
|
|
||||||
//! Paths within the output directory are dependent on the *content* of the corresponding input
|
|
||||||
//! files, so that if a calculated output path already exists, it is already up-to-date; otherwise,
|
|
||||||
//! it must be built. This design may be familiar to users of the Nix or Guix package managers.
|
|
||||||
|
|
||||||
use ide_ci::prelude::*;
|
|
||||||
|
|
||||||
use crate::Paths;
|
|
||||||
|
|
||||||
use enso_prelude::anyhow;
|
|
||||||
use ide_ci::programs::shaderc::Glslc;
|
|
||||||
use ide_ci::programs::shaderc::SpirvOpt;
|
|
||||||
use ide_ci::programs::spirv_cross::SpirvCross;
|
|
||||||
use std::hash::Hasher;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Build ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/// Bring the dynamic assets up-to-date, for the current asset sources. This consists of:
|
|
||||||
/// - Scan the asset source directory tree, hashing the input files.
|
|
||||||
/// - Update the assets:
|
|
||||||
/// - For each asset-source directory, determine an output directory based on the inputs name and
|
|
||||||
/// the hashes of its files.
|
|
||||||
/// - If that output directory doesn't exist, run the builder (determined by the top-level
|
|
||||||
/// directory the in which the asset was found, e.g. `shader`) and populate the directory.
|
|
||||||
/// - Generate a manifest, identifying the current assets and paths to their sources.
|
|
||||||
pub async fn build(paths: &Paths) -> Result<()> {
|
|
||||||
info!("Building dynamic assets.");
|
|
||||||
let sources = survey_asset_sources(paths)?;
|
|
||||||
let assets = update_assets(paths, &sources).await?;
|
|
||||||
let manifest = serde_json::to_string(&assets)?;
|
|
||||||
ide_ci::fs::tokio::write(&paths.target.enso_pack.dist.dynamic_assets.manifest, manifest)
|
|
||||||
.await?;
|
|
||||||
gc_assets(paths, &assets)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ===============
|
|
||||||
// === Builder ===
|
|
||||||
// ===============
|
|
||||||
|
|
||||||
/// Identifies an asset type, which determines how it is built.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
enum Builder {
|
|
||||||
Font,
|
|
||||||
Shader,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
fn dir_name<'a>(self) -> &'a str {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn build_asset(
|
|
||||||
self,
|
|
||||||
input_dir: &Path,
|
|
||||||
input_files: &[String],
|
|
||||||
output_dir: &Path,
|
|
||||||
tmp: &Path,
|
|
||||||
) -> Result<()> {
|
|
||||||
match self {
|
|
||||||
Builder::Font => build_font(input_dir, input_files, output_dir).await,
|
|
||||||
Builder::Shader => build_shader(input_dir, input_files, output_dir, tmp).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for Builder {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
"font" => Ok(Builder::Font),
|
|
||||||
"shader" => Ok(Builder::Shader),
|
|
||||||
other => Err(anyhow!("Unknown builder: {other:?}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Builder> for &'static str {
|
|
||||||
fn from(value: Builder) -> Self {
|
|
||||||
match value {
|
|
||||||
Builder::Font => "font",
|
|
||||||
Builder::Shader => "shader",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ====================
|
|
||||||
// === Build Inputs ===
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
/// The inputs to a builder.
|
|
||||||
struct AssetSources {
|
|
||||||
asset_key: String,
|
|
||||||
input_files: Vec<String>,
|
|
||||||
inputs_hash: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AssetSources {
|
|
||||||
/// The output directory name for the asset.
|
|
||||||
fn dir_name(&self) -> String {
|
|
||||||
let key = &self.asset_key;
|
|
||||||
let hash = self.inputs_hash;
|
|
||||||
format!("{key}-{hash:x}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =====================
|
|
||||||
// === Build Outputs ===
|
|
||||||
// =====================
|
|
||||||
|
|
||||||
/// The outputs of a builder.
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Asset {
|
|
||||||
dir: String,
|
|
||||||
files: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The outputs of all builders.
|
|
||||||
type AssetManifest = BTreeMap<Builder, BTreeMap<String, Asset>>;
|
|
||||||
|
|
||||||
|
|
||||||
// ================
|
|
||||||
// === Building ===
|
|
||||||
// ================
|
|
||||||
|
|
||||||
/// Scan the sources found in the asset sources directory.
|
|
||||||
///
|
|
||||||
/// Returns, for each [`Builder`] (e.g. shader or font), for each asset directory found, an
|
|
||||||
/// [`AssetSources`] object identifying the asset key (i.e. the name of its directory), its input
|
|
||||||
/// files, and a hash covering all its input files.
|
|
||||||
fn survey_asset_sources(paths: &Paths) -> Result<HashMap<Builder, Vec<AssetSources>>> {
|
|
||||||
let dir = ide_ci::fs::read_dir(&paths.target.enso_pack.dynamic_assets)?;
|
|
||||||
let mut asset_sources: HashMap<_, Vec<_>> = HashMap::new();
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
for entry in dir {
|
|
||||||
let entry = entry?;
|
|
||||||
let builder = Builder::try_from(entry.file_name().to_string_lossy().as_ref())?;
|
|
||||||
let builder_dir = ide_ci::fs::read_dir(entry.path())?;
|
|
||||||
let builder_sources = asset_sources.entry(builder).or_default();
|
|
||||||
for entry in builder_dir {
|
|
||||||
let entry = entry?;
|
|
||||||
let asset_key = entry.file_name().to_string_lossy().to_string();
|
|
||||||
let dir = ide_ci::fs::read_dir(entry.path())?;
|
|
||||||
let mut file_hashes = BTreeMap::new();
|
|
||||||
for entry in dir {
|
|
||||||
let entry = entry?;
|
|
||||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
|
||||||
let path = entry.path();
|
|
||||||
buf.clear();
|
|
||||||
ide_ci::fs::open(path)?.read_to_end(&mut buf)?;
|
|
||||||
let mut file_hasher = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
buf.hash(&mut file_hasher);
|
|
||||||
file_hashes.insert(file_name, file_hasher.finish());
|
|
||||||
}
|
|
||||||
let mut asset_hasher = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
file_hashes.hash(&mut asset_hasher);
|
|
||||||
let inputs_hash = asset_hasher.finish();
|
|
||||||
let input_files = file_hashes.into_keys().collect();
|
|
||||||
builder_sources.push(AssetSources { asset_key, input_files, inputs_hash });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(asset_sources)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate any assets not found up-to-date in the cache.
|
|
||||||
///
|
|
||||||
/// If an output directory already exists, it can be assumed to be up-to-date (because output path
|
|
||||||
/// is dependent on the input data), and is used as-is. Otherwise, [`build_asset`] runs the
|
|
||||||
/// appropriate builder to generate the output directory. In either case, a summary of the files
|
|
||||||
/// present in the output directory is produced; these summaries are assembled into an
|
|
||||||
/// [`AssetManifest`].
|
|
||||||
///
|
|
||||||
/// When asset builders need to be invoked, they are all run in parallel.
|
|
||||||
async fn update_assets(
|
|
||||||
paths: &Paths,
|
|
||||||
sources: &HashMap<Builder, Vec<AssetSources>>,
|
|
||||||
) -> Result<AssetManifest> {
|
|
||||||
let out = &paths.target.enso_pack.dist.dynamic_assets;
|
|
||||||
ide_ci::fs::create_dir_if_missing(out)?;
|
|
||||||
let mut assets: AssetManifest = BTreeMap::new();
|
|
||||||
let mut deferred_assets: BTreeMap<Builder, Vec<_>> = BTreeMap::new();
|
|
||||||
for (&builder, builder_sources) in sources {
|
|
||||||
let out = out.join(builder.dir_name());
|
|
||||||
ide_ci::fs::create_dir_if_missing(&out)?;
|
|
||||||
for source_specification in builder_sources {
|
|
||||||
let out = out.join(source_specification.dir_name());
|
|
||||||
let key = source_specification.asset_key.clone();
|
|
||||||
match std::fs::try_exists(&out)? {
|
|
||||||
false => {
|
|
||||||
info!("Rebuilding asset: `{}`.", out.display());
|
|
||||||
let builder_assets = deferred_assets.entry(builder).or_default();
|
|
||||||
let build = build_asset(paths, builder, source_specification);
|
|
||||||
builder_assets.push(async move { Ok((key, build.await?)) });
|
|
||||||
}
|
|
||||||
true => {
|
|
||||||
debug!("Skipping clean asset: `{}`.", out.display());
|
|
||||||
let builder_assets = assets.entry(builder).or_default();
|
|
||||||
let asset = survey_asset(paths, builder, source_specification)?;
|
|
||||||
builder_assets.insert(key, asset);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (builder, deferred_assets) in deferred_assets.into_iter() {
|
|
||||||
let deferred_assets =
|
|
||||||
futures::stream::iter(deferred_assets).buffer_unordered(50).collect::<Vec<_>>().await;
|
|
||||||
let deferred_assets: Result<Vec<_>> = deferred_assets.into_iter().collect();
|
|
||||||
assets.entry(builder).or_default().extend(deferred_assets?);
|
|
||||||
}
|
|
||||||
Ok(assets)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate an asset from the given sources.
|
|
||||||
///
|
|
||||||
/// Set up paths (as described in the [`crate`] docs): run the appropriate [`Builder`]; move its
|
|
||||||
/// output from a temporary path into its final location (note that outputs are not built directly
|
|
||||||
/// in their final location, because directories found in the output tree are assumed to
|
|
||||||
/// accurately represent the results of running the specified builder for the specified inputs;
|
|
||||||
/// creating the output directory in its complete state ensures that if a build process is
|
|
||||||
/// interrupted, incomplete artifacts are never used).
|
|
||||||
async fn build_asset(
|
|
||||||
paths: &Paths,
|
|
||||||
builder: Builder,
|
|
||||||
source_specification: &AssetSources,
|
|
||||||
) -> Result<Asset> {
|
|
||||||
let input_dir = paths
|
|
||||||
.target
|
|
||||||
.enso_pack
|
|
||||||
.dynamic_assets
|
|
||||||
.join(builder.dir_name())
|
|
||||||
.join(&source_specification.asset_key);
|
|
||||||
let tmp_output_dir = paths
|
|
||||||
.target
|
|
||||||
.enso_pack
|
|
||||||
.dist
|
|
||||||
.dynamic_assets
|
|
||||||
.join(builder.dir_name())
|
|
||||||
.join(&source_specification.asset_key);
|
|
||||||
tokio::fs::create_dir(&tmp_output_dir).await?;
|
|
||||||
let work_path = paths
|
|
||||||
.target
|
|
||||||
.enso_pack
|
|
||||||
.dynamic_assets
|
|
||||||
.join(builder.dir_name())
|
|
||||||
.join(format!("{}.work", source_specification.asset_key));
|
|
||||||
builder
|
|
||||||
.build_asset(&input_dir, &source_specification.input_files, &tmp_output_dir, &work_path)
|
|
||||||
.await?;
|
|
||||||
let output_dir = paths
|
|
||||||
.target
|
|
||||||
.enso_pack
|
|
||||||
.dist
|
|
||||||
.dynamic_assets
|
|
||||||
.join(builder.dir_name())
|
|
||||||
.join(source_specification.dir_name());
|
|
||||||
tokio::fs::rename(tmp_output_dir, output_dir).await?;
|
|
||||||
survey_asset(paths, builder, source_specification)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Identify the files present in an asset directory.
|
|
||||||
fn survey_asset(
|
|
||||||
paths: &Paths,
|
|
||||||
builder: Builder,
|
|
||||||
source_specification: &AssetSources,
|
|
||||||
) -> Result<Asset> {
|
|
||||||
let dir = source_specification.dir_name();
|
|
||||||
let path = paths.target.enso_pack.dist.dynamic_assets.join(builder.dir_name()).join(&dir);
|
|
||||||
let mut files = Vec::new();
|
|
||||||
for entry in ide_ci::fs::read_dir(&path)? {
|
|
||||||
files.push(entry?.file_name().to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
Ok(Asset { dir, files })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove any assets not present in the manifest.
|
|
||||||
fn gc_assets(paths: &Paths, assets: &AssetManifest) -> Result<()> {
|
|
||||||
let is_not_manifest = |entry: &std::io::Result<std::fs::DirEntry>| {
|
|
||||||
entry
|
|
||||||
.as_ref()
|
|
||||||
.map(|entry| entry.path() != paths.target.enso_pack.dist.dynamic_assets.manifest)
|
|
||||||
.unwrap_or(true)
|
|
||||||
};
|
|
||||||
for entry in paths.target.enso_pack.dist.dynamic_assets.read_dir()?.filter(is_not_manifest) {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
let builder = Builder::try_from(entry.file_name().to_string_lossy().as_ref()).ok();
|
|
||||||
let assets = builder.and_then(|builder| assets.get(&builder));
|
|
||||||
match assets {
|
|
||||||
Some(assets) => {
|
|
||||||
let assets: HashSet<_> = assets.values().map(|asset| asset.dir.as_ref()).collect();
|
|
||||||
for entry in path.read_dir()? {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
if !assets.contains(entry.file_name().to_string_lossy().as_ref()) {
|
|
||||||
info!("Cleaning unused asset at `{}`.", path.display());
|
|
||||||
ide_ci::fs::remove_if_exists(path)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
info!("Cleaning unused builder at `{}`.", path.display());
|
|
||||||
ide_ci::fs::remove_if_exists(path)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Fonts ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
async fn build_font(input_dir: &Path, input_files: &[String], output_dir: &Path) -> Result<()> {
|
|
||||||
for file_name in input_files {
|
|
||||||
crate::copy(input_dir.join(file_name), output_dir.join(file_name))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ===============
|
|
||||||
// === Shaders ===
|
|
||||||
// ===============
|
|
||||||
|
|
||||||
/// Build optimized shaders by using `glslc`, `spirv-opt` and `spirv-cross`.
|
|
||||||
async fn build_shader(
|
|
||||||
input_dir: &Path,
|
|
||||||
input_files: &[String],
|
|
||||||
output_dir: &Path,
|
|
||||||
work_dir: &Path,
|
|
||||||
) -> Result<()> {
|
|
||||||
ide_ci::fs::tokio::create_dir_if_missing(work_dir).await?;
|
|
||||||
info!("Optimizing `{}`.", input_dir.file_name().unwrap_or_default().to_string_lossy());
|
|
||||||
for glsl_file_name in input_files {
|
|
||||||
let glsl_path = input_dir.join(glsl_file_name);
|
|
||||||
let work_path = work_dir.join(glsl_file_name);
|
|
||||||
let stage_path = work_path.with_extension("");
|
|
||||||
let stage =
|
|
||||||
stage_path.file_name().ok_or_else(|| anyhow!("Empty stage path."))?.to_string_lossy();
|
|
||||||
let spv_path = stage_path.with_appended_extension("spv");
|
|
||||||
let spv_opt_path = stage_path.with_appended_extension("opt.spv");
|
|
||||||
let glsl_opt_path = stage_path.with_appended_extension("opt.glsl");
|
|
||||||
let glsl_opt_dist_path = output_dir.join(glsl_file_name);
|
|
||||||
let spv_path = spv_path.as_str();
|
|
||||||
let glsl_path = glsl_path.as_str();
|
|
||||||
let shader_stage = &format!("-fshader-stage={stage}");
|
|
||||||
let glslc_args = ["--target-env=opengl", shader_stage, "-o", spv_path, glsl_path];
|
|
||||||
let spirv_opt_args = ["-O", "-o", spv_opt_path.as_str(), spv_path.as_str()];
|
|
||||||
let spirv_cross_args = ["--output", glsl_opt_path.as_str(), spv_opt_path.as_str()];
|
|
||||||
Glslc.cmd()?.args(glslc_args).run_ok().await?;
|
|
||||||
SpirvOpt.cmd()?.args(spirv_opt_args).run_ok().await?;
|
|
||||||
SpirvCross.cmd()?.args(spirv_cross_args).run_ok().await?;
|
|
||||||
|
|
||||||
let content =
|
|
||||||
ide_ci::fs::tokio::read_to_string(&glsl_opt_path).await?.replace("\r\n", "\n");
|
|
||||||
let extract_err = || format!("Failed to process shader '{}'.", glsl_opt_path.as_str());
|
|
||||||
let code = extract_main_shader_code(&content).with_context(extract_err)?;
|
|
||||||
ide_ci::fs::tokio::write(&glsl_opt_dist_path, code).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the optimized shader code, extract the main function body and preserve all top-level
|
|
||||||
/// variable declarations.
|
|
||||||
fn extract_main_shader_code(code: &str) -> Result<String> {
|
|
||||||
let main_start_str = "void main()\n{";
|
|
||||||
let main_end_str = "}";
|
|
||||||
let main_fn_find_err = "Failed to find main function.";
|
|
||||||
let main_start = code.find(main_start_str).with_context(|| main_fn_find_err)?;
|
|
||||||
let main_end = code.rfind(main_end_str).with_context(|| main_fn_find_err)?;
|
|
||||||
let before_main = &code[..main_start];
|
|
||||||
let declarations: Vec<&str> = before_main
|
|
||||||
.lines()
|
|
||||||
.filter_map(|line| {
|
|
||||||
let version_def = line.starts_with("#version ");
|
|
||||||
let precision_def = line.starts_with("precision ");
|
|
||||||
let layout_def = line.starts_with("layout(");
|
|
||||||
let def = version_def || precision_def || layout_def;
|
|
||||||
(!def).then_some(line)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let declarations = declarations.join("\n");
|
|
||||||
let main_content = &code[main_start + main_start_str.len()..main_end];
|
|
||||||
Ok(format!("{declarations}\n{main_content}"))
|
|
||||||
}
|
|
@ -1,464 +0,0 @@
|
|||||||
//! EnsoGL Pack compiles Rust sources, builds the dynamic assets of the EnsoGL app (including
|
|
||||||
//! optimized shaders and pre-seeded caches), and outputs the JS WASM loader, additional JS runtime
|
|
||||||
//! utilities, and a set of optimized dynamic assets. It is a wrapper for `wasm-pack` tool.
|
|
||||||
//!
|
|
||||||
//! # Compilation process.
|
|
||||||
//! When run, the following file tree will be created/used. The files/directories marked with '*'
|
|
||||||
//! are required to be included with your final application code. The files marked with '**' are
|
|
||||||
//! recommended to be included.
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! workspace | The main workspace directory (repo root).
|
|
||||||
//! ├─ ... / this_crate | This crate's directory.
|
|
||||||
//! │ ╰─ js | This crate's JS sources.
|
|
||||||
//! │ ├─ runner | Runner of WASM app. Used as a package by the app.
|
|
||||||
//! │ ├─ runtime-libs | Additional libs bundled with app. E.g. SpectorJS.
|
|
||||||
//! │ ├─ shader-extractor | App to extract shaders from WASM.
|
|
||||||
//! │ ╰─ wasm-pack-bundle | Glue for `wasm-pack` artifacts.
|
|
||||||
//! │ ╰─ index.ts | Copied to `target/ensogl-pack/wasm-pack/index.ts`.
|
|
||||||
//! ╰─ target | Directory where Rust and wasm-pack store build artifacts.
|
|
||||||
//! ╰─ ensogl-pack | Directory where ensogl-pack stores its build artifacts.
|
|
||||||
//! ├─ wasm-pack | Wasm-pack artifacts, re-created on every run.
|
|
||||||
//! │ ├─ pkg.js | Wasm-pack JS file to load WASM and glue it with snippets.
|
|
||||||
//! │ ├─ pkg_bg.wasm | Wasm-pack WASM bundle.
|
|
||||||
//! │ ├─ index.ts | Main file, copied from `this_crate/js/wasm-pack-bundle`.
|
|
||||||
//! │ ├─ runtime-libs.js | Bundled `this_crate/js/runtime-libs`.
|
|
||||||
//! │ ╰─ snippets | Rust-extracted JS snippets.
|
|
||||||
//! │ ╰─ <name>.js | A single Rust-extracted JS snippet.
|
|
||||||
//! ├─ dynamic-assets | Dynamic asset sources extracted from WASM bundle.
|
|
||||||
//! │ ├─ shader | Pre-compiled shaders.
|
|
||||||
//! │ │ ├─ <key> | Asset sources (the GLSL file).
|
|
||||||
//! │ │ ├─ <key>.work | Intermediate files produced by the shader compiler.
|
|
||||||
//! │ │ ╰─ ...
|
|
||||||
//! │ ├─ font | Pre-generated MSDF data.
|
|
||||||
//! │ │ ├─ <key> | Asset sources (the glyph atlas image, and metadata).
|
|
||||||
//! │ │ ╰─ ...
|
|
||||||
//! │ ╰─ <type>...
|
|
||||||
//! ├─ runtime-libs
|
|
||||||
//! │ ╰─ runtime-libs.js
|
|
||||||
//! ╰─ dist | Final build artifacts of ensogl-pack.
|
|
||||||
//! * ├─ index.js | The main JS bundle to load WASM and JS wasm-pack bundles.
|
|
||||||
//! ├─ index.js.map | The sourcemap mapping to sources in TypeScript.
|
|
||||||
//! ** ├─ index.d.ts | TypeScript types interface file.
|
|
||||||
//! ├─ asset-extractor.cjs | Node program to extract asset sources from WASM.
|
|
||||||
//! ├─ asset-extractor.cjs.map | The sourcemap mapping to sources in TypeScript.
|
|
||||||
//! * ├─ pkg.js | The `pks.js` artifact of wasm-pack WITH bundled snippets.
|
|
||||||
//! ├─ pkg.js.map | The sourcemap mapping to `pkg.js` generated by wasm-pack.
|
|
||||||
//! * ├─ pkg.wasm | The `pks_bg.wasm` artifact of wasm-pack.
|
|
||||||
//! * ╰─ dynamic-assets | Built dynamic assets.
|
|
||||||
//! ├─ manifest.json | An index of all the assets and their files.
|
|
||||||
//! ├─ shader | Pre-compiled shaders.
|
|
||||||
//! │ ├─ <key> | A subdirectory for each asset.
|
|
||||||
//! │ ╰─ ...
|
|
||||||
//! ├─ font | Pre-generated MSDF data.
|
|
||||||
//! │ ├─ <key> | A subdirectory for each asset.
|
|
||||||
//! │ ╰─ ...
|
|
||||||
//! ╰─ <type>...
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! The high-level app compilation process is summarized below:
|
|
||||||
//!
|
|
||||||
//! 1. If the `dist/index.js` file does not exist, or its modification date is older than
|
|
||||||
//! `this_crate/js` sources:
|
|
||||||
//!
|
|
||||||
//! 1. `npm install` is assumed to have been already run in the `this_crate/js` directory.
|
|
||||||
//!
|
|
||||||
//! 2. The `this_crate/js/runner` is compiled to `target/ensogl-pack/dist/index.cjs`. This is the
|
|
||||||
//! main file which is capable of loading WASM file, displaying a loading screen, running
|
|
||||||
//! before-main entry points, and running the main entry point of the application.
|
|
||||||
//!
|
|
||||||
//! 3. The `this_crate/js/shader-extractor` is compiled to
|
|
||||||
//! `target/ensogl-pack/dist/shader-extractor.js`. This is a node program that extracts
|
|
||||||
//! non-optimized shaders from the WASM file.
|
|
||||||
//!
|
|
||||||
//! 4. The `this_crate/js/runtime-libs` is compiled to
|
|
||||||
//! `target/ensogl-pack/runtime-libs/runtime-libs.js`. This is a bundle containing additional JS
|
|
||||||
//! libs, such as SpectorJS.
|
|
||||||
//!
|
|
||||||
//! 2. The rust sources are build with `wasm-pack`, which produces the following artifacts:
|
|
||||||
//! `target/ensogl-pack/wasm-pack/{pkg.js, pkg_bg.wasm, snippets}`. The file `pkg_bg.wasm` is copied
|
|
||||||
//! to `target/ensogl-pack/dist/pkg.wasm`.
|
|
||||||
//!
|
|
||||||
//! 3. The file `this_crate/js/wasm-pack-bundle/index.ts` is copied to
|
|
||||||
//! `target/ensogl-pack/wasm-pack/index.ts`. This is the main file which when compiled glues
|
|
||||||
//! `pkg.js`, `snippets`, and `runtime-libs.js` into a single bundle.
|
|
||||||
//!
|
|
||||||
//! 4. The program `target/ensogl-pack/dist/asset-extractor.cjs` is run. It loads
|
|
||||||
//! `target/dist/pkg.wasm` and writes asset sources to `target/ensogl-pack/dynamic-assets`.
|
|
||||||
//!
|
|
||||||
//! 5. For each asset, its inputs are hashed and an output directory is determined based on its
|
|
||||||
//! name and input hash. If the output directory doesn't already exist, the asset is built, and the
|
|
||||||
//! result is written to `dist/dynamic-assets`. The manifest is rebuilt to reflect the current set
|
|
||||||
//! of asset outputs, and any outdated output directories are removed.
|
|
||||||
//!
|
|
||||||
//! 6. The `target/ensogl-pack/wasm-pack/index.ts` is compiled to
|
|
||||||
//! `target/ensogl-pack/dist/index.js`.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! # Runtime process.
|
|
||||||
//! When `target/dist/index.js` is run:
|
|
||||||
//!
|
|
||||||
//! 1. The following files are downloaded from a server:
|
|
||||||
//! `target/dist/{pkg.js, pkg.wasm, dynamic-assets}`.
|
|
||||||
//! 2. The code from `pkg.js` is run to compile the WASM file.
|
|
||||||
//! 3. All before-main entry points are run.
|
|
||||||
//! 4. Optimized shaders are uploaded to the EnsoGL application.
|
|
||||||
//! 5. The main entry point is run.
|
|
||||||
|
|
||||||
// === Features ===
|
|
||||||
#![feature(async_closure)]
|
|
||||||
#![feature(fs_try_exists)]
|
|
||||||
// === Standard Linter Configuration ===
|
|
||||||
#![deny(non_ascii_idents)]
|
|
||||||
#![warn(unsafe_code)]
|
|
||||||
#![allow(clippy::bool_to_int_with_if)]
|
|
||||||
#![allow(clippy::let_and_return)]
|
|
||||||
// === Non-Standard Linter Configuration ===
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
|
|
||||||
use ide_ci::prelude::*;
|
|
||||||
|
|
||||||
use ide_ci::program::EMPTY_ARGS;
|
|
||||||
use ide_ci::programs::wasm_pack::WasmPackCommand;
|
|
||||||
use manifest_dir_macros::path;
|
|
||||||
use std::env;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// === Export ===
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
pub mod assets;
|
|
||||||
|
|
||||||
pub use ide_ci::prelude;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Hot Fixes ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/// A hot-fix for a bug on macOS, where `std::fs::copy` causes cargo-watch to loop infinitely.
|
|
||||||
/// See: https://github.com/watchexec/cargo-watch/issues/242
|
|
||||||
pub fn copy(source_file: impl AsRef<Path>, destination_file: impl AsRef<Path>) -> Result {
|
|
||||||
if env::consts::OS == "macos" {
|
|
||||||
Command::new("cp").arg(source_file.as_ref()).arg(destination_file.as_ref()).spawn()?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
ide_ci::fs::copy(source_file, destination_file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Paths ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/// Paths of the directories and files used by `ensogl-pack`. This struct maps to variables the
|
|
||||||
/// directory layout described in the docs of this module.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub struct Paths {
|
|
||||||
pub workspace: PathBuf,
|
|
||||||
pub this_crate: paths::ThisCrate,
|
|
||||||
pub target: paths::Target,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! define_paths {
|
|
||||||
($(
|
|
||||||
$name:ident {
|
|
||||||
$($field:ident : $field_ty:ty),* $(,)?
|
|
||||||
}
|
|
||||||
)*) => {$(
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub struct $name {
|
|
||||||
pub root: PathBuf,
|
|
||||||
$( pub $field: $field_ty ),*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for $name {
|
|
||||||
type Target = PathBuf;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.root
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<std::path::Path> for $name {
|
|
||||||
fn as_ref(&self) -> &std::path::Path {
|
|
||||||
&self.root
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<OsStr> for $name {
|
|
||||||
fn as_ref(&self) -> &OsStr {
|
|
||||||
self.root.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Paths used during build.
|
|
||||||
pub mod paths {
|
|
||||||
use super::*;
|
|
||||||
define_paths! {
|
|
||||||
ThisCrate {
|
|
||||||
js: ThisCrateJs,
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisCrateJs {
|
|
||||||
wasm_pack_bundle: ThisCrateJsWasmPackBundle,
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisCrateJsWasmPackBundle {
|
|
||||||
index: PathBuf
|
|
||||||
}
|
|
||||||
|
|
||||||
Target {
|
|
||||||
enso_pack: TargetEnsoglPack,
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetEnsoglPack {
|
|
||||||
wasm_pack: TargetEnsoglPackWasmPack,
|
|
||||||
dynamic_assets: PathBuf,
|
|
||||||
runtime_libs: TargetEnsoglPackRuntimeLibs,
|
|
||||||
dist: TargetEnsoglPackDist,
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetEnsoglPackRuntimeLibs {
|
|
||||||
runtime_libs: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetEnsoglPackWasmPack {
|
|
||||||
index: PathBuf,
|
|
||||||
pkg_bg: PathBuf,
|
|
||||||
pkg_js: PathBuf,
|
|
||||||
runtime_libs: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetEnsoglPackDist {
|
|
||||||
asset_extractor: PathBuf,
|
|
||||||
pkg_js: PathBuf,
|
|
||||||
main_wasm: PathBuf,
|
|
||||||
dynamic_assets: TargetEnsoglPackDistDynamicAssets,
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetEnsoglPackDistDynamicAssets {
|
|
||||||
manifest: PathBuf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const WASM_PACK_OUT_NAME: &str = "pkg";
|
|
||||||
|
|
||||||
impl Paths {
|
|
||||||
/// Create a set of paths values.
|
|
||||||
pub async fn new() -> Result<Self> {
|
|
||||||
let mut p = Paths::default();
|
|
||||||
let current_cargo_path = Path::new(path!("Cargo.toml"));
|
|
||||||
p.this_crate.root = current_cargo_path.try_parent()?.into();
|
|
||||||
p.this_crate.js.root = p.this_crate.join("js");
|
|
||||||
p.this_crate.js.wasm_pack_bundle.root =
|
|
||||||
p.this_crate.js.root.join("src").join("wasm-pack-bundle");
|
|
||||||
p.this_crate.js.wasm_pack_bundle.index = p.this_crate.js.wasm_pack_bundle.join("index.ts");
|
|
||||||
p.workspace = workspace_dir().await?;
|
|
||||||
p.target.root = p.workspace.join("target");
|
|
||||||
p.target.enso_pack.root = p.target.join("ensogl-pack");
|
|
||||||
p.target.enso_pack.wasm_pack.root = p.target.enso_pack.join("wasm-pack");
|
|
||||||
let pkg_wasm = format!("{WASM_PACK_OUT_NAME}_bg.wasm");
|
|
||||||
let pkg_js = format!("{WASM_PACK_OUT_NAME}.js");
|
|
||||||
p.target.enso_pack.wasm_pack.index = p.target.enso_pack.wasm_pack.join("index.ts");
|
|
||||||
p.target.enso_pack.wasm_pack.pkg_bg = p.target.enso_pack.wasm_pack.join(pkg_wasm);
|
|
||||||
p.target.enso_pack.wasm_pack.pkg_js = p.target.enso_pack.wasm_pack.join(pkg_js);
|
|
||||||
p.target.enso_pack.wasm_pack.runtime_libs =
|
|
||||||
p.target.enso_pack.wasm_pack.join("runtime-libs.js");
|
|
||||||
p.target.enso_pack.dynamic_assets = p.target.enso_pack.join("dynamic-assets");
|
|
||||||
p.target.enso_pack.runtime_libs.root = p.target.enso_pack.join("runtime-libs");
|
|
||||||
p.target.enso_pack.runtime_libs.runtime_libs =
|
|
||||||
p.target.enso_pack.runtime_libs.join("runtime-libs.js");
|
|
||||||
p.target.enso_pack.dist.root = p.target.enso_pack.join("dist");
|
|
||||||
p.target.enso_pack.dist.asset_extractor =
|
|
||||||
p.target.enso_pack.dist.join("asset-extractor.cjs");
|
|
||||||
p.target.enso_pack.dist.pkg_js = p.target.enso_pack.dist.join("pkg.js");
|
|
||||||
p.target.enso_pack.dist.main_wasm = p.target.enso_pack.dist.join("pkg.wasm");
|
|
||||||
p.target.enso_pack.dist.dynamic_assets.root =
|
|
||||||
p.target.enso_pack.dist.join("dynamic-assets");
|
|
||||||
p.target.enso_pack.dist.dynamic_assets.manifest =
|
|
||||||
p.target.enso_pack.dist.dynamic_assets.join("manifest.json");
|
|
||||||
Ok(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the workspace directory (repo root).
|
|
||||||
pub async fn workspace_dir() -> Result<PathBuf> {
|
|
||||||
use ide_ci::programs::cargo;
|
|
||||||
use ide_ci::programs::Cargo;
|
|
||||||
let output = Cargo
|
|
||||||
.cmd()?
|
|
||||||
.apply(&cargo::Command::LocateProject)
|
|
||||||
.apply(&cargo::LocateProjectOption::Workspace)
|
|
||||||
.apply(&cargo::LocateProjectOption::MessageFormat(cargo::MessageFormat::Plain))
|
|
||||||
.output_ok()
|
|
||||||
.await?
|
|
||||||
.into_stdout_string()?;
|
|
||||||
let cargo_path = Path::new(output.trim());
|
|
||||||
Ok(cargo_path.try_parent()?.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Build ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/// The arguments to `wasm-pack build` that `ensogl-pack` wants to customize.
|
|
||||||
pub struct WasmPackOutputs {
|
|
||||||
/// Value to passed as `--out-dir` to `wasm-pack`.
|
|
||||||
pub out_dir: PathBuf,
|
|
||||||
/// Value to passed as `--out-name` to `wasm-pack`.
|
|
||||||
pub out_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the modification time of all files in this crate's `js` directory and compare them with
|
|
||||||
/// the modification time of dist artifacts, if any. Do not traverse `node_modules` directory.
|
|
||||||
fn check_if_ts_needs_rebuild(paths: &Paths) -> Result<bool> {
|
|
||||||
let walk = WalkDir::new(&paths.this_crate.js).into_iter();
|
|
||||||
let walk_no_node_modules = walk.filter_entry(|e| e.file_name() != "node_modules");
|
|
||||||
let mut newest_mod_time: Option<std::time::SystemTime> = None;
|
|
||||||
for opt_entry in walk_no_node_modules {
|
|
||||||
let entry = opt_entry?;
|
|
||||||
if entry.file_type().is_file() {
|
|
||||||
let metadata = entry.metadata()?;
|
|
||||||
let mod_time = metadata.modified()?;
|
|
||||||
newest_mod_time = Some(newest_mod_time.map_or(mod_time, |t| t.max(mod_time)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Ok(app_js_metadata) = std::fs::metadata(&paths.target.enso_pack.dist.asset_extractor) {
|
|
||||||
let app_js_mod_time = app_js_metadata.modified()?;
|
|
||||||
Ok(newest_mod_time.map_or(true, |t| t > app_js_mod_time))
|
|
||||||
} else {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compile TypeScript sources of this crate in case they were not compiled yet.
|
|
||||||
pub async fn compile_this_crate_ts_sources(paths: &Paths) -> Result<()> {
|
|
||||||
println!("compile_this_crate_ts_sources");
|
|
||||||
if check_if_ts_needs_rebuild(paths)? {
|
|
||||||
info!("EnsoGL Pack TypeScript sources changed, recompiling.");
|
|
||||||
let run_script = async move |script_name, script_args: &[&str]| {
|
|
||||||
ide_ci::programs::Npm
|
|
||||||
.cmd()?
|
|
||||||
.run(script_name)
|
|
||||||
.args(script_args)
|
|
||||||
.current_dir(&paths.this_crate.js)
|
|
||||||
.run_ok()
|
|
||||||
.await
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("Linting TypeScript sources.");
|
|
||||||
run_script("lint", &EMPTY_ARGS).await?;
|
|
||||||
|
|
||||||
info!("Building TypeScript sources.");
|
|
||||||
let args = ["--", &format!("--out-dir={}", paths.target.enso_pack.dist.display())];
|
|
||||||
run_script("build-asset-extractor", &args).await?;
|
|
||||||
println!("BUILD build-runtime-libs");
|
|
||||||
let args = ["--", &format!("--outdir={}", paths.target.enso_pack.runtime_libs.display())];
|
|
||||||
run_script("build-runtime-libs", &args).await?;
|
|
||||||
} else {
|
|
||||||
println!("NO BUILD");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run wasm-pack to build the wasm artifact.
|
|
||||||
#[context("Failed to run wasm-pack.")]
|
|
||||||
pub async fn run_wasm_pack(
|
|
||||||
paths: &Paths,
|
|
||||||
provider: impl FnOnce(WasmPackOutputs) -> Result<WasmPackCommand>,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("Obtaining and running the wasm-pack command.");
|
|
||||||
let replaced_args = WasmPackOutputs {
|
|
||||||
out_dir: paths.target.enso_pack.wasm_pack.root.clone(),
|
|
||||||
out_name: WASM_PACK_OUT_NAME.to_string(),
|
|
||||||
};
|
|
||||||
let mut command = provider(replaced_args).context("Failed to obtain wasm-pack command.")?;
|
|
||||||
command.run_ok().await?;
|
|
||||||
|
|
||||||
copy(&paths.this_crate.js.wasm_pack_bundle.index, &paths.target.enso_pack.wasm_pack.index)?;
|
|
||||||
copy(
|
|
||||||
&paths.target.enso_pack.runtime_libs.runtime_libs,
|
|
||||||
&paths.target.enso_pack.wasm_pack.runtime_libs,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
compile_wasm_pack_artifacts(
|
|
||||||
&paths.target.enso_pack.wasm_pack,
|
|
||||||
&paths.target.enso_pack.wasm_pack.index,
|
|
||||||
&paths.target.enso_pack.dist.pkg_js,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
ide_ci::fs::copy(
|
|
||||||
&paths.target.enso_pack.wasm_pack.pkg_bg,
|
|
||||||
&paths.target.enso_pack.dist.main_wasm,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compile wasm-pack artifacts (JS sources and snippets) to a single bundle.
|
|
||||||
async fn compile_wasm_pack_artifacts(pwd: &Path, pkg_js: &Path, out: &Path) -> Result {
|
|
||||||
info!("Compiling {}.", pkg_js.display());
|
|
||||||
ide_ci::programs::Npx
|
|
||||||
.cmd()?
|
|
||||||
.args([
|
|
||||||
"--yes",
|
|
||||||
"esbuild",
|
|
||||||
pkg_js.display().to_string().as_str(),
|
|
||||||
"--format=cjs",
|
|
||||||
"--bundle",
|
|
||||||
"--sourcemap",
|
|
||||||
"--platform=node",
|
|
||||||
&format!("--outfile={}", out.display()),
|
|
||||||
])
|
|
||||||
.current_dir(pwd)
|
|
||||||
.run_ok()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract asset sources from the WASM artifact.
|
|
||||||
async fn extract_assets(paths: &Paths) -> Result<()> {
|
|
||||||
info!("Extracting asset sources from generated WASM file.");
|
|
||||||
ide_ci::programs::Node
|
|
||||||
.cmd()?
|
|
||||||
.arg(&paths.target.enso_pack.dist.asset_extractor)
|
|
||||||
.arg("--out-dir")
|
|
||||||
.arg(&paths.target.enso_pack.dynamic_assets)
|
|
||||||
.run_ok()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Just builds the TypeScript sources.
|
|
||||||
pub async fn build_ts_sources_only() -> Result {
|
|
||||||
let paths = Paths::new().await?;
|
|
||||||
compile_this_crate_ts_sources(&paths).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper over `wasm-pack build` command.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `outputs` - The outputs that'd be usually given to `wasm-pack build` command.
|
|
||||||
/// * `provider` - Function that generates an invocation of the `wasm-pack build` command that has
|
|
||||||
/// applied given (customized) output-related arguments.
|
|
||||||
pub async fn build(
|
|
||||||
outputs: WasmPackOutputs,
|
|
||||||
provider: impl FnOnce(WasmPackOutputs) -> Result<WasmPackCommand>,
|
|
||||||
) -> Result {
|
|
||||||
let paths = Paths::new().await?;
|
|
||||||
compile_this_crate_ts_sources(&paths).await?;
|
|
||||||
run_wasm_pack(&paths, provider).await?;
|
|
||||||
extract_assets(&paths).await?;
|
|
||||||
assets::build(&paths).await?;
|
|
||||||
let out_dir = Path::new(&outputs.out_dir);
|
|
||||||
ide_ci::fs::copy(&paths.target.enso_pack.dist, out_dir)
|
|
||||||
}
|
|
346
package-lock.json
generated
346
package-lock.json
generated
@ -8,8 +8,8 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"app/ide-desktop",
|
"app/ide-desktop",
|
||||||
"app/ide-desktop/lib/*",
|
"app/ide-desktop/lib/*",
|
||||||
"app/gui2",
|
"lib/js/runner",
|
||||||
"lib/rust/enso-pack/js"
|
"app/gui2"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chromedriver": "^106.0.1",
|
"chromedriver": "^106.0.1",
|
||||||
@ -190,6 +190,8 @@
|
|||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
"fast-glob": "^3.2.12",
|
"fast-glob": "^3.2.12",
|
||||||
"portfinder": "^1.0.32",
|
"portfinder": "^1.0.32",
|
||||||
|
"sharp": "^0.31.2",
|
||||||
|
"to-ico": "^1.1.5",
|
||||||
"tsx": "^4.7.1"
|
"tsx": "^4.7.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
@ -206,6 +208,7 @@
|
|||||||
"app/ide-desktop/lib/content": {
|
"app/ide-desktop/lib/content": {
|
||||||
"name": "enso-content",
|
"name": "enso-content",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"extraneous": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/semver": "^7.3.9",
|
"@types/semver": "^7.3.9",
|
||||||
"enso-content-config": "^1.0.0",
|
"enso-content-config": "^1.0.0",
|
||||||
@ -286,6 +289,7 @@
|
|||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
"esbuild-plugin-inline-image": "^0.0.9",
|
"esbuild-plugin-inline-image": "^0.0.9",
|
||||||
"esbuild-plugin-time": "^1.0.0",
|
"esbuild-plugin-time": "^1.0.0",
|
||||||
|
"esbuild-plugin-yaml": "^0.0.1",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.49.0",
|
||||||
"eslint-plugin-jsdoc": "^46.8.1",
|
"eslint-plugin-jsdoc": "^46.8.1",
|
||||||
"eslint-plugin-react": "^7.32.1",
|
"eslint-plugin-react": "^7.32.1",
|
||||||
@ -316,10 +320,56 @@
|
|||||||
"name": "enso-icons",
|
"name": "enso-icons",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/sharp": "^0.31.1",
|
||||||
|
"@types/to-ico": "^1.1.1",
|
||||||
"sharp": "^0.31.2",
|
"sharp": "^0.31.2",
|
||||||
"to-ico": "^1.1.5"
|
"to-ico": "^1.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app/ide-desktop/lib/js": {
|
||||||
|
"name": "enso-runner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"extraneous": true,
|
||||||
|
"dependencies": {
|
||||||
|
"spectorjs": "^0.9.27"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||||
|
"@typescript-eslint/parser": "^6.7.2",
|
||||||
|
"esbuild": "^0.19.3",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-jsdoc": "^46.8.1",
|
||||||
|
"tsup": "^7.2.0",
|
||||||
|
"typescript": "~5.2.2"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"esbuild-darwin-64": "^0.15.18",
|
||||||
|
"esbuild-linux-64": "^0.15.18",
|
||||||
|
"esbuild-windows-64": "^0.15.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/lib/runner": {
|
||||||
|
"name": "enso-runner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"extraneous": true,
|
||||||
|
"dependencies": {
|
||||||
|
"spectorjs": "^0.9.27"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||||
|
"@typescript-eslint/parser": "^6.7.2",
|
||||||
|
"esbuild": "^0.19.3",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-jsdoc": "^46.8.1",
|
||||||
|
"tsup": "^7.2.0",
|
||||||
|
"typescript": "~5.2.2"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"esbuild-darwin-64": "^0.15.18",
|
||||||
|
"esbuild-linux-64": "^0.15.18",
|
||||||
|
"esbuild-windows-64": "^0.15.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"app/ide-desktop/lib/ts-plugin-namespace-auto-import": {
|
"app/ide-desktop/lib/ts-plugin-namespace-auto-import": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -368,6 +418,69 @@
|
|||||||
"zen-observable-ts": "0.8.19"
|
"zen-observable-ts": "0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app/ide-desktop/node_modules/@eslint/eslintrc": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^6.12.4",
|
||||||
|
"debug": "^4.3.2",
|
||||||
|
"espree": "^9.6.0",
|
||||||
|
"globals": "^13.19.0",
|
||||||
|
"ignore": "^5.2.0",
|
||||||
|
"import-fresh": "^3.2.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
"strip-json-comments": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/@eslint/eslintrc/node_modules/ajv": {
|
||||||
|
"version": "6.12.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/@eslint/js": {
|
||||||
|
"version": "8.49.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
|
||||||
|
"integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/@types/sharp": {
|
||||||
|
"version": "0.31.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz",
|
||||||
|
"integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"app/ide-desktop/node_modules/ajv": {
|
"app/ide-desktop/node_modules/ajv": {
|
||||||
"version": "8.12.0",
|
"version": "8.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
@ -395,6 +508,143 @@
|
|||||||
"js-cookie": "^2.2.1"
|
"js-cookie": "^2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app/ide-desktop/node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/eslint": {
|
||||||
|
"version": "8.49.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
|
||||||
|
"integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
|
"@eslint/eslintrc": "^2.1.2",
|
||||||
|
"@eslint/js": "8.49.0",
|
||||||
|
"@humanwhocodes/config-array": "^0.11.11",
|
||||||
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"ajv": "^6.12.4",
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"cross-spawn": "^7.0.2",
|
||||||
|
"debug": "^4.3.2",
|
||||||
|
"doctrine": "^3.0.0",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"eslint-scope": "^7.2.2",
|
||||||
|
"eslint-visitor-keys": "^3.4.3",
|
||||||
|
"espree": "^9.6.1",
|
||||||
|
"esquery": "^1.4.2",
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"file-entry-cache": "^6.0.1",
|
||||||
|
"find-up": "^5.0.0",
|
||||||
|
"glob-parent": "^6.0.2",
|
||||||
|
"globals": "^13.19.0",
|
||||||
|
"graphemer": "^1.4.0",
|
||||||
|
"ignore": "^5.2.0",
|
||||||
|
"imurmurhash": "^0.1.4",
|
||||||
|
"is-glob": "^4.0.0",
|
||||||
|
"is-path-inside": "^3.0.3",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||||
|
"levn": "^0.4.1",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
"natural-compare": "^1.4.0",
|
||||||
|
"optionator": "^0.9.3",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"text-table": "^0.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"eslint": "bin/eslint.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/eslint/node_modules/ajv": {
|
||||||
|
"version": "6.12.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/eslint/node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/eslint/node_modules/json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"app/ide-desktop/node_modules/glob-parent": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-glob": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"app/ide-desktop/node_modules/json-schema-traverse": {
|
"app/ide-desktop/node_modules/json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
@ -405,9 +655,31 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
|
"lib/js/runner": {
|
||||||
|
"name": "enso-runner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"spectorjs": "^0.9.27"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||||
|
"@typescript-eslint/parser": "^6.7.2",
|
||||||
|
"esbuild": "^0.19.3",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-jsdoc": "^46.8.1",
|
||||||
|
"tsup": "^7.2.0",
|
||||||
|
"typescript": "~5.2.2"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"esbuild-darwin-64": "^0.15.18",
|
||||||
|
"esbuild-linux-64": "^0.15.18",
|
||||||
|
"esbuild-windows-64": "^0.15.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"lib/rust/enso-pack/js": {
|
"lib/rust/enso-pack/js": {
|
||||||
"name": "enso-runner",
|
"name": "enso-runner",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"extraneous": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"spectorjs": "^0.9.27"
|
"spectorjs": "^0.9.27"
|
||||||
},
|
},
|
||||||
@ -1966,15 +2238,6 @@
|
|||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild-plugins/node-globals-polyfill": {
|
|
||||||
"version": "0.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz",
|
|
||||||
"integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==",
|
|
||||||
"dev": true,
|
|
||||||
"peerDependencies": {
|
|
||||||
"esbuild": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-plugins/node-modules-polyfill": {
|
"node_modules/@esbuild-plugins/node-modules-polyfill": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz",
|
||||||
@ -3592,15 +3855,6 @@
|
|||||||
"@types/responselike": "^1.0.0"
|
"@types/responselike": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/connect": {
|
|
||||||
"version": "3.4.38",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
|
||||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/cookie": {
|
"node_modules/@types/cookie": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
||||||
@ -3936,12 +4190,6 @@
|
|||||||
"integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
|
"integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/http-errors": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/istanbul-lib-coverage": {
|
"node_modules/@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||||
@ -4005,12 +4253,6 @@
|
|||||||
"integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==",
|
"integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/mime": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/mime-types": {
|
"node_modules/@types/mime-types": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
|
||||||
@ -4023,15 +4265,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/morgan": {
|
|
||||||
"version": "1.9.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.9.tgz",
|
|
||||||
"integrity": "sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
"version": "0.7.34",
|
"version": "0.7.34",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||||
@ -4130,27 +4363,8 @@
|
|||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.8",
|
"version": "7.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="
|
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||||
},
|
"dev": true
|
||||||
"node_modules/@types/serve-static": {
|
|
||||||
"version": "1.15.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
|
|
||||||
"integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/http-errors": "*",
|
|
||||||
"@types/mime": "*",
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/sharp": {
|
|
||||||
"version": "0.31.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz",
|
|
||||||
"integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/shuffle-seed": {
|
"node_modules/@types/shuffle-seed": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
@ -6556,6 +6770,7 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
@ -8427,10 +8642,6 @@
|
|||||||
"resolved": "app/ide-desktop/lib/common",
|
"resolved": "app/ide-desktop/lib/common",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/enso-content": {
|
|
||||||
"resolved": "app/ide-desktop/lib/content",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/enso-content-config": {
|
"node_modules/enso-content-config": {
|
||||||
"resolved": "app/ide-desktop/lib/content-config",
|
"resolved": "app/ide-desktop/lib/content-config",
|
||||||
"link": true
|
"link": true
|
||||||
@ -8452,7 +8663,7 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/enso-runner": {
|
"node_modules/enso-runner": {
|
||||||
"resolved": "lib/rust/enso-pack/js",
|
"resolved": "lib/js/runner",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/entities": {
|
"node_modules/entities": {
|
||||||
@ -14903,6 +15114,7 @@
|
|||||||
"version": "9.1.3",
|
"version": "9.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz",
|
||||||
"integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==",
|
"integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clsx": "^1.1.1"
|
"clsx": "^1.1.1"
|
||||||
},
|
},
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"app/ide-desktop",
|
"app/ide-desktop",
|
||||||
"app/ide-desktop/lib/*",
|
"app/ide-desktop/lib/*",
|
||||||
"app/gui2",
|
"lib/js/runner",
|
||||||
"lib/rust/enso-pack/js"
|
"app/gui2"
|
||||||
],
|
],
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"tslib": "$tslib"
|
"tslib": "$tslib"
|
||||||
|
Loading…
Reference in New Issue
Block a user