151 lines
4.2 KiB
TypeScript
151 lines
4.2 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { TsPublishConfig } from '../mod_config/index.js';
|
|
import { FsHelpers } from '../mod_fs/index.js';
|
|
|
|
/**
|
|
* TsUnpacker handles flattening of nested TypeScript output directories.
|
|
*
|
|
* When TypeScript compiles files that import from sibling directories,
|
|
* it creates a nested structure like:
|
|
* dist_ts_core/ts_core/index.js
|
|
* dist_ts_core/ts_shared/helper.js
|
|
*
|
|
* This class flattens it to:
|
|
* dist_ts_core/index.js
|
|
*/
|
|
export class TsUnpacker {
|
|
private sourceFolderName: string;
|
|
private destDir: string;
|
|
private cwd: string;
|
|
private config: TsPublishConfig;
|
|
|
|
constructor(sourceFolderName: string, destDir: string, cwd: string = process.cwd()) {
|
|
this.sourceFolderName = sourceFolderName;
|
|
this.destDir = destDir;
|
|
this.cwd = cwd;
|
|
this.config = new TsPublishConfig(path.join(cwd, sourceFolderName));
|
|
}
|
|
|
|
/**
|
|
* Create an unpacker from a glob pattern
|
|
* './ts_core/**\/*.ts' → sourceFolderName = 'ts_core'
|
|
*/
|
|
public static fromGlobPattern(
|
|
sourcePattern: string,
|
|
destDir: string,
|
|
cwd: string = process.cwd()
|
|
): TsUnpacker | null {
|
|
const sourceFolderName = FsHelpers.extractSourceFolder(sourcePattern);
|
|
if (!sourceFolderName) {
|
|
return null;
|
|
}
|
|
return new TsUnpacker(sourceFolderName, destDir, cwd);
|
|
}
|
|
|
|
/**
|
|
* Get the source folder name
|
|
*/
|
|
public getSourceFolderName(): string {
|
|
return this.sourceFolderName;
|
|
}
|
|
|
|
/**
|
|
* Get the destination directory
|
|
*/
|
|
public getDestDir(): string {
|
|
return this.destDir;
|
|
}
|
|
|
|
/**
|
|
* Check if unpacking should be performed based on tspublish.json config
|
|
* Default is true if not specified
|
|
*/
|
|
public async shouldUnpack(): Promise<boolean> {
|
|
return this.config.shouldUnpack;
|
|
}
|
|
|
|
/**
|
|
* Check if nested structure exists in the destination directory
|
|
*/
|
|
public async detectNesting(): Promise<boolean> {
|
|
const nestedPath = path.join(this.destDir, this.sourceFolderName);
|
|
return FsHelpers.directoryExists(nestedPath);
|
|
}
|
|
|
|
/**
|
|
* Get the path to the nested directory
|
|
*/
|
|
public getNestedPath(): string {
|
|
return path.join(this.destDir, this.sourceFolderName);
|
|
}
|
|
|
|
/**
|
|
* Perform the unpack operation - flatten nested output directories.
|
|
*
|
|
* When TypeScript compiles files that import from sibling directories,
|
|
* it creates a nested structure like dist_ts/ts/ with siblings like
|
|
* dist_ts/ts_interfaces/. This method flattens by:
|
|
* 1. Removing sibling directories (non-source folders)
|
|
* 2. Moving contents of the nested source folder up to the dest dir
|
|
* 3. Removing the now-empty nested source folder
|
|
*
|
|
* Uses synchronous fs operations for reliability.
|
|
* Called after all compilations are complete (not between compilations)
|
|
* to avoid filesystem metadata issues on XFS.
|
|
*
|
|
* Returns true if unpacking was performed, false if skipped.
|
|
*/
|
|
public async unpack(): Promise<boolean> {
|
|
if (!(await this.shouldUnpack())) {
|
|
return false;
|
|
}
|
|
|
|
if (!(await this.detectNesting())) {
|
|
return false;
|
|
}
|
|
|
|
const nestedPath = this.getNestedPath();
|
|
|
|
// Step 1: Remove sibling entries (everything in dest except the source folder)
|
|
const destEntries = fs.readdirSync(this.destDir);
|
|
for (const entry of destEntries) {
|
|
if (entry !== this.sourceFolderName) {
|
|
fs.rmSync(path.join(this.destDir, entry), { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
// Step 2: Move all contents from nested dir up to dest dir
|
|
const nestedEntries = fs.readdirSync(nestedPath);
|
|
for (const entry of nestedEntries) {
|
|
fs.renameSync(
|
|
path.join(nestedPath, entry),
|
|
path.join(this.destDir, entry),
|
|
);
|
|
}
|
|
|
|
// Step 3: Remove the now-empty nested directory
|
|
fs.rmdirSync(nestedPath);
|
|
|
|
console.log(` 📦 Unpacked ${this.sourceFolderName}: ${nestedEntries.length} entries`);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience function to perform unpack operation
|
|
* Can be used directly without instantiating the class
|
|
*/
|
|
export async function performUnpack(
|
|
sourcePattern: string,
|
|
destDir: string,
|
|
cwd: string = process.cwd()
|
|
): Promise<boolean> {
|
|
const unpacker = TsUnpacker.fromGlobPattern(sourcePattern, destDir, cwd);
|
|
if (!unpacker) {
|
|
return false;
|
|
}
|
|
return unpacker.unpack();
|
|
}
|