Skip to content

Commit

Permalink
feat: use typescript with wing cli and worker thread for preflight (#…
Browse files Browse the repository at this point in the history
…5247)

#### General Vibe
- This is an MVP of getting TS working via the wing cli (`compile`, `run`, and `test`). This is basically only usable for preflight
- I wanted to change as little as possible in the SDK to avoid wasted work once resources are moved to winglibs
- Does not address inflight stuff yet

The entrypoint TS experience is different than the existing issue/story:

```ts
import { main } from "ts4wing";

main((app) => {
  // stuff goes here
})
```

This was due to our usage of a "root" construct for testing isolation. The wing cli needs to have control over the instantiation of resources, so `new App()` is not really feasible because we don't want users to actually attach stuff at the app-level. It may be possible to get it working with `new App()` but the hacks needed didn't seem worth it.
An added benefit of this approach is that the user no longer needs to call `app.synth()`

Anecdotally, I have built an awscdk-based internal framework at a previous job that looked like this for a similar reason (to duplicate stuff across Stages) and it worked pretty well.

#### Indirect Changes
- Made sure everything matches their `@types/node` version
- **preflight execution now happens in a worker thread instead of a VM**
  - VM context did not allow data to be provided past the initially loaded file.
  - In a VM, process.env is useful but pollutes the parent process
  - The overloaded "require" was only useful for the initial file. Now I added a shim that overloads require everywhere to make sure the SDK is loaded properly

Fixes #3678

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
MarkMcCulloh authored Dec 20, 2023
1 parent 7cbfa37 commit e6e06a0
Show file tree
Hide file tree
Showing 46 changed files with 6,093 additions and 5,323 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ jobs:
set -o pipefail
cd dist
PACKAGES=("@winglang/sdk" "@winglang/compiler" "@wingconsole/design-system" "@wingconsole/ui" "@wingconsole/server" "@wingconsole/app" "winglang" "@winglang/platform-awscdk")
PACKAGES=("@winglang/sdk" "ts4w" "@winglang/compiler" "@wingconsole/design-system" "@wingconsole/ui" "@wingconsole/server" "@wingconsole/app" "winglang" "@winglang/platform-awscdk")
for PACKAGE in "${PACKAGES[@]}"; do
# Check if already published
VERSION_FOUND=$(npm view "$PACKAGE@$PACKAGE_VERSION" version --verbose || true)
Expand Down
5 changes: 5 additions & 0 deletions apps/jsii-docgen/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/jsii-docgen/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions apps/jsii-docgen/.projenrc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { typescript, javascript } from "projen";
import { typescript, javascript, DependencyType } from "projen";

const project = new typescript.TypeScriptProject({
name: "@winglang/jsii-docgen",
Expand All @@ -19,7 +19,6 @@ const project = new typescript.TypeScriptProject({
"@types/fs-extra",
"@types/semver",
"@types/yargs@^16",
"@types/node",
"constructs",
],
deps: [
Expand Down Expand Up @@ -73,4 +72,6 @@ project.addFields({
project.package.file.addDeletionOverride("pnpm");
project.tryRemoveFile(".npmrc");

project.deps.addDependency("@types/node@^18.17.13", DependencyType.DEVENV);

project.synth();
2 changes: 1 addition & 1 deletion apps/jsii-docgen/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/jsii-docgen/test/docgen/view/_npm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ class MockChildProcess extends EventEmitter implements ChildProcess {
public unref(): never {
throw new UnsupportedCallError();
}

[Symbol.dispose](): void {}
}

export class UnsupportedCallError extends Error {
Expand Down
5 changes: 5 additions & 0 deletions apps/vscode-wing/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/vscode-wing/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions apps/vscode-wing/.projenrc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IgnoreFile } from "projen";
import { IgnoreFile, DependencyType } from "projen";
import { NodePackageManager } from "projen/lib/javascript";
import { TypeScriptAppProject } from "projen/lib/typescript";
import { VSCodeExtensionContributions } from "./src/project/vscode_types";
Expand Down Expand Up @@ -61,7 +61,6 @@ const project = new TypeScriptAppProject({
"ws",
"open",
"node-fetch@^2.6.7",
"@types/node",
"@types/which",
"@vscode/vsce",
"@types/node-fetch",
Expand Down Expand Up @@ -251,4 +250,6 @@ project.tryRemoveFile(".npmrc");

project.addTask("dev").exec("node scripts/dev.mjs");

project.deps.addDependency("@types/node@^18.17.13", DependencyType.DEVENV);

project.synth();
2 changes: 1 addition & 1 deletion apps/vscode-wing/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions apps/wing-api-checker/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/wing-api-checker/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions apps/wing-api-checker/.projenrc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { javascript, typescript } from "projen";
import { javascript, typescript, DependencyType } from "projen";

const project = new typescript.TypeScriptProject({
defaultReleaseBranch: "main",
Expand All @@ -20,7 +20,7 @@ const project = new typescript.TypeScriptProject({
prettier: true,
package: false,
deps: ["chalk", "chokidar", "glob-promise", "jsii-reflect", "yargs"],
devDeps: ["@types/node@^18", "@types/yargs"],
devDeps: ["@types/yargs"],
});

const bumpTask = project.tasks.tryFind("bump")!;
Expand All @@ -37,4 +37,6 @@ project.addFields({
project.package.file.addDeletionOverride("pnpm");
project.tryRemoveFile(".npmrc");

project.deps.addDependency("@types/node@^18.17.13", DependencyType.DEVENV);

project.synth();
2 changes: 1 addition & 1 deletion apps/wing-api-checker/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/ts-fixture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TypeScript Wing Project
16 changes: 16 additions & 0 deletions examples/ts-fixture/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ts-fixture",
"version": "0.0.0",
"scripts": {
"compile": "wing compile ./src/main.ts",
"test": "wing test ./src/main.ts"
},
"dependencies": {
"ts4w": "workspace:^",
"@winglang/sdk": "workspace:^",
"winglang": "workspace:^"
},
"volta": {
"extends": "../../package.json"
}
}
6 changes: 6 additions & 0 deletions examples/ts-fixture/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { main } from "ts4w";
import { cloud } from "@winglang/sdk";

main((app) => {
new cloud.Bucket(app, "Bucket");
});
10 changes: 10 additions & 0 deletions examples/ts-fixture/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://turborepo.org/schema.json",
"extends": ["//"],
"pipeline": {
"compile": {
"outputs": ["src/target/**"]
},
"test": {}
}
}
1 change: 1 addition & 0 deletions libs/ts4w/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
17 changes: 17 additions & 0 deletions libs/ts4w/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ts4w - Experimental TypeScript experience for Wing

```ts
// main.ts
import { main } from "ts4w";
import { cloud } from "@winglang/sdk";

main((app) => {
new cloud.Bucket(app, "Bucket");
})
```

```shell
wing compile main.ts
wing compile -t tf-aws main.ts
wing test main.ts
```
44 changes: 44 additions & 0 deletions libs/ts4w/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "ts4w",
"version": "0.0.0",
"author": {
"name": "Wing Cloud",
"email": "[email protected]",
"organization": true
},
"repository": {
"type": "git",
"url": "https://github.com/winglang/wing.git",
"directory": "libs/ts4w"
},
"description": "Experimental TypeScript experience for Wing",
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"compile": "tsup",
"package": "bump-pack -b"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org",
"tag": "latest"
},
"dependencies": {
"@winglang/sdk": "workspace:^",
"constructs": "~10.2.69"
},
"peerDependencies": {
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/node": "^18.17.13",
"tsup": "^8.0.1",
"bump-pack": "workspace:^"
},
"files": [
"dist"
],
"volta": {
"extends": "../../package.json"
}
}
76 changes: 76 additions & 0 deletions libs/ts4w/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { std, platform } from "@winglang/sdk";
import { Construct } from "constructs";

export * as internal from "./internal";

/**
* Properties for a Wing app.
*/
export interface AppProps {
/**
* The name and id of the app.
* @default "main"
*/
name?: string;
}

/**
* Create a Wing app.
*
* ```ts
* import { main } from "ts4w";
* import { cloud } from "@winglang/sdk";
*
* main(app => {
* new cloud.Bucket(app, "Bucket");
* });
* ```
*
* @param fn Define your application using the provided root construct.
* Note that this function may be called multiple times when used with `wing test`.
*/
export function main(fn: (root: Construct) => void, props: AppProps = {}) {
// check if we have everything we need
const requiredEnvVars = [
"WING_PLATFORMS",
"WING_SYNTH_DIR",
"WING_SOURCE_DIR",
"WING_IS_TEST",
];
for (const envVar of requiredEnvVars) {
if (process.env[envVar] === undefined) {
throw new Error(`\
Missing environment variable: ${envVar}
This is a Wing app and must be run through the Wing CLI (npm install -f winglang).`);
}
}

class $Root extends std.Resource {
constructor(scope: Construct, id: string) {
super(scope, id);
fn(this);
}
}

const platformPaths = ((s) => (!s ? [] : s.split(";")))(
process.env.WING_PLATFORMS
);
const outdir = process.env.WING_SYNTH_DIR;
const name = props.name ?? "main";
const rootConstruct = $Root;
const isTestEnvironment = process.env.WING_IS_TEST === "true";
const entrypointDir = process.env.WING_SOURCE_DIR!;
const rootId = process.env.WING_ROOT_ID;

const $PlatformManager = new platform.PlatformManager({ platformPaths });
const app = $PlatformManager.createApp({
outdir,
name,
rootConstruct,
isTestEnvironment,
entrypointDir,
rootId,
});

app.synth();
}
43 changes: 43 additions & 0 deletions libs/ts4w/src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { join } from "path";

export interface CompileOptions {
workDir: string;
entrypoint: string;
}

export async function compile(options: CompileOptions) {
const ts = (await import("typescript")).default;
const outDir = join(options.workDir, "ts");

const program = ts.createProgram([options.entrypoint], {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
alwaysStrict: true,
allowSyntheticDefaultImports: true,
esModuleInterop: true,
strict: true,
sourceMap: true,
outDir,
noEmitOnError: true,
listEmittedFiles: true,
})
const results = program.emit();

for (const diagnostic of results.diagnostics) {
console.error(diagnostic.messageText);
}
if (results.emitSkipped) {
throw new Error("TS compilation failed");
}

// get the last .js file emitted, this should be the entrypoint
const emittedFiles = results.emittedFiles?.filter((f) => f.endsWith(".js"));
const emittedFile = emittedFiles?.[emittedFiles.length - 1];

if (!emittedFile) {
throw new Error(`TS compilation failed: Could not find emitted file in ${outDir}`);
}

return emittedFile;
}
Loading

0 comments on commit e6e06a0

Please sign in to comment.