Scenario generator for vore roleplay and story ideas. https://scenario-generator.deliciousreya.net/responses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

201 lines
6.4 KiB

import {
createPrinter,
factory,
NewLineKind,
NodeFlags,
type PropertyAssignment,
SyntaxKind,
type VariableDeclaration,
type VariableStatement
} from 'typescript';
import typescriptModule from 'typescript';
import { readFile, writeFile, readdir } from 'node:fs/promises';
import { basename, dirname, join, normalize } from 'node:path';
import {createHash} from 'node:crypto';
import camelcase from 'camelcase';
import { render as renderLess } from 'less';
import CleanCSS from 'clean-css';
import type { HashedBundled, SourceMappedHashedBundled, SourceMappedBundled, Bundled } from '../common/bundle.js';
import type { RawSourceMap } from 'source-map';
import { rollup, type RollupCache } from 'rollup';
import babel from '@rollup/plugin-babel';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
function* assignProperties(pairs: Iterable<[string, HashedBundled]>): Generator<PropertyAssignment> {
for (const [identifier, { bundled, hash }] of pairs) {
yield factory.createPropertyAssignment(
factory.createIdentifier(identifier),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment(
factory.createIdentifier("bundled"),
factory.createNoSubstitutionTemplateLiteral(bundled)
),
factory.createPropertyAssignment(
factory.createIdentifier("hash"),
factory.createStringLiteral(hash)
),
], true));
}
}
function declareObjectLiteral(identifier: string, pairs: Iterable<[string, HashedBundled]>): VariableDeclaration {
return factory.createVariableDeclaration(
factory.createIdentifier(identifier),
undefined,
undefined,
factory.createSatisfiesExpression(
factory.createAsExpression(
factory.createObjectLiteralExpression(Array.from(assignProperties(pairs)), true),
factory.createTypeReferenceNode(factory.createIdentifier('const'))),
factory.createTypeReferenceNode(
factory.createIdentifier("Record"),
[
factory.createTypeReferenceNode(factory.createIdentifier("string")),
factory.createTypeReferenceNode(factory.createIdentifier("HashedBundled"))])));
}
function exportObjectLiteral(identifier: string, pairs: Iterable<[string, HashedBundled]>): VariableStatement {
return factory.createVariableStatement(
[factory.createToken(SyntaxKind.ExportKeyword)],
factory.createVariableDeclarationList([declareObjectLiteral(identifier, pairs)], NodeFlags.Const)
);
}
async function processLess(atPath: string): Promise<SourceMappedBundled> {
const fileBase = basename(atPath.substring(0, atPath.length - LESS_SUFFIX.length));
const { css: lessCss, map: lessMap } = await renderLess(await readFile(atPath, { encoding: 'utf-8' }), {
paths: [dirname(atPath)],
math: 'strict',
strictUnits: true,
filename: fileBase + '.less',
strictImports: true,
sourceMap: {
outputSourceFiles: true,
sourceMapFileInline: true
}
});
const { styles, sourceMap } = await new CleanCSS({
sourceMap: true,
sourceMapInlineSources: true,
returnPromise: true,
level: 2,
format: false,
inline: ['all'],
rebase: false,
compatibility: '*',
fetch(uri): never {
throw Error(`external files are unexpected after less compilation, but found ${uri}`)
},
}).minify({
[fileBase + '.css']: {
styles: lessCss,
sourceMap: lessMap
}
})
return { bundled: styles, sourceMap: JSON.parse(sourceMap!.toString()) as RawSourceMap };
}
async function processTypescript(atPath: string, inDir: string, cache?: RollupCache): Promise<{cache: RollupCache, bundle: SourceMappedBundled}> {
const build = await rollup({
cache: cache ?? true,
input: atPath,
plugins: [
typescript({
noEmitOnError: true,
noForceEmit: true,
emitDeclarationOnly: false,
noEmit: true,
include: [join(inDir, '**', '*.ts')],
typescript: typescriptModule,
tsconfig: join(inDir, 'tsconfig.json')
}),
babel({
babelHelpers: 'bundled',
include: [join(inDir, '**', '*.ts'), join(inDir, '**', '*.js')],
extensions: ['js', 'ts'],
presets: ['@babel/preset-typescript']
}),
terser({})
]
})
const {output: [chunk]} = await build.generate({
sourcemap: 'hidden',
sourcemapFile: join(inDir, 'sourcemap.map'),
format: 'iife',
compact: true,
})
return {
cache: build.cache!,
bundle: {
bundled: chunk.code,
sourceMap: chunk.map!
}
}
}
const LESS_SUFFIX = '-entrypoint.less';
const TS_SUFFIX = '-entrypoint.ts';
function hashBundled<T extends Bundled>(value: T & {readonly hash?: never}): T & HashedBundled {
const hash = createHash('sha256').update(value.bundled).digest('hex')
return {
...value,
hash,
}
}
export async function getBundle(inDir: string): Promise<{ css: Map<string, SourceMappedHashedBundled>, js: Map<string, SourceMappedHashedBundled> }> {
const css = new Map<string, SourceMappedHashedBundled>();
const js = new Map<string, SourceMappedHashedBundled>();
const dir = await readdir(inDir, { withFileTypes: true });
let cache: RollupCache|undefined = undefined
for (const ent of dir) {
if (!ent.isFile()) {
continue;
}
if (ent.name.endsWith(LESS_SUFFIX)) {
css.set(camelcase(ent.name.substring(0, ent.name.length - LESS_SUFFIX.length)), hashBundled(await processLess(join(inDir, ent.name))));
} else if (ent.name.endsWith(TS_SUFFIX)) {
const {cache: newCache, bundle} = await processTypescript(join(inDir, ent.name), inDir, cache)
cache = newCache
js.set(camelcase(ent.name.substring(0, ent.name.length - TS_SUFFIX.length)), hashBundled(bundle));
} else {
// continue;
}
}
return { css, js };
}
export const DEFAULT_IN_PATH = normalize(join(__dirname, '../../src/client/'))
export const DEFAULT_OUT_PATH = normalize(join(__dirname, '../../src/server/web/client.generated.ts'))
export async function writeBundle({ css, js }: {css: Map<string, HashedBundled>, js: Map<string, HashedBundled>}, outFile: string): Promise<void> {
const printer = createPrinter({
newLine: NewLineKind.LineFeed,
omitTrailingSemicolon: true
});
await writeFile(outFile, printer.printFile(factory.createSourceFile([
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(
true,
undefined,
factory.createIdentifier("HashedBundled"))
])
),
factory.createStringLiteral("../../common/bundle.js")),
exportObjectLiteral('CSS', css),
exportObjectLiteral('JS', js)
], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None)), {
encoding: 'utf-8',
mode: 0o644
});
}