Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for --build --verbose invocations #590

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
295 changes: 295 additions & 0 deletions packages/core/__tests__/cli/build.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { existsSync, statSync } from 'fs';
import { sep } from 'path';

import { stripIndent } from 'common-tags';
import stripAnsi = require('strip-ansi');
Expand All @@ -15,6 +16,11 @@ import {
setupCompositeProject,
} from 'glint-monorepo-test-utils';

const TIMESTAMP = /\d{1,2}:\d{2}:\d{2} (AM|PM)/g;
let EPHEMERAL = new RegExp(`(test-packages${sep}ephemeral${sep}[\\d\\w]+)${sep}`);

// <time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/d526aeff72031/c/tsconfig.json'...

describe('CLI: single-pass build mode typechecking', () => {
describe('simple projects using `--build`', () => {
let project!: Project;
Expand Down Expand Up @@ -1067,6 +1073,65 @@ describe('CLI: single-pass build mode typechecking', () => {
});
});
});

describe('with `--verbose`', () => {
test('for a valid composite subproject with a reference', async () => {
let checkResult = await projects.children.a.build({ reject: false, flags: ['--verbose'] });

expect(checkResult.stdout.replace(TIMESTAMP, '<time stamp>')).toMatchInlineSnapshot(`
"<time stamp> - Projects in this build:
* ../c/tsconfig.json
* tsconfig.json

<time stamp> - Project '../c/tsconfig.json' is out of date because output file '../c/tsconfig.tsbuildinfo' does not exist

<time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/d75a8393430fd/c/tsconfig.json'...

<time stamp> - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist

<time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/d75a8393430fd/a/tsconfig.json'...
"
`);
});

test('for a project transitively referenced by the root with a template type error, built from the main project', async () => {
let cCode = stripIndent`
const double = (n: number) => n * 2;
const useDouble = <template>{{double "hello"}}</template>;
const C = 'world';
export default C;
`;

projects.children.c.write(INPUT_SFC, cCode);

let checkResult = await projects.main.build({ reject: false, flags: ['--verbose'] });

expect(checkResult.stdout).toMatchInlineSnapshot(`
"12:10:54 PM - Projects in this build:
* ../c/tsconfig.json
* ../a/tsconfig.json
* ../b/tsconfig.json
* tsconfig.json

12:10:54 PM - Project '../c/tsconfig.json' is out of date because output file '../c/tsconfig.tsbuildinfo' does not exist

12:10:54 PM - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/c/tsconfig.json'...

12:10:55 PM - Project '../a/tsconfig.json' can't be built because its dependency '../c' has errors

12:10:55 PM - Skipping build of project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/a/tsconfig.json' because its dependency '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/c' has errors

12:10:55 PM - Project '../b/tsconfig.json' is out of date because output file '../b/tsconfig.tsbuildinfo' does not exist

12:10:55 PM - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/b/tsconfig.json'...

12:10:55 PM - Project 'tsconfig.json' can't be built because its dependency '../a' was not built

12:10:55 PM - Skipping build of project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/main/tsconfig.json' because its dependency '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/a' was not built
"
`);
});
});
});
});

Expand Down Expand Up @@ -1606,3 +1671,233 @@ describe('CLI: --build --dry', () => {
});
});
});

describe('CLI: --build --verbose', () => {
describe('for basic projects', () => {
let project!: Project;
beforeEach(async () => {
project = await Project.createExact(BASE_TS_CONFIG);
});

afterEach(async () => {
await project.destroy();
});

test('prints verbose output for a valid basic project', async () => {
let code = stripIndent`
import '@glint/environment-ember-template-imports';
import Component from '@glimmer/component';

type ApplicationArgs = {
version: string;
};

export default class Application extends Component<{ Args: ApplicationArgs }> {
private startupTime = new Date().toISOString();

<template>
Welcome to app v{{@version}}.
The current time is {{this.startupTime}}.
</template>
}
`;

project.write(INPUT_SFC, code);

let checkResult = await project.build({ reject: false, flags: ['--verbose'] });

expect(checkResult.exitCode).toBe(0);
expect(checkResult.stdout.replace(TIMESTAMP, '<time stamp>')).toMatchInlineSnapshot(`
"<time stamp> - Projects in this build:
* tsconfig.json

<time stamp> - Project 'tsconfig.json' is out of date because output file 'dist/tsconfig.tsbuildinfo' does not exist

<time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/d6cdd8c6a1fd6/tsconfig.json'...
"
`);
expect(checkResult.stderr).toEqual('');
});

test('prints verbose output for a project with a basic template type error', async () => {
let code = stripIndent`
import '@glint/environment-ember-template-imports';
import Component from '@glimmer/component';

type ApplicationArgs = {
version: string;
};

const truncate = (length: number, s: string): string =>
s.slice(0, length);

export default class Application extends Component<{ Args: ApplicationArgs }> {
private startupTime = new Date().toISOString();

<template>
Welcome to app v{{@version}}.
The current time is {{truncate this.startupTime 12}}.
</template>
}
`;

project.write(INPUT_SFC, code);

let checkResult = await project.build({ reject: false, flags: ['--verbose'] });

expect(checkResult.exitCode).toBe(1);
expect(checkResult.stdout.replace(TIMESTAMP, '<time stamp>')).toMatchInlineSnapshot(`
"<time stamp> - Projects in this build:
* tsconfig.json

<time stamp> - Project 'tsconfig.json' is out of date because output file 'dist/tsconfig.tsbuildinfo' does not exist

<time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e994479a9ca/tsconfig.json'...
"
`);
expect(stripAnsi(checkResult.stderr)).toMatchInlineSnapshot(`
"src/index.gts:16:36 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

16 The current time is {{truncate this.startupTime 12}}.
~~~~~~~~~~~~~~~~
"
`);
});
});

describe('for composite projects', () => {
// The basic structure here is designed to give minimal coverage over all
// interesting combinations of project invalidation:
//
// - main -> a invalidated itself
// - main -> a invalidated via c invalidated
// - main -> b invalidated
// - a invalidated itself
// - a -> c invalidated
// - b invalidated
// - c invalidated
//
// The `root` is the workspace root, while the others are packages nested
// within the workspace. There are other possible designs, but this is the
// most common and the one we teach.
let projects!: {
root: Project;
main: Project;
children: {
a: Project;
b: Project;
c: Project;
};
};

beforeEach(async () => {
projects = await setupCompositeProject();
});

afterEach(async () => {
// Invariant: this will clean up properly as long as `destroy()` continues
// to recursively remove all directories *and* all child projects are
// rooted in the root project.
await projects.root.destroy();
});

beforeEach(async () => {
let rootCode = stripIndent`
import Component from '@glimmer/component';
import A from '@glint-test/a';
import B from '@glint-test/b';

type ApplicationArgs = {
version: string;
};

export default class Application extends Component<{ Args: ApplicationArgs }> {
private startupTime = new Date().toISOString();

<template>
Welcome to app v{{@version}}.
The current time is {{this.startupTime}}.
</template>
}
`;

let aCode = stripIndent`
import C from '@glint-test/c';
const A = 'hello ' + C;
export default A;
`;

let bCode = stripIndent`
const B = 'ahoy';
export default B;
`;

let cCode = stripIndent`
const C = 'world';
export default C;
`;

projects.main.write(INPUT_SFC, rootCode);
projects.children.a.write(INPUT_SFC, aCode);
projects.children.b.write(INPUT_SFC, bCode);
projects.children.c.write(INPUT_SFC, cCode);
});

test('for a valid composite subproject with a reference', async () => {
let checkResult = await projects.children.a.build({ reject: false, flags: ['--verbose'] });

expect(checkResult.stdout.replace(TIMESTAMP, '<time stamp>')).toMatchInlineSnapshot(`
"<time stamp> - Projects in this build:
* ../c/tsconfig.json
* tsconfig.json

<time stamp> - Project '../c/tsconfig.json' is out of date because output file '../c/tsconfig.tsbuildinfo' does not exist

<time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/d75a8393430fd/c/tsconfig.json'...

<time stamp> - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist

<time stamp> - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/d75a8393430fd/a/tsconfig.json'...
"
`);
});

test('for a project transitively referenced by the root with a template type error, built from the main project', async () => {
let cCode = stripIndent`
const double = (n: number) => n * 2;
const useDouble = <template>{{double "hello"}}</template>;
const C = 'world';
export default C;
`;

projects.children.c.write(INPUT_SFC, cCode);

let checkResult = await projects.main.build({ reject: false, flags: ['--verbose'] });

expect(checkResult.stdout).toMatchInlineSnapshot(`
"12:10:54 PM - Projects in this build:
* ../c/tsconfig.json
* ../a/tsconfig.json
* ../b/tsconfig.json
* tsconfig.json

12:10:54 PM - Project '../c/tsconfig.json' is out of date because output file '../c/tsconfig.tsbuildinfo' does not exist

12:10:54 PM - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/c/tsconfig.json'...

12:10:55 PM - Project '../a/tsconfig.json' can't be built because its dependency '../c' has errors

12:10:55 PM - Skipping build of project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/a/tsconfig.json' because its dependency '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/c' has errors

12:10:55 PM - Project '../b/tsconfig.json' is out of date because output file '../b/tsconfig.tsbuildinfo' does not exist

12:10:55 PM - Building project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/b/tsconfig.json'...

12:10:55 PM - Project 'tsconfig.json' can't be built because its dependency '../a' was not built

12:10:55 PM - Skipping build of project '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/main/tsconfig.json' because its dependency '/Users/ckrycho/dev/typed-ember/glint/test-packages/ephemeral/c9e5192458bc8/a' was not built
"
`);
});
});
});
7 changes: 7 additions & 0 deletions packages/core/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ const argv = yargs(process.argv.slice(2))
description: `Show what would be built (or deleted, if specified with '--clean'). Same as the TS \`--dry\` flag.`,
type: 'boolean',
})
.option('verbose', {
implies: 'build',
description:
'Prints out verbose logging to explain what’s going on (may be combined with any other flag). Same as the TS `--verbose` flag.',
type: 'boolean',
})
.option('incremental', {
description:
'Save .tsbuildinfo files to allow for incremental compilation of projects. Same as the TS `--incremental` flag.',
Expand Down Expand Up @@ -87,6 +93,7 @@ if (argv.build) {
clean: argv.clean,
force: argv.force,
dry: argv.dry,
verbose: argv.verbose,
};

if ('incremental' in argv) {
Expand Down
13 changes: 13 additions & 0 deletions test-packages/ts-template-imports-app/src/Playground.gts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ declare const CanvasThing: ComponentLike<{ Args: { str: string }; Element: HTMLC
declare const makeString: HelperLike<{ Args: { Named: { len: number } }; Return: string }>;
declare const drawCanvasStuff: ModifierLike<{ Args: { Named: { width: number; height: number } }; Element: HTMLCanvasElement }>;

declare const customModifierManagerExample: abstract new <El extends Element, Args extends Array<any>>() => InstanceType<ModifierLike<{
Element: El,
Args: {
Positional: [(element: El, args: Args) => void, ...Args]
}
}>>;

declare function exampleCallback(el: Element, pos: [number, string]): void;

export const CanvasPlayground = <template>
{{#let (component CanvasThing str="hi") as |BoundCanvasThing|}}
<BoundCanvasThing />
Expand All @@ -58,4 +67,8 @@ export const CanvasPlayground = <template>
{{#let (helper makeString len=5) (modifier drawCanvasStuff width=10) as |boundMakeString boundDrawCanvasStuff|}}
<CanvasThing @str={{(boundMakeString)}} {{boundDrawCanvasStuff height=5}} />
{{/let}}

{{#let (modifier customModifierManagerExample exampleCallback) as |m|}}
<div {{m}}></div>
{{/let}}
</template>