Skip to content

Commit 6a20995

Browse files
committed
chore(nx-infra-plugin): add custom build script for nx-infra-plugin to use postinstall hook
1 parent f26d128 commit 6a20995

File tree

4 files changed

+218
-28
lines changed

4 files changed

+218
-28
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"license": "MIT",
55
"author": "Developer Express Inc.",
66
"scripts": {
7+
"postinstall": "npx ts-node tools/scripts/build-nx-plugin.ts || true",
78
"devextreme:inject-descriptions-to-bundle": "dx-tools inject-descriptions --target-path ./packages/devextreme/ts/dx.all.d.ts --artifacts ./node_modules/devextreme-metadata/dist",
89
"devextreme:inject-descriptions-to-modules": "dx-tools inject-descriptions --collapse-tags --sources ./packages/devextreme/js --artifacts ./node_modules/devextreme-metadata/dist",
910
"devextreme:inject-descriptions": "npm run devextreme:inject-descriptions-to-bundle && npm run devextreme:inject-descriptions-to-modules",

packages/nx-infra-plugin/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"executors": "./dist/executors.json",
77
"exports": {
88
".": {
9-
"require": "./dist/src/index.js",
10-
"types": "./dist/src/index.d.ts"
9+
"require": "./dist/index.js",
10+
"types": "./dist/index.d.ts"
1111
},
1212
"./package.json": "./package.json"
1313
},
@@ -23,10 +23,9 @@
2323
"ts-jest": "29.1.3"
2424
},
2525
"scripts": {
26-
"postinstall": "pnpm run build",
2726
"format:check": "prettier --check .",
2827
"format": "prettier --write .",
29-
"build": "pnpm --workspace-root nx build devextreme-nx-infra-plugin",
28+
"build": "npx ts-node ../../tools/scripts/build-nx-plugin.ts --force",
3029
"test": "pnpm --workspace-root nx test devextreme-nx-infra-plugin",
3130
"lint": "pnpm run format:check"
3231
}

packages/nx-infra-plugin/project.json

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,10 @@
66
"tags": [],
77
"targets": {
88
"build": {
9-
"executor": "@nx/js:tsc",
10-
"outputs": ["{options.outputPath}"],
9+
"executor": "nx:run-script",
10+
"outputs": ["{projectRoot}/dist"],
1111
"options": {
12-
"outputPath": "{projectRoot}/dist",
13-
"main": "{projectRoot}/src/index.ts",
14-
"tsConfig": "{projectRoot}/tsconfig.lib.json",
15-
"generateExportsField": true,
16-
"assets": [
17-
"{projectRoot}/*.md",
18-
{
19-
"input": "{projectRoot}/src",
20-
"glob": "**/!(*.ts)",
21-
"output": "./src"
22-
},
23-
{
24-
"input": "{projectRoot}/src",
25-
"glob": "**/*.d.ts",
26-
"output": "./src"
27-
},
28-
{
29-
"input": "{projectRoot}",
30-
"glob": "executors.json",
31-
"output": "."
32-
}
33-
]
12+
"script": "build"
3413
}
3514
},
3615
"lint": {

tools/scripts/build-nx-plugin.ts

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import { execSync } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
5+
const PLUGIN_DIR_NAME = 'nx-infra-plugin';
6+
const DIST_DIR_NAME = 'dist';
7+
const SRC_DIR_NAME = 'src';
8+
const TEMP_TSCONFIG_NAME = 'tsconfig.bootstrap.json';
9+
const TSCONFIG_LIB_NAME = 'tsconfig.lib.json';
10+
const EXECUTORS_JSON_NAME = 'executors.json';
11+
const JSON_EXTENSION = '.json';
12+
const TSCONFIG_PREFIX = 'tsconfig';
13+
14+
interface PathConfig {
15+
pluginDir: string;
16+
distDir: string;
17+
srcDir: string;
18+
tsconfig: string;
19+
}
20+
21+
interface TsConfig {
22+
extends?: string;
23+
compilerOptions?: Record<string, unknown>;
24+
include?: string[];
25+
exclude?: string[];
26+
}
27+
28+
interface CompilationResult {
29+
success: boolean;
30+
error?: string;
31+
}
32+
33+
interface AssetCopyResult {
34+
filesCopied: number;
35+
}
36+
37+
const buildPathConfig = (rootDir: string): PathConfig => {
38+
const pluginDir = path.join(rootDir, '../../packages', PLUGIN_DIR_NAME);
39+
return {
40+
pluginDir,
41+
distDir: path.join(pluginDir, DIST_DIR_NAME),
42+
srcDir: path.join(pluginDir, SRC_DIR_NAME),
43+
tsconfig: path.join(pluginDir, TSCONFIG_LIB_NAME)
44+
};
45+
};
46+
47+
const checkDistExists = (distPath: string): boolean => {
48+
if (!fs.existsSync(distPath)) return false;
49+
const files = fs.readdirSync(distPath);
50+
return files.length > 0;
51+
};
52+
53+
const readTsConfig = (configPath: string): TsConfig => {
54+
const content = fs.readFileSync(configPath, 'utf8');
55+
return JSON.parse(content);
56+
};
57+
58+
const createBootstrapConfig = (original: TsConfig): TsConfig => ({
59+
...original,
60+
compilerOptions: {
61+
...original.compilerOptions,
62+
rootDir: undefined,
63+
outDir: `./${DIST_DIR_NAME}`
64+
}
65+
});
66+
67+
const writeTsConfig = (configPath: string, config: TsConfig): void => {
68+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
69+
};
70+
71+
const compileTypeScript = (pluginDir: string, configPath: string): CompilationResult => {
72+
try {
73+
execSync(
74+
`npx tsc -p ${configPath}`,
75+
{
76+
cwd: pluginDir,
77+
stdio: 'inherit',
78+
env: { ...process.env, NODE_ENV: 'production' }
79+
}
80+
);
81+
return { success: true };
82+
} catch (error) {
83+
return {
84+
success: false,
85+
error: (error as Error).message
86+
};
87+
}
88+
};
89+
90+
const isJsonAsset = (filename: string): boolean =>
91+
filename.endsWith(JSON_EXTENSION) && !filename.includes(TSCONFIG_PREFIX);
92+
93+
const copyJsonAssets = (srcDir: string, destDir: string): AssetCopyResult => {
94+
if (!fs.existsSync(srcDir)) {
95+
return { filesCopied: 0 };
96+
}
97+
98+
let filesCopied = 0;
99+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
100+
101+
for (const entry of entries) {
102+
const srcPath = path.join(srcDir, entry.name);
103+
const destPath = path.join(destDir, entry.name);
104+
105+
if (entry.isDirectory()) {
106+
if (!fs.existsSync(destPath)) {
107+
fs.mkdirSync(destPath, { recursive: true });
108+
}
109+
const result = copyJsonAssets(srcPath, destPath);
110+
filesCopied += result.filesCopied;
111+
} else if (isJsonAsset(entry.name)) {
112+
fs.copyFileSync(srcPath, destPath);
113+
filesCopied++;
114+
}
115+
}
116+
117+
return { filesCopied };
118+
};
119+
120+
const updateExecutorPaths = (config: Record<string, unknown>): Record<string, unknown> => {
121+
const executors = config.executors as Record<string, { implementation: string; schema: string }>;
122+
123+
const updated = Object.entries(executors).reduce((acc, [key, value]) => {
124+
acc[key] = {
125+
...value,
126+
implementation: value.implementation.replace('./src/', './'),
127+
schema: value.schema.replace('./src/', './')
128+
};
129+
return acc;
130+
}, {} as Record<string, { implementation: string; schema: string }>);
131+
132+
return { ...config, executors: updated };
133+
};
134+
135+
const copyExecutorsJson = (pluginDir: string, distDir: string): boolean => {
136+
const sourcePath = path.join(pluginDir, EXECUTORS_JSON_NAME);
137+
if (!fs.existsSync(sourcePath)) return false;
138+
139+
const content = fs.readFileSync(sourcePath, 'utf8');
140+
const config = JSON.parse(content);
141+
const updatedConfig = updateExecutorPaths(config);
142+
143+
const destPath = path.join(distDir, EXECUTORS_JSON_NAME);
144+
fs.writeFileSync(destPath, JSON.stringify(updatedConfig, null, 2));
145+
return true;
146+
};
147+
148+
const cleanupTempConfig = (configPath: string): void => {
149+
if (fs.existsSync(configPath)) {
150+
fs.unlinkSync(configPath);
151+
}
152+
};
153+
154+
const shouldSkipBuild = (distPath: string, forceRebuild: boolean): boolean => {
155+
if (forceRebuild) return false;
156+
return checkDistExists(distPath);
157+
};
158+
159+
const buildPlugin = (paths: PathConfig, forceRebuild = false): void => {
160+
if (shouldSkipBuild(paths.distDir, forceRebuild)) {
161+
console.log('✓ Plugin already built, skipping...');
162+
process.exit(0);
163+
}
164+
165+
console.log(' Compiling TypeScript...');
166+
167+
const tempConfigPath = path.join(paths.pluginDir, TEMP_TSCONFIG_NAME);
168+
const originalConfig = readTsConfig(paths.tsconfig);
169+
const bootstrapConfig = createBootstrapConfig(originalConfig);
170+
171+
writeTsConfig(tempConfigPath, bootstrapConfig);
172+
173+
try {
174+
const result = compileTypeScript(paths.pluginDir, tempConfigPath);
175+
if (!result.success) {
176+
throw new Error(result.error);
177+
}
178+
179+
console.log(' Copying assets...');
180+
copyJsonAssets(paths.srcDir, paths.distDir);
181+
182+
copyExecutorsJson(paths.pluginDir, paths.distDir);
183+
184+
console.log('✓ Plugin built successfully!');
185+
} finally {
186+
cleanupTempConfig(tempConfigPath);
187+
}
188+
};
189+
190+
const parseArgs = (): { forceRebuild: boolean } => {
191+
const args = process.argv.slice(2);
192+
return {
193+
forceRebuild: args.includes('--force') || args.includes('-f')
194+
};
195+
};
196+
197+
const main = (): void => {
198+
console.log('🔨 Building nx-infra-plugin...');
199+
200+
try {
201+
const { forceRebuild } = parseArgs();
202+
const paths = buildPathConfig(__dirname);
203+
buildPlugin(paths, forceRebuild);
204+
} catch (error) {
205+
console.error('⚠ Failed to build plugin:', (error as Error).message);
206+
console.error(' The plugin will be built on first use by NX');
207+
process.exit(0);
208+
}
209+
};
210+
211+
main();

0 commit comments

Comments
 (0)