Skip to content

Commit ec0fb5c

Browse files
authored
fix(typesync cli): wireup typesync to make runnable. cleanup (#217)
1 parent d6be4c1 commit ec0fb5c

File tree

7 files changed

+132
-99
lines changed

7 files changed

+132
-99
lines changed

apps/typesync/README.md

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
# @graphprotocol/typesync
22

3-
CLI toolchain to view existing types, select, pick, extend to create schemas and generate a @graphprotocol/hypergraph schema.
3+
CLI toolchain to view existing types, select, pick, extend to create schemas and generate a [@graphprotocol/hypergraph](https://github.com/graphprotocol/hypergraph/tree/main/packages/hypergraph) schema.
44

5-
The `@graphprotocol/typesync` cli works by spinning up a [hono](https://hono.dev/) nodejs server that exposes a built vitejs react app. This app will let users see their created app schemas as well as search existing types to create new app schemas.
6-
Once the user has a schema built in the app, they can then run codegen, which will send a message to the server to codegen the built schema using the `@graphprotocol/hypergraph` framework.
5+
## Installing
76

8-
## Running Code
7+
```bash
8+
# npm
9+
npm i -g @graphprotocol/typesync-cli
910

10-
This template leverages [tsx](https://tsx.is) to allow execution of TypeScript files via NodeJS as if they were written in plain JavaScript.
11+
# yarn
12+
yarn global add @graphprotocol/typesync-cli
1113

12-
To execute a file with `tsx`:
13-
14-
```sh
15-
pnpm run dev
14+
# pnpm
15+
pnpm install -g @graphprotocol/typesync-cli
1616
```
1717

18-
## Operations
18+
## Running
1919

20-
**Building**
20+
```bash
21+
typsync --help
2122

22-
To build the package:
23+
# opening typesync studio
24+
typesync studio
2325

24-
```sh
25-
pnpm build
26+
# opening typesync studio in firefox automatically
27+
typesync studio --open --browser firefox
2628
```
2729

28-
**Testing**
29-
30-
To test the package:
31-
32-
```sh
33-
pnpm test
34-
```
30+
## Commands
3531

32+
- `studio` -> runs the `Typesync` api and client UI application for viewing created application schemas, browsing the Knowledge Graph, and creating new application schemas.
33+
- running: `typesync studio`
34+
- args:
35+
- `port` [OPTIONAL, default = 3000] port to run the application on
36+
- example: `typesync studio --port 3001`
37+
- `browser` [OPTION, default 'browser'] browser to open the app in, if the `--open` flag is passed
38+
- example: `typesync studio --open --browser firefox`

apps/typesync/package.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@graphprotocol/typesync-app",
2+
"name": "@graphprotocol/typesync-cli",
33
"version": "0.0.0-alpha",
44
"type": "module",
55
"license": "MIT",
@@ -11,22 +11,31 @@
1111
},
1212
"publishConfig": {
1313
"access": "public",
14-
"directory": "dist"
14+
"directory": "dist",
15+
"linkDirectory": false
1516
},
17+
"bin": {
18+
"typesync": "./dist/bin.cjs"
19+
},
20+
"files": ["README.md", "dist"],
1621
"scripts": {
1722
"codegen:gql": "graphql-codegen --config ./graphql.codegen.ts",
1823
"build:client": "vite build",
19-
"build": "pnpm run build:client && tsup && pnpm copy-package-json",
24+
"build": "rm -rf dist && pnpm run build:client && tsup && pnpm run copy-all",
2025
"build:ts": "tsup",
2126
"dev": "vite build && pnpx tsx ./src/bin.ts",
22-
"dev:cli": "pnpx tsx ./src/bin.ts",
27+
"dev:cli": "pnpx tsx ./src/bin.ts studio",
2328
"dev:client": "vite --force",
2429
"clean": "rimraf dist/*",
2530
"start": "node ./dist/bin.cjs",
2631
"check": "tsc --noEmit",
2732
"test": "vitest run",
2833
"coverage": "vitest run --coverage",
29-
"copy-package-json": "tsx scripts/copy-package-json.ts"
34+
"copy-package-json": "tsx scripts/copy-package-json.ts",
35+
"copy-db-migrations": "cp -rp ./src/migrations ./dist/migrations",
36+
"copy-client-dist": "mkdir -p ./dist/client && cp -rp ./client/dist ./dist/client/dist",
37+
"copy-all": "pnpm run copy-package-json && pnpm run copy-db-migrations && pnpm run copy-client-dist",
38+
"typesync": "pnpx tsx ./src/bin.ts studio"
3039
},
3140
"devDependencies": {
3241
"@effect/cli": "latest",

apps/typesync/scripts/copy-package-json.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const program = Effect.gen(function* () {
2424
homepage: json.homepage,
2525
tags: json.tags,
2626
keywords: json.keywords,
27+
exports: json.exports,
2728
};
2829
yield* fs.writeFileString(path.join('dist', 'package.json'), JSON.stringify(pkg, null, 2));
2930
yield* Effect.log('[Build] Build completed.');

apps/typesync/src/Api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ const ApiAppsLive = HttpApiBuilder.group(Api, 'Api', (handlers) =>
112112

113113
const payload = Schema.decodeUnknownSync(TypesyncDomain.InsertAppSchema)(body);
114114

115+
yield* Console.log(`Creating application ${payload.name} schema...`);
116+
115117
return yield* db.Apps.create(payload).pipe(
116118
Effect.tapError((err) => Console.error('POST /v1/apps - failure creating app schema', { err })),
117119
Effect.mapError((_) => new HttpApiError.InternalServerError()),
@@ -139,6 +141,7 @@ const ApiAppsLive = HttpApiBuilder.group(Api, 'Api', (handlers) =>
139141
Effect.mapError((_) => new HttpApiError.InternalServerError()),
140142
),
141143
),
144+
Effect.tap((app) => Console.log(`Application ${app.name} generated at ${app.directory}`)),
142145
);
143146
}),
144147
)

apps/typesync/src/Cli.ts

Lines changed: 8 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,15 @@
1-
// import type { ChildProcess } from 'node:child_process';
2-
import { createServer } from 'node:http';
31
import * as Command from '@effect/cli/Command';
4-
import * as Options from '@effect/cli/Options';
5-
import * as NodeHttpServer from '@effect/platform-node/NodeHttpServer';
6-
import * as HttpServer from '@effect/platform/HttpServer';
7-
// import * as Data from 'effect/Data';
8-
import * as Effect from 'effect/Effect';
9-
import * as Layer from 'effect/Layer';
10-
// import open, { apps } from 'open';
112

12-
import * as Server from './Server.js';
3+
import { studio } from './subcommands/studio.js';
134

14-
// class OpenBrowserError extends Data.TaggedError('OpenBrowserError')<{
15-
// readonly cause: unknown;
16-
// }> {}
17-
18-
const port = Options.integer('port').pipe(
19-
Options.withAlias('p'),
20-
Options.withDefault(3000),
21-
Options.withDescription('The port to run the server on. Default 3000'),
22-
);
23-
// const openApp = Options.boolean('open').pipe(
24-
// Options.withAlias('o'),
25-
// Options.withDefault(true),
26-
// Options.withDescription('If true, the TypeSync app will open in your default browser. Default true'),
27-
// );
28-
29-
const command = Command.make('typesync', { port }, ({ port }) =>
30-
Effect.gen(function* () {
31-
// const openBrowser = Effect.async<ChildProcess | undefined, Error>((resume) => {
32-
// if (!openApp) {
33-
// resume(Effect.succeed(undefined));
34-
// }
35-
// open(`http://localhost:${port}`, { app: { name: apps.firefox } }).then((subprocess) => {
36-
// // wait for child process to start befor succeeding
37-
// subprocess.on('spawn', () => {
38-
// resume(Effect.succeed(subprocess));
39-
// });
40-
// subprocess.on('error', (err) => {
41-
// resume(Effect.fail(new OpenBrowserError({ cause: err })));
42-
// });
43-
// });
44-
// });
45-
46-
// start serving api
47-
yield* Server.Server.pipe(
48-
HttpServer.withLogAddress,
49-
Layer.provide(
50-
NodeHttpServer.layer(createServer, { port }).pipe(
51-
// Layer.tap(() =>
52-
// openBrowser.pipe(
53-
// Effect.tapBoth({
54-
// onFailure(e) {
55-
// return Effect.logError('failure initializing and opening browser', e);
56-
// },
57-
// onSuccess(a) {
58-
// if (a) {
59-
// return Effect.logInfo(`TypeSync app opened in browser at http://localhost:${port}`);
60-
// }
61-
// return Effect.void;
62-
// },
63-
// }),
64-
// ),
65-
// ),
66-
),
67-
),
68-
Layer.launch,
69-
);
70-
}),
5+
const typesync = Command.make('typesync').pipe(
6+
Command.withDescription(
7+
'Typesync command line interface for building and interacting with @graphprotocol/hypergraph schemas',
8+
),
9+
Command.withSubcommands([studio]),
7110
);
7211

73-
export const run = Command.run(command, {
74-
name: '@graphprotocol/typesync',
12+
export const run = Command.run(typesync, {
13+
name: 'typesync',
7514
version: '0.0.0-alpha',
7615
});

apps/typesync/src/bin.ts

100644100755
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ import { run } from './Cli.js';
99
import { DatabaseServiceLive } from './Database.js';
1010
import { SchemaGeneratorLayer } from './Generator.js';
1111

12-
run(process.argv).pipe(
12+
const runnable = Effect.suspend(() => run(process.argv)).pipe(
1313
Effect.provide(DatabaseServiceLive),
1414
Effect.provide(SchemaGeneratorLayer),
1515
Effect.provide(NodeFileSystem.layer),
1616
Effect.provide(NodeContext.layer),
17-
NodeRuntime.runMain({
18-
disableErrorReporting: process.env.NODE_ENV === 'prod',
19-
}),
2017
);
18+
runnable.pipe(NodeRuntime.runMain({ disableErrorReporting: process.env.NODE_ENV === 'prod' }));
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { createServer } from 'node:http';
2+
import * as Command from '@effect/cli/Command';
3+
import * as Options from '@effect/cli/Options';
4+
import * as NodeHttpServer from '@effect/platform-node/NodeHttpServer';
5+
import * as HttpServer from '@effect/platform/HttpServer';
6+
import * as Console from 'effect/Console';
7+
import * as Data from 'effect/Data';
8+
import * as Effect from 'effect/Effect';
9+
import * as Layer from 'effect/Layer';
10+
import open, { type AppName } from 'open';
11+
12+
import * as Server from '../Server.js';
13+
14+
export const studio = Command.make('studio', {
15+
args: {
16+
port: Options.integer('port').pipe(
17+
Options.withAlias('p'),
18+
Options.withDefault(3000),
19+
Options.withDescription('The port to run the Typesync studio server on. Default 3000'),
20+
),
21+
open: Options.boolean('open').pipe(
22+
Options.withDescription('If true, opens the Typesync studio in your browser'),
23+
Options.withDefault(true),
24+
),
25+
browser: Options.choice('browser', ['chrome', 'firefox', 'edge', 'browser', 'browserPrivate']).pipe(
26+
Options.withAlias('b'),
27+
Options.withDescription('Broweser to open the Typesync studio app in. Default is your default selected browser'),
28+
Options.withDefault('browser'),
29+
),
30+
},
31+
}).pipe(
32+
Command.withDescription('Opens the Typesync studio for interacting with application schemas'),
33+
Command.withHandler(({ args }) =>
34+
Effect.gen(function* () {
35+
yield* Server.Server.pipe(
36+
HttpServer.withLogAddress,
37+
Layer.provide(NodeHttpServer.layer(createServer, { port: args.port })),
38+
Layer.tap(() =>
39+
Effect.gen(function* () {
40+
if (args.open) {
41+
return yield* openBrowser(args.port, args.browser).pipe(
42+
Effect.tapErrorCause((cause) =>
43+
Console.warn(
44+
`Failure opening Typesync studio in your browser. Open at http://localhost:${args.port}`,
45+
{
46+
cause,
47+
},
48+
),
49+
),
50+
Effect.orElseSucceed(() => Effect.void),
51+
);
52+
}
53+
return Effect.void;
54+
}),
55+
),
56+
Layer.tap(() => Console.log(`🎉 Typesync studio started and running at http://localhost:${args.port}`)),
57+
Layer.launch,
58+
);
59+
}),
60+
),
61+
);
62+
63+
const openBrowser = (port: number, browser: AppName) =>
64+
Effect.async<void, OpenBrowserError>((resume) => {
65+
open(`http://localhost:${port}`, {
66+
app: { name: browser },
67+
}).then((subprocess) => {
68+
// wait for child process to start before succeeding
69+
subprocess.on('spawn', () => {
70+
resume(Effect.void);
71+
});
72+
subprocess.on('error', (err) => {
73+
resume(Effect.fail(new OpenBrowserError({ cause: err })));
74+
});
75+
});
76+
});
77+
78+
export class OpenBrowserError extends Data.TaggedError('/typesync/errors/OpenBrowserError')<{
79+
readonly cause: unknown;
80+
}> {}

0 commit comments

Comments
 (0)