import * as plugins from './plugins.js'; import * as paths from './paths.js'; import { logger } from './logging.js'; export interface ITsPublishJson { name: string; dependencies: string[]; registries: string[]; } export interface IPublishModuleOptions { monoRepoDir: string; packageSubFolder: string; packageSubFolderFullPath?: string; tsPublishJson?: ITsPublishJson; publishModDirFullPath?: string; name?: string; version?: string; dependencies?: { [key: string]: string }; } export class PublishModule { public options: IPublishModuleOptions; constructor(options: IPublishModuleOptions) { this.options = options; } public async init() { this.options.packageSubFolderFullPath = plugins.path.join( this.options.monoRepoDir, this.options.packageSubFolder ); // check requirements if (!this.options.packageSubFolder.startsWith('ts')) { throw new Error('subFolder must start with "ts"'); } this.options.tsPublishJson = plugins.smartfile.fs.toObjectSync( plugins.path.join(this.options.packageSubFolderFullPath, 'tspublish.json') ); const monoRepoPackageJson = JSON.parse( plugins.smartfile.fs.toStringSync(plugins.path.join(this.options.monoRepoDir, 'package.json')) ); this.options.dependencies = { ...this.options.dependencies, ...(() => { const resultDependencies = {}; for (const dependency of this.options.tsPublishJson.dependencies) { resultDependencies[dependency] = monoRepoPackageJson.dependencies[dependency]; } return resultDependencies; })(), }; this.options.name = this.options.name || this.options.tsPublishJson.name; this.options.version = monoRepoPackageJson.version; // now that we have a name and version, lets check if there is already a package under the same name and version. const smartnpmInstance = new plugins.smartnpm.NpmRegistry({}); // TODO: pass in options const packageInfo = await smartnpmInstance.getPackageInfo(this.options.name); if (packageInfo) { const availableVersions = packageInfo.allVersions.map((versionArg) => versionArg.version); logger.log('info', `available versions are: ${availableVersions.toString()}`); if (availableVersions.includes(this.options.version)) { logger.log('error', `package ${this.options.name} already exists with version ${this.options.version}`); process.exit(1); } } } public async getLatestVersionOfPackage(name: string) { const smartnpmInstance = new plugins.smartnpm.NpmRegistry({}); // TODO: pass in options const packageInfo = await smartnpmInstance.getPackageInfo(name); if (!packageInfo) { throw new Error(`package ${name} not found`); } return packageInfo.allVersions[0].version; } public async createTsconfigJson() { const originalTsConfig = plugins.smartfile.fs.toObjectSync( plugins.path.join(paths.cwd, 'tsconfig.json') ); if (originalTsConfig?.compilerOptions?.paths) { for (const path of originalTsConfig.compilerOptions.paths) { originalTsConfig.compilerOptions.paths[path][0] = `.${originalTsConfig.compilerOptions.paths[path][0]}`; } } const tsconfigJson = { compilerOptions: { experimentalDecorators: true, useDefineForClassFields: false, target: 'ES2022', module: 'NodeNext', moduleResolution: 'NodeNext', esModuleInterop: true, verbatimModuleSyntax: true, paths: originalTsConfig?.compilerOptions?.paths, }, exclude: [ 'dist_*/**/*.d.ts', ], }; return JSON.stringify(tsconfigJson, null, 2); } public async createPackageJson() { const packageJson = { name: this.options.name, version: this.options.version, type: 'module', description: '', exports: { '.': { import: `./dist_${this.options.packageSubFolder}/index.js`, }, }, scripts: { build: 'tsbuild tsfolders --allowimplicitany', }, dependencies: this.options.dependencies, devDependencies: { '@git.zone/tsbuild': await this.getLatestVersionOfPackage('@git.zone/tsbuild'), }, files: [ 'ts/**/*', 'ts_*/**/*', 'dist/**/*', 'dist_*/**/*', 'dist_ts/**/*', 'dist_ts_web/**/*', 'assets/**/*', 'cli.js', 'npmextra.json', 'readme.md', ], }; return JSON.stringify(packageJson, null, 2); } public async createPublishModuleDir() { this.options.publishModDirFullPath = plugins.path.join( this.options.monoRepoDir, `dist_publish_${this.options.packageSubFolder}` ); await plugins.smartfile.fs.ensureEmptyDir(this.options.publishModDirFullPath); // package.json const packageJson = await plugins.smartfile.SmartFile.fromString( plugins.path.join(this.options.publishModDirFullPath, 'package.json'), await this.createPackageJson(), 'utf8' ); await packageJson.write(); // tsconfig.json const originalTsConfigJson = await plugins.smartfile.SmartFile.fromString( plugins.path.join(this.options.publishModDirFullPath, 'tsconfig.json'), await this.createTsconfigJson(), 'utf8' ); await originalTsConfigJson.write(); // ts folder await plugins.smartfile.fs.copy( this.options.packageSubFolderFullPath, plugins.path.join(this.options.publishModDirFullPath, this.options.packageSubFolder) ); } public async build() { const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); await smartshellInstance.exec(`cd ${this.options.publishModDirFullPath} && pnpm run build`); } public async publish() { const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); for (const registry of this.options.tsPublishJson.registries) { const registryArray = registry.split(':'); const registryUrl = registryArray[0]; const registryAccessLevel = registryArray[1]; await smartshellInstance.exec( `cd ${this.options.publishModDirFullPath} && pnpm publish ${ registryAccessLevel === 'public' ? '--access public' : '' } --no-git-checks --registry https://${registryUrl}` ); } } }