Skip to content

Commit

Permalink
Add init-v1, see phetsims/chipper#1345
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Jan 9, 2025
1 parent 1f5a031 commit f9c626a
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 1 deletion.
224 changes: 224 additions & 0 deletions js/scripts/init-v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright 2024, University of Colorado Boulder

/**
* Script to clone repositories, install dependencies, transpile code, and start an HTTP server.
*
* The script performs the following steps:
* 1. Clones any missing repositories.
* 2. Installs NPM dependencies in specified directories.
* 3. Starts the transpiler (`grunt transpile --live`) and monitors its output.
* 4. Once initial transpiling is complete, starts the HTTP server (`http-server -p 80 -c`).
* 5. Handles process lifecycle to keep both the transpiler and HTTP server running.
*
* TODO: https://github.com/phetsims/chipper/issues/1345
* "update-v1": "npm install && bash bin/sage run js/scripts/update-v1.ts && npm run start-v1",
* "start-v1": "npm install && bash bin/sage run js/scripts/start-v1.ts",
*
* @author Sam Reid (PhET Interactive Simulations)
*/

import { ChildProcess, spawn } from 'child_process';
import path from 'path';
import cloneMissingRepos from '../common/cloneMissingRepos.js';
import execute from '../common/execute.js';
import npmCommand from '../common/npmCommand.js';

// Log initial messages
console.log( 'hello' );
console.log( 'done cloning' );

// Configuration
const phetsimsDirectory = path.join( __dirname, '../../../' );
const chipperDir = path.join( phetsimsDirectory, 'chipper' );
const perennialAliasDir = path.join( phetsimsDirectory, 'perennial-alias' );
const transpileCwd = path.join( phetsimsDirectory, 'chipper' );

// State variables to track transpiling progress
let totalChunks = 0;
let successfulChunks = 0;
let httpServerStarted = false;

// References to child processes for graceful shutdown
let transpileProcess: ChildProcess | null = null;
let httpServerProcess: ChildProcess | null = null;

/**
* Starts the HTTP server using `http-server`.
*/
/**
* Starts the HTTP server using `http-server` and ensures custom messages are printed last.
*/
function startHttpServer(): void {
console.log( '\nStarting http-server...\n' );

httpServerProcess = spawn( 'http-server', [ '..', '-p', '80', '-c' ], {
shell: true,
stdio: [ 'ignore', 'pipe', 'pipe' ] // Use 'pipe' to capture stdout and stderr
} );

// Listen to stdout data
if ( httpServerProcess.stdout ) {
httpServerProcess.stdout.on( 'data', data => {
const output = data.toString();

// Check if the server has started by looking for the specific message
if ( data.includes( 'Hit CTRL-C to stop the server' ) ) {

process.stdout.write( output ); // Pipe to parent stdout

// Print the custom message after the server has fully started
console.log( 'Open http://localhost/phetmarks in your browser to view the sims.' );
}
} );
}

// Listen to stderr data and pipe to parent stderr
if ( httpServerProcess.stderr ) {
httpServerProcess.stderr.on( 'data', data => {
const errorOutput = data.toString();
process.stderr.write( errorOutput );
} );
}

// Handle HTTP server process events
httpServerProcess.on( 'error', error => {
console.error( 'HTTP server process error:', error );
process.exit( 1 );
} );

httpServerProcess.on( 'exit', ( code, signal ) => {
if ( code !== null ) {
console.log( `HTTP server exited with code ${code}` );
process.exit( code );
}
else {
console.log( `HTTP server was killed by signal ${signal}` );
process.exit( 1 );
}
} );

httpServerStarted = true;

console.log( 'http server started' );
// Remove the immediate console.log to prevent it from appearing before server messages
// console.log('Open http://localhost/phetmarks in your browser to view the sims.');
}

/**
* Starts the transpiler using `grunt transpile --live` and monitors its output.
*/
function startTranspiler(): void {
console.log( '\nStarting the transpiler...\n' );

transpileProcess = spawn( 'bash', [ '../perennial/bin/sage', 'run', 'js/grunt/tasks/transpile.ts', '--live' ], {
cwd: transpileCwd,
shell: true,
stdio: [ 'ignore', 'pipe', 'pipe' ] // Pipe stdout and stderr for monitoring
} );

// Handle transpiler process events
transpileProcess.on( 'error', error => {
console.error( 'Transpiler process error:', error );
process.exit( 1 );
} );

transpileProcess?.stdout?.on( 'data', data => {
const output = data.toString();
process.stdout.write( `transpile: ${output}` );

// Parse the number of chunks
const splitMatch = output.match( /split into (\d+) chunks/ );
if ( splitMatch && splitMatch[ 1 ] ) {
totalChunks = parseInt( splitMatch[ 1 ], 10 );
console.log( `Detected ${totalChunks} chunks for transpiling.` );
}

// Parse successful compilations
const successMatch = output.includes( 'Watching' );
if ( successMatch ) {
successfulChunks += 1;
console.log( `Chunk ${successfulChunks} compiled successfully.` );

// If all chunks are compiled, start the HTTP server
if ( totalChunks > 0 && successfulChunks === totalChunks && !httpServerStarted ) {
console.log( 'All chunks compiled successfully. Starting HTTP server...' );
startHttpServer();
}
}
} );

transpileProcess?.stderr?.on( 'data', data => {
const errorOutput = data.toString();
process.stderr.write( `transpile stderr: ${errorOutput}` );
} );

transpileProcess.on( 'exit', ( code, signal ) => {
if ( code !== 0 ) {
console.error( `Transpiler exited with code ${code} and signal ${signal}` );
process.exit( code );
}
else {
console.log( 'Transpiler exited successfully.' );
process.exit( 0 );
}
} );
}

/**
* Handles graceful shutdown of child processes upon receiving termination signals.
*/
function setupGracefulShutdown(): void {
const shutdown = () => {
console.log( '\nShutting down gracefully...' );

if ( transpileProcess ) {
transpileProcess.kill();
}

if ( httpServerProcess ) {
httpServerProcess.kill();
}

process.exit( 0 );
};

// Listen for termination signals
process.on( 'SIGINT', shutdown );
process.on( 'SIGTERM', shutdown );
}

/**
* Main asynchronous function to orchestrate cloning, installing, transpiling, and serving.
*/
( async () => {
try {
setupGracefulShutdown();

// Clone missing repositories
console.log( 'Cloning missing repositories...' );
const cloned = await cloneMissingRepos( true );
console.log( 'Cloned repositories:', cloned );

// Install NPM dependencies in ../chipper
console.log( '\nInstalling npm dependencies for chipper...' );
await execute( npmCommand, [ 'install' ], chipperDir );
console.log( 'NPM dependencies for chipper installed successfully.' );

// Install NPM dependencies in ../perennial-alias
console.log( '\nInstalling npm dependencies for perennial-alias...' );
await execute( npmCommand, [ 'install' ], perennialAliasDir );
console.log( 'NPM dependencies for perennial-alias installed successfully.' );

// Start the transpiler and monitor its output
startTranspiler();

// Keep the main process alive
await new Promise( () => {
// Do nothing, the process will be kept alive by the transpiler and HTTP server
} );
}
catch( error ) {
console.error( 'An error occurred:', error );
process.exit( 1 );
}
} )();
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"test-long": "npm run test && bash bin/sage run node_modules/qunit/bin/qunit.js js/test/perennialGruntTest.js",
"type-check-all": "grunt type-check --all --clean",
"lint-all": "grunt lint --all --clean",
"scenerystack-test": "bash bin/sage run js/scripts/scenerystack-test.ts"
"scenerystack-test": "bash bin/sage run js/scripts/scenerystack-test.ts",
"init-v1": "npm install && bash bin/sage run js/scripts/init-v1.ts && npm run start-v1"
},
"devDependencies": {
"@octokit/rest": "~16.33.1",
Expand Down

0 comments on commit f9c626a

Please sign in to comment.