Skip to content

Commit

Permalink
feat: improve typescript build process; automatically (re)build repli…
Browse files Browse the repository at this point in the history
…cant schemas (#93)
  • Loading branch information
Alex authored Jun 12, 2023
1 parent ae20746 commit 32ed8a9
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 108 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ jobs:
key: npm-${{ runner.os }}-${{ hashFiles('yarn.lock') }}

- name: Install dependencies
run: yarn --ignore-engines --frozen-lockfile --network-timeout 1000000
run: npm ci

- name: Lint
run: |
npm run lint
build-test:
strategy:
fail-fast: false
matrix:
node-version: [16.x, 18.x]
os: [ubuntu-latest, windows-latest, macos-latest]
Expand All @@ -58,12 +59,15 @@ jobs:
key: npm-${{ runner.os }}-${{ hashFiles('yarn.lock') }}

- name: Install dependencies
run: yarn --ignore-engines --frozen-lockfile --network-timeout 1000000
run: npm ci

- name: Build
run: |
npm run build
- name: Install nodecg-cli to use in tests
run: npm i -g nodecg-cli@latest

- name: Test
run: |
npm run test
Expand Down
11 changes: 10 additions & 1 deletion src/generators/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ export default class AppGenerator extends Generator {
'build:extension': 'node scripts/build.mjs --extension',
watch: 'node scripts/build.mjs --all --watch',
'watch:browser': 'node scripts/build.mjs --dashboard --graphics --watch',
dev: 'concurrently --kill-others "npm run watch:browser" "nodemon"',
'watch:schemas': 'node scripts/build.mjs --schemas --watch',
dev: 'concurrently --kill-others "npm run watch:schemas" "npm run watch:browser" "nodemon"',
'generate-schema-types': 'trash src/types/schemas && nodecg schema-types',
};
/* eslint-enable @typescript-eslint/naming-convention */
Expand Down Expand Up @@ -253,6 +254,10 @@ export default class AppGenerator extends Generator {
this.fs.copy(this.templatePath('scripts/build.mjs'), this.destinationPath('scripts/build.mjs'));
}

if (!this.fs.exists(this.destinationPath('scripts/debounce.mjs'))) {
this.fs.copy(this.templatePath('scripts/debounce.mjs'), this.destinationPath('scripts/debounce.mjs'));
}

if (!this.fs.exists(this.destinationPath('.parcelrc'))) {
this.fs.copy(this.templatePath('.parcelrc'), this.destinationPath('.parcelrc'));
}
Expand Down Expand Up @@ -282,6 +287,10 @@ export default class AppGenerator extends Generator {
this.fs.copy(this.templatePath('nodemon.json'), this.destinationPath('nodemon.json'));
}

if (!this.fs.exists(this.destinationPath('.eslintignore'))) {
this.fs.copy(this.templatePath('.eslintignore'), this.destinationPath('.eslintignore'));
}

await this.addDependencies(['ts-node']);
await this.addDevDependencies([
'typescript',
Expand Down
2 changes: 2 additions & 0 deletions src/generators/app/templates/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
/scripts
192 changes: 109 additions & 83 deletions src/generators/app/templates/scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,115 +6,141 @@
// Native
import { fileURLToPath } from 'url';
import { argv } from 'process';
import { execSync } from 'child_process';

// Packages
import { glob } from 'glob';
import { Parcel } from '@parcel/core';
import chokidar from 'chokidar';

// Ours
import pjson from '../package.json' assert { type: 'json' };
import debounce from './debounce.mjs';

const buildAll = argv.includes('--all');
const buildExtension = argv.includes('--extension') || buildAll;
const buildDashboard = argv.includes('--dashboard') || buildAll;
const buildGraphics = argv.includes('--graphics') || buildAll;
const buildSchemas = argv.includes('--schemas') || buildAll;

const bundlers = new Set();
const commonBrowserTargetProps = {
engines: {
browsers: ['last 5 Chrome versions'],
},
context: 'browser',
engines: {
browsers: ['last 5 Chrome versions'],
},
context: 'browser',
};

if (buildDashboard) {
bundlers.add(
new Parcel({
entries: glob.sync('src/dashboard/**/*.html'),
targets: {
default: {
...commonBrowserTargetProps,
distDir: 'dashboard',
publicUrl: `/bundles/${pjson.name}/dashboard`,
},
},
defaultConfig: '@parcel/config-default',
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
}),
);
bundlers.add(
new Parcel({
entries: glob.sync('src/dashboard/**/*.html'),
targets: {
default: {
...commonBrowserTargetProps,
distDir: 'dashboard',
publicUrl: `/bundles/${pjson.name}/dashboard`,
},
},
defaultConfig: '@parcel/config-default',
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
}),
);
}

if (buildGraphics) {
bundlers.add(
new Parcel({
entries: glob.sync('src/graphics/**/*.html'),
targets: {
default: {
...commonBrowserTargetProps,
distDir: 'graphics',
publicUrl: `/bundles/${pjson.name}/graphics`,
},
},
defaultConfig: '@parcel/config-default',
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
}),
);
bundlers.add(
new Parcel({
entries: glob.sync('src/graphics/**/*.html'),
targets: {
default: {
...commonBrowserTargetProps,
distDir: 'graphics',
publicUrl: `/bundles/${pjson.name}/graphics`,
},
},
defaultConfig: '@parcel/config-default',
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
}),
);
}

if (buildExtension) {
bundlers.add(
new Parcel({
entries: 'src/extension/index.ts',
targets: {
default: {
context: 'node',
distDir: 'extension',
},
},
defaultConfig: '@parcel/config-default',
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
}),
);
bundlers.add(
new Parcel({
entries: 'src/extension/index.ts',
targets: {
default: {
context: 'node',
distDir: 'extension',
},
},
defaultConfig: '@parcel/config-default',
additionalReporters: [
{
packageName: '@parcel/reporter-cli',
resolveFrom: fileURLToPath(import.meta.url),
},
],
}),
);
}

try {
if (argv.includes('--watch')) {
const watchPromises = [];
for (const bundler of bundlers.values()) {
watchPromises.push(
bundler.watch((err) => {
if (err) {
// fatal error
throw err;
}
}),
);
}
await Promise.all(watchPromises);
} else {
const buildPromises = [];
for (const bundler of bundlers.values()) {
buildPromises.push(bundler.run());
}
await Promise.all(buildPromises);
}
console.log('Bundle build completed successfully');
if (argv.includes('--watch')) {
if (buildSchemas) {
watchSchemas();
}

const watchPromises = [];
for (const bundler of bundlers.values()) {
watchPromises.push(
bundler.watch((err) => {
if (err) {
// fatal error
throw err;
}
}),
);
}

await Promise.all(watchPromises);
} else {
if (buildSchemas) {
doBuildSchemas();
}

const buildPromises = [];
for (const bundler of bundlers.values()) {
buildPromises.push(bundler.run());
}

await Promise.all(buildPromises);
}

console.log('Bundle build completed successfully');
} catch (_) {
// the reporter-cli package will handle printing errors to the user
process.exit(1);
// the reporter-cli package will handle printing errors to the user
process.exit(1);
}

function doBuildSchemas() {
execSync('npm run generate-schema-types');
process.stdout.write(`🔧 Built Replicant schema types!\n`);
}

function watchSchemas() {
chokidar.watch('schemas/**/*.json').on('all', () => {
debounce('compileSchemas', doBuildSchemas);
});
}
19 changes: 19 additions & 0 deletions src/generators/app/templates/scripts/debounce.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const timers = new Map();

/**
* A standard debounce, but uses a string `name` as the key instead of the callback.
*/
export default function (name, callback, duration = 500) {
const existing = timers.get(name);
if (existing) {
clearTimeout(existing);
}

timers.set(
name,
setTimeout(() => {
timers.delete(name);
callback();
}, duration),
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* tslint:disable */
/* eslint:disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
Expand Down
21 changes: 0 additions & 21 deletions test/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,27 +183,6 @@ describe('nodecg:app', () => {
assert.fileContent('typescript-bundle/package.json', '"concurrently"');
});

it('adds build scripts to package.json', () => {
assert.fileContent('typescript-bundle/package.json', '"build": "node scripts/build.mjs"');
assert.fileContent(
'typescript-bundle/package.json',
'"build:extension": "node scripts/build.mjs --skipBrowser"',
);
assert.fileContent('typescript-bundle/package.json', '"watch": "node scripts/build.mjs --watch"');
assert.fileContent(
'typescript-bundle/package.json',
'"watch:browser": "node scripts/build.mjs --skipExtension --watch"',
);
assert.fileContent(
'typescript-bundle/package.json',
`"dev": "concurrently --kill-others \\"npm run watch:browser\\" \\"nodemon\\""`,
);
assert.fileContent(
'typescript-bundle/package.json',
'"generate-schema-types": "trash src/types/schemas && nodecg schema-types"',
);
});

it('generates an actually buildable bundle', async function () {
// Increase timeout because npm install (and build) can take some time...
this.timeout(300000); // 5 minutes
Expand Down

0 comments on commit 32ed8a9

Please sign in to comment.