UNPKG

@strapi/strapi

Version:

An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite

351 lines (347 loc) • 14.6 kB
'use strict'; var fs = require('node:fs/promises'); var path = require('node:path'); var cluster = require('node:cluster'); var dependencies = require('./core/dependencies.js'); var timer = require('./core/timer.js'); // Lazy: worker-only deps; primary cluster process should not pay for them const lazy = (spec)=>{ let cached; return ()=>{ if (cached === undefined) { // eslint-disable-next-line @typescript-eslint/no-var-requires cached = require(spec); } return cached; }; }; const tsUtils = lazy('@strapi/typescript-utils'); const utils = lazy('@strapi/utils'); const chokidar = lazy('chokidar'); const core = lazy('@strapi/core'); const buildCtx = lazy('./create-build-context'); const staticFs = lazy('./staticFiles'); // This method removes all non-admin build files from the dist directory const cleanupDistDirectory = async ({ tsconfig, logger, timer: timer$1 })=>{ const distDir = tsconfig?.config?.options?.outDir; if (!distDir || // we don't have a dist dir await fs.access(distDir).then(()=>false).catch(()=>true) // it doesn't exist -- if it does but no access, that will be caught later ) { return; } const timerName = `cleaningDist${Date.now()}`; timer$1.start(timerName); const cleaningSpinner = logger.spinner(`Cleaning dist dir ${distDir}`).start(); try { const dirContent = await fs.readdir(distDir); const validFilenames = dirContent// Ignore the admin build folder and the TypeScript incremental cache .filter((filename)=>filename !== 'build' && !filename.endsWith('.tsbuildinfo')); for (const filename of validFilenames){ await fs.rm(path.resolve(distDir, filename), { recursive: true }); } } catch (err) { const generatingDuration = timer$1.end(timerName); cleaningSpinner.text = `Error cleaning dist dir: ${err} (${timer.prettyTime(generatingDuration)})`; cleaningSpinner?.fail(); return; } const generatingDuration = timer$1.end(timerName); cleaningSpinner.text = `Cleaning dist dir (${timer.prettyTime(generatingDuration)})`; cleaningSpinner?.succeed(); }; const develop = async ({ cwd, polling, logger, tsconfig, watchAdmin, buildAdmin, ...options })=>{ const timer$1 = timer.getTimer(); if (cluster.isPrimary) { const { didInstall } = await dependencies.checkRequiredDependencies({ cwd, logger }).catch((err)=>{ logger.error(err.message); process.exit(1); }); if (didInstall) { return; } if (tsconfig?.config) { // Build without diagnostics in case schemas have changed await cleanupDistDirectory({ tsconfig, logger, timer: timer$1 }); try { await tsUtils().compile(cwd, { configOptions: { ignoreDiagnostics: true } }); } catch (err) { logger.error(`Error during initial TypeScript compilation: ${err.message}`); // We don't return here because we want to attempt to start the server even if the initial compilation fails, as it can be fixed while the server is running } } /** * IF we're not watching the admin we're going to build it, this makes * sure that at least the admin is built for users & they can interact * with the application. */ if (!watchAdmin && buildAdmin) { timer$1.start('createBuildContext'); const contextSpinner = logger.spinner(`Building build context`).start(); console.log(''); const ctx = await buildCtx().createBuildContext({ cwd, logger, tsconfig, options }); const contextDuration = timer$1.end('createBuildContext'); contextSpinner.text = `Building build context (${timer.prettyTime(contextDuration)})`; contextSpinner.succeed(); timer$1.start('creatingAdmin'); const adminSpinner = logger.spinner(`Creating admin`).start(); await staticFs().writeStaticClientFiles(ctx); if (ctx.bundler === 'webpack') { const { build: buildWebpack } = await Promise.resolve().then(function () { return require('./webpack/build.js'); }); await buildWebpack(ctx); } else if (ctx.bundler === 'vite') { const { build: buildVite } = await Promise.resolve().then(function () { return require('./vite/build.js'); }); await buildVite(ctx); } const adminDuration = timer$1.end('creatingAdmin'); adminSpinner.text = `Creating admin (${timer.prettyTime(adminDuration)})`; adminSpinner.succeed(); } cluster.on('message', async (worker, message)=>{ switch(message){ case 'reload': { if (tsconfig?.config) { try { // Build without diagnostics in case schemas have changed await cleanupDistDirectory({ tsconfig, logger, timer: timer$1 }); await tsUtils().compile(cwd, { configOptions: { ignoreDiagnostics: true } }); } catch (err) { const message = err instanceof Error ? err.message : String(err); logger.error(`Error during TypeScript compilation on reload: ${message}`); process.exit(1); } } logger.debug('cluster has the reload message, sending the worker kill message'); worker.send('kill'); break; } case 'killed': { logger.debug('cluster has the killed message, forking the cluster'); cluster.fork(); break; } case 'stop': { process.exit(1); break; } } }); cluster.fork(); } if (cluster.isWorker) { timer$1.start('loadStrapi'); const loadStrapiSpinner = logger.spinner(`Loading Strapi`).start(); const strapi = core().createStrapi({ appDir: cwd, distDir: tsconfig?.config.options.outDir ?? '', autoReload: true, serveAdminPanel: !watchAdmin }); /** * If we're watching the admin panel then we're going to attach the watcher * as a strapi middleware. */ let bundleWatcher; const strapiInstance = await strapi.load(); const contextSpinner = logger.spinner(`Building build context`); const adminSpinner = logger.spinner(`Creating admin`); const generatingTsSpinner = logger.spinner(`Generating types`); const compilingTsSpinner = logger.spinner(`Compiling TS`); let watcherStarted = false; const ensureWatcher = ()=>{ if (!watcherStarted) { watcherStarted = true; startWatcher(strapiInstance, cwd, polling ?? false, logger, bundleWatcher); } }; try { if (watchAdmin) { timer$1.start('createBuildContext'); contextSpinner.start(); const ctx = await buildCtx().createBuildContext({ cwd, logger, strapi, tsconfig, options }); const contextDuration = timer$1.end('createBuildContext'); contextSpinner.text = `Building build context (${timer.prettyTime(contextDuration)})`; contextSpinner.succeed(); timer$1.start('creatingAdmin'); adminSpinner.start(); await staticFs().writeStaticClientFiles(ctx); if (ctx.bundler === 'webpack') { const { watch: watchWebpack } = await Promise.resolve().then(function () { return require('./webpack/watch.js'); }); bundleWatcher = await watchWebpack(ctx); } else if (ctx.bundler === 'vite') { const { watch: watchVite } = await Promise.resolve().then(function () { return require('./vite/watch.js'); }); bundleWatcher = await watchVite(ctx); } const adminDuration = timer$1.end('creatingAdmin'); adminSpinner.text = `Creating admin (${timer.prettyTime(adminDuration)})`; adminSpinner.succeed(); } const loadStrapiDuration = timer$1.end('loadStrapi'); loadStrapiSpinner.text = `Loading Strapi (${timer.prettyTime(loadStrapiDuration)})`; loadStrapiSpinner.succeed(); // For TS projects, type generation is a requirement for the develop command so that the server can restart // For JS projects, we respect the experimental autogenerate setting if (tsconfig?.config || strapi.config.get('typescript.autogenerate') !== false) { timer$1.start('generatingTS'); generatingTsSpinner.start(); await tsUtils().generators.generate({ strapi: strapiInstance, pwd: cwd, rootDir: undefined, logger: { silent: true, debug: false }, artifacts: { contentTypes: true, components: true } }); const generatingDuration = timer$1.end('generatingTS'); generatingTsSpinner.text = `Generating types (${timer.prettyTime(generatingDuration)})`; generatingTsSpinner.succeed(); } if (tsconfig?.config) { timer$1.start('compilingTS'); compilingTsSpinner.start(); await cleanupDistDirectory({ tsconfig, logger, timer: timer$1 }); await tsUtils().compile(cwd, { configOptions: { ignoreDiagnostics: false } }); const compilingDuration = timer$1.end('compilingTS'); compilingTsSpinner.text = `Compiling TS (${timer.prettyTime(compilingDuration)})`; compilingTsSpinner.succeed(); } ensureWatcher(); strapiInstance.start(); } catch (err) { const message = err instanceof Error ? err.message : String(err); logger.error(`Error during development: ${message}`); if (loadStrapiSpinner.isSpinning) { loadStrapiSpinner.fail(); } // Fail any spinners that were left running. if (contextSpinner.isSpinning) { contextSpinner.fail(); } if (compilingTsSpinner.isSpinning) { compilingTsSpinner.fail(); } if (adminSpinner.isSpinning) { adminSpinner.fail(); } if (generatingTsSpinner.isSpinning) { generatingTsSpinner.fail(); } ensureWatcher(); } } }; function startWatcher(strapiInstance, cwd, polling, logger, bundleWatcher) { const restart = async ()=>{ if (strapiInstance.reload.isWatching && !strapiInstance.reload.isReloading) { strapiInstance.reload.isReloading = true; strapiInstance.reload(); } }; const watcher = chokidar().watch(cwd, { ignoreInitial: true, usePolling: polling, ignored: [ /(^|[/\\])\../, /tmp/, '**/src/admin/**', '**/src/plugins/**/admin/**', '**/dist/src/plugins/test/admin/**', '**/documentation', '**/documentation/**', '**/node_modules', '**/node_modules/**', '**/plugins.json', '**/build', '**/build/**', '**/log', '**/log/**', '**/logs', '**/logs/**', '**/*.log', '**/index.html', '**/public', '**/public/**', strapiInstance.dirs.static.public, utils().strings.joinBy('/', strapiInstance.dirs.static.public, '**'), '**/*.db*', '**/exports/**', '**/dist/**', '**/*.d.ts', '**/.yalc/**', '**/yalc.lock', // TODO v6: watch only src folder by default, and flip this to watchIncludeFiles ...strapiInstance.config.get('admin.watchIgnoreFiles', []) ] }).on('add', (path)=>{ strapiInstance.log.info(`File created: ${path}`); restart(); }).on('change', (path)=>{ strapiInstance.log.info(`File changed: ${path}`); restart(); }).on('unlink', (path)=>{ strapiInstance.log.info(`File deleted: ${path}`); restart(); }); process.on('message', async (message)=>{ switch(message){ case 'kill': { logger.debug('child process has the kill message, destroying the strapi instance and sending the killed process message'); await watcher.close(); await strapiInstance.destroy(); if (bundleWatcher) { bundleWatcher.close(); } process.send?.('killed'); break; } } }); } exports.develop = develop; //# sourceMappingURL=develop.js.map