tswow/tswow-scripts/runtime/Datascripts.ts

418 lines
No EOL
14 KiB
TypeScript

import { watchTsc } from "../util/CompileTS";
import { ConfigFile, Property, Section } from "../util/ConfigFile";
import { wfs } from "../util/FileSystem";
import { ipaths } from "../util/Paths";
import { wsys } from "../util/System";
import { term } from "../util/Terminal";
import { termCustom } from "../util/TerminalCategories";
import { termc } from "../util/TerminalColors";
import { BuildCommand, CleanCommand, ListCommand } from "./CommandActions";
import { Dataset } from "./Dataset";
import { Identifier } from "./Identifiers";
import { Module, ModuleEndpoint } from "./Modules";
import { NodeConfig } from "./NodeConfig";
/**
* The example patch file that will be written to the 'datascripts' directory of new modules.
*/
const patch_example_ts = (name: string) =>
`import { std } from "wow/wotlk";
console.log("Hello from ${name} data script!");`;
/**
* The default package.json that will be written to datascript build directory of new modules.
*/
const lib_package_json =
(name: string) => ({
'name': name,
'version': '1.0.0',
'description': '',
'main': `datascripts.js`,
'types': `datascripts.d.ts`,
'dependencies': {
},
'devDependencies': {},
'scripts': {},
});
/**
* The tsconfig.json that will be used to compile 'datascript' directories
*/
const data_tsconfig =
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"outDir": "./build",
"rootDir": "./",
"emitDeclarationOnly": true,
"strict": true,
"esModuleInterop": true,
"declaration": true,
"skipLibCheck": true,
"incremental": true,
"allowJs": false,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true
},
"exclude":["**/build/**","**/tswow/wotlkdata/**"]
};
const datascripts_swcrc = {
"module": {
"type":"commonjs"
},
"exclude":[".*.js$",".*\\.d.ts$"],
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true,
"dynamicImport": true
},
"transform":null,
"target":"es2016",
"loose":false
},
"sourceMaps": true
}
type TypeGeneration = 'none'|'startup'|'watch'
export class DatascriptsConfig extends ConfigFile {
protected description(): string {
return "Datascript settings"
}
@Section('Datascripts')
@Property({
name: 'Datascript.Dependencies'
, description: ''
, examples: [
[[],'No dependencies'],
[['other-module'],'other-module will be built before this one']
]
})
protected _dependencies: string[] = this.undefined()
get Dependencies() {
return this.getArrayAll(this._dependencies, [])
}
@Property({
name: 'Datascript.TypeGeneration'
, description:''
, examples: [
['none','']
, ['startup','']
, ['watch','']
]
})
TypeGeneration: TypeGeneration = this.undefined();
}
export class Datascripts {
mod: ModuleEndpoint
config: DatascriptsConfig;
get path() {
return this.mod.path.datascripts
}
logName() {
return termCustom('datascripts',this.mod.fullName)
}
installLibrary() {
if(this.config.TypeGeneration !== 'none') {
this.path.build.package_json
.writeJson(lib_package_json(this.mod.fullName))
if(!ipaths.node_modules.join(this.mod.fullName).exists()) {
term.log(
this.logName()
, `Installing ${this.mod.fullName} datascript library...`
)
wsys.exec(`npm i -S ${this.path.build.abs()}`)
}
}
}
initialize() {
this.path.mkdir();
if(!this.path.index.exists()) {
this.path.index.write(patch_example_ts(this.mod.subId))
}
ipaths.bin.include.global_d_ts.copy(this.path.global_d_ts)
this.path.tsconfig_json.writeJson(data_tsconfig);
if(!this.path.datascripts_conf.exists()) {
this.config.generateIfNotExists();
}
if(this.config.TypeGeneration === 'startup') {
this.compileTypes(true);
}
if(this.config.TypeGeneration === 'watch' && ! process.argv.includes('server-mode')) {
watchTsc(
ipaths.node_modules.typescript_js.abs().get()
, this.path.abs().get()
, this.logName()
);
}
this.installLibrary();
return this;
}
constructor(mod: ModuleEndpoint) {
this.mod = mod;
this.config = new DatascriptsConfig(this.path.datascripts_conf.get())
}
exists() {
return this.path.exists()
}
symlink() {
this.path.build.package_json.writeJson(lib_package_json(this.mod.fullName))
if(!ipaths.node_modules.join(this.mod.fullName).exists()) {
wsys.exec(`npm i -S ${this.path.build.get()}`)
}
}
compile() {
this.path.swcrc.writeJson(datascripts_swcrc)
try {
wsys.execIn(
this.path.dirname().get()
, `swc datascripts -d datascripts/build`,'inherit'
)
} catch(err) {
this.path.swcrc.remove();
throw new Error(`Failed to compile datascripts`)
}
this.path.swcrc.remove();
}
compileTypes(declare: boolean) {
term.log(this.logName(),`Compiling types for ${this.path}`)
try {
wsys.execIn(this.path.get(),'tsc','inherit');
} catch(error) {
// error printed by tsc
}
this.compile(); // ensure all sources exist
if(declare) {
this.symlink();
this.path.build.package_json.writeJson(
lib_package_json(this.mod.fullName)
)
}
}
static all() {
return Module.endpoints()
.filter(x=>x.path.datascripts.exists())
.map(x=>new Datascripts(x))
}
static create(mod: ModuleEndpoint) {
return new Datascripts(mod).initialize();
}
static installWowLib() {
if(!ipaths.node_modules.wow.exists()) {
term.log('datascripts','Linking wow data libraries...');
wsys.exec(`npm i -S ${ipaths.bin.scripts.wow.get()}`)
}
}
static initialize() {
this.installWowLib();
if(!ipaths.node_modules.wow.exists()) {
wsys.exec(`npm i`);
}
BuildCommand.addCommand(
'datascripts'
, 'dataset '
+ '--(client|server|inline)-only'
+ ' --readonly'
+ ' --rebuild'
+ ' --no-shutdown(-server|-client|)'
+ ' --no-restart(-server|-client|)'
, 'Builds datascripts for the selected dataset'
, async args => {
for(const value of Identifier.getDatasets(
args
, 'MATCH_ANY'
, NodeConfig.DefaultDataset
)) {
await this.build(value,args)
}
}
)
.addAlias('datascript')
.addAlias('data')
ListCommand.addCommand(
'datascripts'
, 'dataset?'
, ''
, args => {
let isDataset = Identifier.isDataset(args[0])
let eps = isDataset
? Dataset.all()
.find(x=>x.name === args[0])
.modules()
: Module.endpoints()
eps.forEach(x=>{
term.log('datascripts',x.path.get())
})
}
)
.addAlias('datascript').addAlias('data')
CleanCommand.addCommand(
'datascripts'
, 'modules'
, ''
, args => {
let mods = args.length === 0
? Module.endpoints().filter(x=>x.datascripts.exists())
: Identifier.getModules(args,'MATCH_ALL')
mods.forEach(x=>{
term.log('datascripts',`Removing ${x.path.livescripts.build}`)
x.path.datascripts.build.remove()
})
term.log('datascripts',`Removed ${mods.length} datascript builds`)
}
).addAlias('datascript').addAlias('data')
}
static async build(
dataset: Dataset
, args: string[] = []
) {
// 1. Install core libraries
this.installWowLib();
// 2. Parse exclusion arguments
const isInlineOnly = args.includes('--inline-only');
const isReadonlyArg = args.includes('--readonly')
const isReadOnly = isInlineOnly || isReadonlyArg
const isRebuild = args.includes('--rebuild')
const serverOnly = args.includes('--server-only')
const clientOnly = args.includes('--client-only')
const noShutdownAnyArg = args.includes('--no-shutdown') || args.includes('--no-shutdowns')
const noShutdownServerArg = noShutdownAnyArg
|| args.includes('--no-shutdown-server') || args.includes('--no-shutdowns-server')
const noShutdownClientArg = noShutdownAnyArg
|| args.includes('--no-shutdown-client') || args.includes('--no-shutdowns-client')
const noRestartAnyArg = args.includes('--no-restart') || args.includes('--no-restarts')
const noRestartServerArg = noRestartAnyArg
|| args.includes('--no-restarts-server') || args.includes('--no-restart-server')
const noRestartsClientArg = noRestartAnyArg
|| args.includes('--no-restarts-client') || args.includes('--no-restart-client')
const writesServer = !clientOnly && !isReadOnly;
const writesClient = !serverOnly && !isReadOnly;
const shutdownsServer = !noShutdownServerArg && writesServer
const shutdownsClient = !noShutdownClientArg && writesClient
const restartsServer = shutdownsServer && !noRestartServerArg
const restartsClient = shutdownsClient && !noRestartsClientArg
// 3. Detect invalid arguments
if(isRebuild && !shutdownsServer) {
throw new Error(
`Incompatible arguments:`
+ ` --rebuild requires the server to be shutdown,`
+ ` so it cannot be combined with any of the following arguments: `
+ ` --inline-only, --readonly, --client-only,`
+ ` --no-shutdown, --no-shutdown-server`
);
}
// 4. Shutdown clients and servers
let runningClients = shutdownsClient ? [dataset.client] : []
let runningWorldservers = shutdownsServer ? dataset.realms() : []
await Promise.all(runningWorldservers.map(x=>x.worldserver.stop()))
await Promise.all(runningClients.map(x=>x.kill()));
// 5. Prepare dataset
await dataset.setupClientData();
if(args.includes('--rebuild')) {
await dataset.setupDatabases('SOURCE',false);
await dataset.setupDatabases('DEST',true);
} else {
await dataset.setupDatabases('BOTH', false);
}
dataset.refreshSymlinks();
dataset.modules().forEach(endpoint=>{
if(endpoint.datascripts.path.exists()) {
endpoint.datascripts.compile();
endpoint.datascripts.installLibrary()
ipaths.bin.include.global_d_ts
.copy(endpoint.datascripts.path.global_d_ts)
}
});
// 6. Run datascripts
term.log('datascripts',
`Building datascripts`
+ ` {`
+ ` server: ${termc.magenta(`${writesServer}`)},`
+ ` client: ${termc.magenta(`${writesClient}`)}`
+ ` }`
)
try {
wsys.exec(
`node -r source-map-support/register`
+ ` ${ipaths.node_modules.wow.data.index.get()}`
+ ` --ipaths=./`
+ ` --dataset=${dataset.path.get()}`
+ ` --datasetName=${dataset.fullName}`
+ ` --clientPatch=${dataset.client.path.Data.devPatch}`
+ ` ${args.join(' ')}`
// Please don't pass these two manually
+ ` ${writesServer?'--__writes-server':''}`
+ ` ${writesClient?'--__writes-client':''}`
, 'inherit'
)
} catch(err) {
term.error('datascripts',`Failed to build datascripts, see error message above`);
return
}
// 7. Present profiling
if(args.includes('--prof')) {
wfs.readDir('./',true,'files')
.filter(x=>x.startsWith('isolate-')
&& x.endsWith('-v8.log'))
.forEach((x,i)=>{
wsys.exec(
`node --prof-process ${x}`
+ ` > node-profiling${i==0?'':`-${i}`}.txt`
)
wfs.remove(x)
})
}
// 8. Restore servers/clients
if(restartsClient) {
await Promise.all(runningClients.map(x=>x.startup(NodeConfig.AutoStartClient)))
}
if(restartsServer) {
let autorealms = NodeConfig.AutoStartRealms
.map(x=>Identifier.getRealm(x))
await Promise.all(runningWorldservers
.filter(x=>autorealms.find(y=>y.fullName===x.fullName))
.map(x=>x.start(x.lastBuildType)))
}
term.success('datascripts',`Finished building DataScripts for dataset ${dataset.name}`);
}
}