Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3f2e827
build: disable esbuild minify, fix rollup externalLiveBindings
sergak01 Feb 20, 2026
a9b83e0
build(deps): add next as dev dependency
sergak01 Feb 20, 2026
c8c2142
refactor: ppDev config from top-level only, update proxy rewritePath
sergak01 Feb 20, 2026
2c16dae
feat(cli): appId support, base path handling, API routes passthrough
sergak01 Feb 20, 2026
1017dda
test(nextjs): enable withPPDev, add basePath and assetPrefix
sergak01 Feb 20, 2026
ba94b65
test(nextjs): add dataset-data API, suppressHydrationWarning
sergak01 Feb 20, 2026
8d3d482
test(nextjs-cjs): enable withPPDev, add templateLess
sergak01 Feb 20, 2026
870b036
test(nextjs-cjs): add dataset-data API, suppressHydrationWarning
sergak01 Feb 20, 2026
0ccf7d7
test(commonjs): update pp-dev package integrity in lockfile
sergak01 Feb 20, 2026
b31e90e
Merge remote-tracking branch 'origin/develop' into pp-2741
sergak01 Feb 20, 2026
e64f4c8
fix: ejs v4 default import for ESM compatibility
sergak01 Feb 20, 2026
e2cc2a9
build: add minimatch override, upgrade packages
sergak01 Feb 20, 2026
352cc4b
test(commonjs): upgrade packages, add minimatch override
sergak01 Feb 20, 2026
dea3e6b
test(nextjs): upgrade packages, add minimatch override
sergak01 Feb 20, 2026
52c761d
test(nextjs-cjs): upgrade packages, add minimatch override
sergak01 Feb 20, 2026
63ec031
feat(cli): appId support, base path handling
sergak01 Feb 20, 2026
3edd020
fix: improve dev server restart reliability and config change detection
sergak01 Feb 20, 2026
78b1e13
test(lockfiles): update pp-dev package integrity values
sergak01 Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,061 changes: 1,050 additions & 2,011 deletions package-lock.json

Large diffs are not rendered by default.

48 changes: 25 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
"node": ">=22.14"
},
"overrides": {
"chokidar": "^4.0.3"
"chokidar": "^4.0.3",
"minimatch": ">=10.2.1"
},
"peerDependencies": {
"next": ">= 13 < 17"
Expand All @@ -81,56 +82,57 @@
"axios": "^1.13.5",
"cac": "^6.7.14",
"chokidar": "^4.0.3",
"deepmerge-ts": "^7.1.3",
"deepmerge-ts": "^7.1.5",
"diff-match-patch": "^1.0.5",
"dir-compare": "^5.0.0",
"ejs": "^4.0.1",
"express": "^5.2.1",
"extract-zip": "^2.0.1",
"file-type": "^19.6.0",
"formdata-node": "^6.0.3",
"http-proxy-middleware": "^3.0.3",
"isbinaryfile": "^5.0.4",
"http-proxy-middleware": "^3.0.5",
"isbinaryfile": "^5.0.7",
"jsdom": "^25.0.1",
"memory-cache": "^0.2.0",
"picocolors": "^1.1.1",
"rollup": "^4.40.0",
"sass": "^1.87.0",
"rollup": "^4.58.0",
"sass": "^1.97.3",
"sharp": "^0.34.5",
"source-map-support": "^0.5.21",
"svgo": "^4.0.0",
"svgtofont": "^6.0.1",
"typescript": "^5.6.0",
"svgtofont": "^6.5.1",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-plugin-image-optimizer": "^1.1.8",
"vite-plugin-image-optimizer": "^1.1.9",
"vite-plugin-zip-pack": "^1.2.4",
"winston": "^3.17.0"
"winston": "^3.19.0"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@playwright/test": "^1.58.2",
"next": "^15.5.12",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-typescript": "^12.3.0",
"@rollup/plugin-url": "^8.0.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/diff": "^8.0.0",
"@types/diff-match-patch": "^1.0.36",
"@types/ejs": "^3.1.5",
"@types/express": "^5.0.0",
"@types/express": "^5.0.6",
"@types/jsdom": "^21.1.7",
"@types/memory-cache": "^0.2.6",
"@types/node": "^22.15.0",
"@vitest/coverage-v8": "^3.0.0",
"esbuild": "^0.25.8",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"rollup-plugin-dts": "^6.2.1",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^3.2.4",
"esbuild": "^0.27.3",
"prettier": "^3.8.1",
"rimraf": "^6.1.3",
"rollup-plugin-dts": "^6.3.0",
"rollup-plugin-scss": "^4.0.1",
"rollup-plugin-visualizer": "^6.0.5",
"semantic-release": "^25.0.2",
"rollup-plugin-visualizer": "6.0.5",
"semantic-release": "^25.0.3",
"tslib": "^2.8.1",
"tsx": "^4.19.2",
"vitest": "^3.0.0",
"tsx": "^4.21.0",
"vitest": "^3.2.4",
"yargs": "^17.7.2"
},
"files": [
Expand Down
4 changes: 4 additions & 0 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ const configs: RollupOptions[] = [
sourcemap: true,
exports: 'auto',
interop: 'compat',
// Fix: avoid _interopNamespaceCompat crash when processing Node.js built-ins
// (path, child_process, etc.) that have inherited prototype properties.
// getOwnPropertyDescriptor returns undefined for inherited props → d.get throws.
externalLiveBindings: false,
generatedCode: {
constBindings: true,
objectShorthand: true,
Expand Down
112 changes: 56 additions & 56 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { ChangelogGenerator } from './lib/changelog-generator.js';
import { IconFontGenerator } from './lib/icon-font-generator.js';
// Remove the explicit process import since it's globally available
import internalServer from './lib/internal.middleware';
import { safeNextImport, isNextAvailable } from './lib/next-import.js';
import { safeNextImport } from './lib/next-import.js';
import { PP_DEV_CONFIG_NAMES, PP_WATCH_CONFIG_NAMES } from './constants.js';

const cli = cac('pp-dev');
Expand Down Expand Up @@ -506,21 +506,25 @@ cli
.action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
filterDuplicateOptions(options);

let nextApp: any = null;
let nextApp: ReturnType<typeof import('next').default> | null = null;
let httpServer: any = null;
let configWatcher: ConfigWatcher | null = null;
let isRestarting = false;

const projectRoot = root
? path.resolve(process.cwd(), root)
: process.cwd();
const logger = createLogger();
Comment thread
sergak01 marked this conversation as resolved.

const startNextServer = async () => {
if (isRestarting) return;
if (isRestarting) {
return;
}

isRestarting = true;

try {
// Import Next.js first – with logger available for error reporting
const { next, constants } = await safeNextImport();
const { PHASE_DEVELOPMENT_SERVER } = constants;

// Clean up existing server if any
if (httpServer) {
logger.info(colors.yellow('🛑 Stopping existing Next.js server...'));
Expand All @@ -535,32 +539,23 @@ cli
// Clean up existing Next.js app if any
if (nextApp && typeof nextApp.close === 'function') {
await nextApp.close();

nextApp = null;
}

// Clear config cache
const { clearConfigCache } = await import('./config.js');
clearConfigCache();

// Check if Next.js is available before proceeding
if (!(await isNextAvailable())) {
throw new Error(
'Next.js is required but not available. Please install Next.js as a dependency:\n' +
'npm install next@^16\n\n' +
'This package requires Next.js >=13 <17 as a peer dependency.',
);
}

const { next } = await safeNextImport();
const { join, basename } = await import('path');
const { createServer } = await import('http');

const importConfig = await import('next/dist/server/config.js');

const loadConfig =
importConfig.default.default ||
importConfig['module.exports'].default ||
importConfig.default;
const loadConfig: typeof import('next/dist/server/config.js').default =
(importConfig as any).default.default ||
(importConfig as any)['module.exports'].default ||
(importConfig as any).default;
Comment thread
sergak01 marked this conversation as resolved.

const opts = cleanOptions(options);

Expand All @@ -585,10 +580,10 @@ cli
logger.info(projectRoot);

// Get pp-dev config from Next.js app config
const config = await loadConfig('development', projectRoot);
const config = await loadConfig(PHASE_DEVELOPMENT_SERVER, projectRoot);

// Extract pp-dev configuration from Next.js config
let ppDevConfig = config?.experimental?.ppDev || config?.ppDev || {};
let ppDevConfig = config?.ppDev || {};

// If no pp-dev config found in Next.js config, try to load from standalone config file
if (Object.keys(ppDevConfig).length === 0) {
Expand Down Expand Up @@ -628,25 +623,36 @@ cli
const {
backendBaseURL = process.env.MI_BACKEND_URL ||
'http://localhost:8080',
portalPageId = parseInt(process.env.MI_PORTAL_PAGE_ID || '1'),
appId: originalAppId,
portalPageId,
templateLess = true,
v7Features = true,
disableSSLValidation = false,
enableProxyCache = true,
proxyCacheTTL = 600000,
personalAccessToken = process.env.MI_ACCESS_TOKEN,
distZip = false,
syncBackupsDir = './backups',
miHudLess = false,
} = ppDevConfig;

const appId: number =
originalAppId ??
portalPageId ??
(process.env.MI_APP_ID
? parseInt(process.env.MI_APP_ID)
: undefined) ??
(process.env.MI_PORTAL_PAGE_ID
? parseInt(process.env.MI_PORTAL_PAGE_ID)
: undefined) ??
1;

// Get template name from config, package.json, or fallback to project directory name
let templateName = ppDevConfig.templateName;
let templateName = null;

if (!templateName) {
try {
const { getPkg } = await import('./config.js');
const pkg = getPkg();

templateName = pkg.name;
} catch (error) {
// Fallback to project directory name
Expand All @@ -658,8 +664,15 @@ cli
const pathPagePrefix = '/p'; // templateLess = true - use /p
const pathTemplatePrefix = '/pl'; // templateLess = false && v7Features = true - use /pl

let base = templateLess ? pathPagePrefix : pathTemplatePrefix;
base += `/${templateName}`;
const configBasePath = config?.basePath;
let base = '';

if (configBasePath) {
base = configBasePath;
} else {
base = templateLess ? pathPagePrefix : pathTemplatePrefix;
base += `/${templateName}`;
}

nextApp = next({
dev: true,
Expand All @@ -669,18 +682,12 @@ cli
conf: {
...config,
basePath: base,
assetPrefix: base, // Fixed: Make assetPrefix consistent with basePath
assetPrefix: `${templateLess ? pathPagePrefix : '/pt'}/${templateName}`, // Fixed: Make assetPrefix consistent with basePath
},
});

await nextApp.prepare();

// Default to templateLess = true for Next.js development
// const templateLess =
// typeof ppDevConfig.templateLess === "boolean"
// ? ppDevConfig.templateLess
// : true;

if (!base.endsWith('/')) {
base += '/';
}
Expand All @@ -691,11 +698,6 @@ cli
);
}

const baseWithoutTrailingSlash = base.substring(
0,
base.lastIndexOf('/'),
);

// Log the configuration
logger.info(colors.green('✅ Next.js app prepared successfully'));
logger.info(
Expand All @@ -707,7 +709,7 @@ cli

if (backendBaseURL) {
logger.info(colors.blue(`🌐 Backend URL: ${backendBaseURL}`));
logger.info(colors.blue(`🆔 Portal Page ID: ${portalPageId}`));
logger.info(colors.blue(`🆔 Custom App ID: ${appId}`));
}

// Get the Next.js request handler
Expand Down Expand Up @@ -735,8 +737,7 @@ cli
const isInternalNextRoute =
originalPathname.startsWith('/_next/') ||
originalPathname === '/favicon.ico' ||
originalPathname.startsWith('/__nextjs_') ||
originalPathname.startsWith('/api/');
originalPathname.startsWith('/__nextjs_');

if (isInternalNextRoute) {
// For internal routes, only apply essential middlewares (skip proxy, cache, etc.)
Expand Down Expand Up @@ -791,17 +792,16 @@ cli
processNextJSRequest();

async function processNextJSRequest() {
// Handle base path requests
// Handle base path requests - pass full path to Next.js so it can apply basePath routing
if (originalPathname.startsWith(base)) {
// Strip the base path for Next.js
const nextPath = originalPathname.substring(base.length);

req.url = nextPath || '/';
parsedUrl = parse(nextPath, true);
// Keep full path - Next.js expects req.url to include basePath for proper routing
parsedUrl = parse(originalUrl, true);
} else if (originalPathname === base.replace(/\/$/, '')) {
// Handle base path without trailing slash
req.url = '/';
parsedUrl = parse('/', true);
// Path without trailing slash - redirect to canonical URL with trailing slash
const redirectUrl = originalUrl.replace(originalPathname, base);
res.writeHead(302, { Location: redirectUrl });
res.end();
return;
} else if (
originalPathname.startsWith('/_next/') ||
originalPathname === '/favicon.ico' ||
Expand Down Expand Up @@ -849,8 +849,8 @@ cli
'$1$2',
),
},
portalPageId,
appId: portalPageId,
portalPageId: appId,
appId,
templateLess,
disableSSLValidation,
v7Features,
Expand Down Expand Up @@ -931,7 +931,7 @@ cli
'/_next',
'/favicon.ico',
'/__nextjs_',
'/api',
'/installHook.js.map',
],
disableSSLValidation,
miAPI: mi,
Expand Down Expand Up @@ -1011,7 +1011,7 @@ cli
logger.info(
colors.blue(`🔧 MiAPI initialized for backend: ${backendBaseURL}`),
);
logger.info(colors.blue(`🔧 Portal Page ID: ${portalPageId}`));
logger.info(colors.blue(`🔧 Custom App ID: ${appId}`));
}

httpServer.listen(port, host, () => {
Expand Down
Loading
Loading