spex-parser 0.1.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -1
- package/dist/ast.d.ts +70 -2
- package/dist/ast.d.ts.map +1 -1
- package/dist/constants.d.ts +20 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +32 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lexer.d.ts +19 -1
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +172 -5
- package/dist/lexer.js.map +1 -1
- package/dist/parser.d.ts +9 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +103 -7
- package/dist/parser.js.map +1 -1
- package/dist/visitor.d.ts +11 -2
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +181 -8
- package/dist/visitor.js.map +1 -1
- package/package.json +1 -1
- package/src/ast.ts +93 -0
- package/src/constants.ts +40 -0
- package/src/index.ts +20 -3
- package/src/lexer.ts +164 -6
- package/src/parser.ts +128 -7
- package/src/visitor.ts +195 -11
- package/tests/constants.test.ts +102 -0
- package/tests/e2e.test.ts +15 -3
- package/tests/lexer.test.ts +291 -9
- package/tests/parser.test.ts +489 -0
- package/tests/props/express_todo_web.spex +108 -0
- package/tests/props/express_web_env.spex +16 -0
- package/tests/props/flask_todo_web.spex +111 -0
- package/tests/props/flask_web_env.spex +16 -0
- package/tests/props/python_cli_env.spex +16 -0
- package/tests/props/python_todo_cli.spex +70 -0
- package/tests/props/todo.spex +100 -60
- package/tests/props/typescript_cli_env.spex +16 -0
- package/tests/props/typescript_todo_cli.spex +70 -0
- package/tests/visitor.test.ts +892 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
-- The Flask realization of the Todo Web app.
|
|
2
|
+
--
|
|
3
|
+
-- Imports the shared TodoWeb representation, the todo operations, and the
|
|
4
|
+
-- Flask environment. SQLite storage, the route handlers, and the main
|
|
5
|
+
-- exponential are defined as separate objects, and the concept is realized
|
|
6
|
+
-- as a product of them in the realize statement. Flask handlers receive a
|
|
7
|
+
-- request and return a response.
|
|
8
|
+
|
|
9
|
+
import TodoWeb from "todo.spex";
|
|
10
|
+
import Todo from "todo.spex";
|
|
11
|
+
import TodoId from "todo.spex";
|
|
12
|
+
import TodoTitle from "todo.spex";
|
|
13
|
+
import AddTodo from "todo.spex";
|
|
14
|
+
import ListTodos from "todo.spex";
|
|
15
|
+
import CompleteTodo from "todo.spex";
|
|
16
|
+
import HttpRequest from "todo.spex";
|
|
17
|
+
import HttpResponse from "todo.spex";
|
|
18
|
+
import FlaskWebEnv from "flask_web_env.spex";
|
|
19
|
+
|
|
20
|
+
-- TODO: realize the shared HttpRequest and HttpResponse objects using the
|
|
21
|
+
-- types provided by the flask dependency in FlaskWebEnv.
|
|
22
|
+
|
|
23
|
+
/* SQLite storage. */
|
|
24
|
+
create SqliteConnection as (
|
|
25
|
+
path: string
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
create OpenSqlite as
|
|
29
|
+
from (path: string) -> SqliteConnection
|
|
30
|
+
select {
|
|
31
|
+
1. open the SQLite database at @path
|
|
32
|
+
2. create the todos table if it does not exist
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
create SelectTodos as
|
|
36
|
+
from (connection: SqliteConnection) -> Todo[]
|
|
37
|
+
select {
|
|
38
|
+
1. select the id, title, and completed columns from the todos table
|
|
39
|
+
2. map each row to a @Todo
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
create InsertTodo as
|
|
43
|
+
from (connection: SqliteConnection, todo: Todo) -> unit
|
|
44
|
+
select {
|
|
45
|
+
1. insert the id, title, and completed values of @todo into the todos table
|
|
46
|
+
2. commit the transaction
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
create UpdateTodo as
|
|
50
|
+
from (connection: SqliteConnection, todo: Todo) -> unit
|
|
51
|
+
select {
|
|
52
|
+
1. update the completed value of the row matching the id of @todo
|
|
53
|
+
2. commit the transaction
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/* Route handlers. */
|
|
57
|
+
create ListTodosHandler as
|
|
58
|
+
from (request: HttpRequest) -> HttpResponse
|
|
59
|
+
select {
|
|
60
|
+
1. open the database with @OpenSqlite
|
|
61
|
+
2. load the todos with @SelectTodos
|
|
62
|
+
3. return a 200 response with the todos serialized as JSON
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
create AddTodoHandler as
|
|
66
|
+
from (request: HttpRequest) -> HttpResponse
|
|
67
|
+
select {
|
|
68
|
+
1. parse the JSON body of @request into a @TodoTitle
|
|
69
|
+
2. create the todo with @AddTodo
|
|
70
|
+
3. open the database with @OpenSqlite
|
|
71
|
+
4. persist the todo with @InsertTodo
|
|
72
|
+
5. return a 201 response with the created todo serialized as JSON
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
create CompleteTodoHandler as
|
|
76
|
+
from (request: HttpRequest) -> HttpResponse
|
|
77
|
+
select {
|
|
78
|
+
1. extract the todo id from the path of @request
|
|
79
|
+
2. open the database with @OpenSqlite
|
|
80
|
+
3. load the todos with @SelectTodos
|
|
81
|
+
4. find the matching @Todo and complete it with @CompleteTodo
|
|
82
|
+
5. persist it with @UpdateTodo
|
|
83
|
+
6. return a 200 response with the updated todo serialized as JSON
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/* The main entry exponential. */
|
|
87
|
+
create FlaskTodoWeb as
|
|
88
|
+
from unit -> unit
|
|
89
|
+
select {
|
|
90
|
+
1. create the Flask application object
|
|
91
|
+
2. register @ListTodosHandler on the GET /todos route
|
|
92
|
+
3. register @AddTodoHandler on the POST /todos route
|
|
93
|
+
4. register @CompleteTodoHandler on the PATCH /todos/:id route
|
|
94
|
+
5. run the application
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
realize TodoWeb as (
|
|
98
|
+
list: ListTodosHandler,
|
|
99
|
+
add: AddTodoHandler,
|
|
100
|
+
complete: CompleteTodoHandler,
|
|
101
|
+
storage: (
|
|
102
|
+
open: OpenSqlite,
|
|
103
|
+
query: SelectTodos,
|
|
104
|
+
insert: InsertTodo,
|
|
105
|
+
update: UpdateTodo
|
|
106
|
+
),
|
|
107
|
+
main: FlaskTodoWeb
|
|
108
|
+
) in FlaskWebEnv;
|
|
109
|
+
|
|
110
|
+
generate FlaskTodoWeb;
|
|
111
|
+
package executable flasktodo as FlaskTodoWeb;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- The Flask web environment.
|
|
2
|
+
--
|
|
3
|
+
-- A subobject of the shared Web environment intersected with the shared
|
|
4
|
+
-- Python environment.
|
|
5
|
+
|
|
6
|
+
import Web from "todo.spex";
|
|
7
|
+
import Python from "todo.spex";
|
|
8
|
+
|
|
9
|
+
create FlaskWebEnv as
|
|
10
|
+
from Web intersect Python
|
|
11
|
+
select {
|
|
12
|
+
dependencies: flask,
|
|
13
|
+
database: SQLite
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export FlaskWebEnv;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- The Python CLI environment.
|
|
2
|
+
--
|
|
3
|
+
-- A subobject of the shared Cli environment intersected with the shared
|
|
4
|
+
-- Python environment.
|
|
5
|
+
|
|
6
|
+
import Cli from "todo.spex";
|
|
7
|
+
import Python from "todo.spex";
|
|
8
|
+
|
|
9
|
+
create PythonCliEnv as
|
|
10
|
+
from Cli intersect Python
|
|
11
|
+
select {
|
|
12
|
+
dependencies: standard library,
|
|
13
|
+
storage: JSON file
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export PythonCliEnv;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- The Python realization of the Todo CLI.
|
|
2
|
+
--
|
|
3
|
+
-- Imports the shared TodoCli representation, the todo operations, and the
|
|
4
|
+
-- Python CLI environment. The argument parsing and JSON file storage are
|
|
5
|
+
-- defined as separate objects, and the concept is realized as a product of
|
|
6
|
+
-- them in the realize statement.
|
|
7
|
+
|
|
8
|
+
import TodoCli from "todo.spex";
|
|
9
|
+
import Todo from "todo.spex";
|
|
10
|
+
import TodoId from "todo.spex";
|
|
11
|
+
import TodoTitle from "todo.spex";
|
|
12
|
+
import AddTodo from "todo.spex";
|
|
13
|
+
import ListTodos from "todo.spex";
|
|
14
|
+
import CompleteTodo from "todo.spex";
|
|
15
|
+
import PythonCliEnv from "python_cli_env.spex";
|
|
16
|
+
|
|
17
|
+
/* Argument parsing. */
|
|
18
|
+
create CliCommand as (
|
|
19
|
+
command: string,
|
|
20
|
+
arguments: string[]
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
create ParseArgs as
|
|
24
|
+
from (args: string[]) -> CliCommand
|
|
25
|
+
select {
|
|
26
|
+
1. create an argparse parser with the add, list, and complete subcommands
|
|
27
|
+
2. parse @args into a command and its arguments
|
|
28
|
+
3. return the parsed command
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/* JSON file storage. */
|
|
32
|
+
create LoadTodosFile as
|
|
33
|
+
from (path: string) -> Todo[]
|
|
34
|
+
select {
|
|
35
|
+
1. check whether the file at @path exists
|
|
36
|
+
2. read the file at @path
|
|
37
|
+
3. parse the JSON content into todos
|
|
38
|
+
4. return an empty list if the file does not exist
|
|
39
|
+
5. throw an exception if the JSON is invalid
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
create SaveTodosFile as
|
|
43
|
+
from (path: string, todos: Todo[]) -> unit
|
|
44
|
+
select {
|
|
45
|
+
1. serialize @todos as formatted JSON
|
|
46
|
+
2. write the JSON to @path
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/* The main entry exponential. */
|
|
50
|
+
create PythonTodoCli as
|
|
51
|
+
from (args: string[]) -> unit
|
|
52
|
+
select {
|
|
53
|
+
1. parse the command with @ParseArgs
|
|
54
|
+
2. load the todos from the JSON file with @LoadTodosFile
|
|
55
|
+
3. dispatch the command:
|
|
56
|
+
- add: call @AddTodo with the given @TodoTitle, then persist with @SaveTodosFile
|
|
57
|
+
- list: call @ListTodos and print the result to stdout
|
|
58
|
+
- complete: call @CompleteTodo with the given @TodoId, then persist with @SaveTodosFile
|
|
59
|
+
4. print user-friendly error messages for exceptions
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
realize TodoCli as (
|
|
63
|
+
parse: ParseArgs,
|
|
64
|
+
load: LoadTodosFile,
|
|
65
|
+
save: SaveTodosFile,
|
|
66
|
+
main: PythonTodoCli
|
|
67
|
+
) in PythonCliEnv;
|
|
68
|
+
|
|
69
|
+
generate PythonTodoCli;
|
|
70
|
+
package executable mytodocli as PythonTodoCli;
|
package/tests/props/todo.spex
CHANGED
|
@@ -1,85 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
);
|
|
8
|
-
|
|
9
|
-
create TodoTitle as from string select {
|
|
10
|
-
are not empty and are shorter than 120 characters
|
|
11
|
-
};
|
|
1
|
+
-- The shared abstract model of a Todo application written in Spex.
|
|
2
|
+
--
|
|
3
|
+
-- This file defines the domain model, the todo operations, the application
|
|
4
|
+
-- concept, its CLI and Web representations, and the base environments that
|
|
5
|
+
-- are shared between multiple realizations. Concrete environments and
|
|
6
|
+
-- realizations live in separate files and import what they need from here.
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
/* The domain model. */
|
|
9
|
+
create TodoId as
|
|
10
|
+
from string
|
|
11
|
+
select {
|
|
12
|
+
are valid UUIDs
|
|
15
13
|
};
|
|
16
14
|
|
|
17
|
-
create
|
|
18
|
-
|
|
15
|
+
create TodoTitle as
|
|
16
|
+
from string
|
|
17
|
+
select {
|
|
18
|
+
are not empty and are shorter than 120 characters
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
create
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
create Todo as (
|
|
22
|
+
id: TodoId,
|
|
23
|
+
title: TodoTitle,
|
|
24
|
+
completed: bool
|
|
24
25
|
);
|
|
25
26
|
|
|
26
|
-
create
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
27
|
+
create HttpRequest as (
|
|
28
|
+
method: string,
|
|
29
|
+
path: string,
|
|
30
|
+
body: string
|
|
31
|
+
);
|
|
32
32
|
|
|
33
|
-
create
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
create HttpResponse as (
|
|
34
|
+
status: number,
|
|
35
|
+
body: string
|
|
36
|
+
);
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
/* The todo operations, defined once and reused by every realization. */
|
|
39
|
+
create AddTodo as
|
|
40
|
+
from (title: TodoTitle) -> Todo
|
|
41
|
+
select {
|
|
39
42
|
1. generate a UUID for the todo id
|
|
40
43
|
2. create a todo with completed set to false
|
|
41
44
|
3. return the created todo
|
|
42
45
|
};
|
|
43
46
|
|
|
44
|
-
create
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
create ListTodos as
|
|
48
|
+
from unit -> Todo[]
|
|
49
|
+
select {
|
|
50
|
+
return all the todos
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
create CompleteTodo as
|
|
54
|
+
from (id: TodoId) -> Todo
|
|
55
|
+
select {
|
|
56
|
+
1. mark the todo with the given id as completed
|
|
57
|
+
2. return the updated todo
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/* The application, specified as an abstract concept. */
|
|
61
|
+
create TodoApp as
|
|
62
|
+
from concept
|
|
63
|
+
select {
|
|
64
|
+
provide adding, listing, and completing todos by composing @AddTodo, @ListTodos, and @CompleteTodo
|
|
50
65
|
};
|
|
51
66
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
/* The concept realized into a CLI and a Web representation. */
|
|
68
|
+
create TodoCli as
|
|
69
|
+
from TodoApp
|
|
70
|
+
select {
|
|
71
|
+
expose the todo operations through a command-line interface
|
|
57
72
|
};
|
|
58
73
|
|
|
59
|
-
create
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
4. set the todo completed status to true
|
|
64
|
-
5. persist the updated todo list using @SaveTodos
|
|
65
|
-
6. return the updated todo
|
|
74
|
+
create TodoWeb as
|
|
75
|
+
from TodoApp
|
|
76
|
+
select {
|
|
77
|
+
expose the todo operations through HTTP endpoints
|
|
66
78
|
};
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
realize TodoApp as TodoCli in Cli;
|
|
81
|
+
realize TodoApp as TodoWeb in Web;
|
|
82
|
+
|
|
83
|
+
/* The base environments shared between realizations. */
|
|
84
|
+
create Cli as
|
|
85
|
+
from environment
|
|
86
|
+
select {
|
|
87
|
+
interact with the user through a terminal
|
|
72
88
|
};
|
|
73
89
|
|
|
74
|
-
create
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
4. if the command is "complete": call @CompleteTodo
|
|
79
|
-
5. print a help message if the command is invalid
|
|
80
|
-
6. print user-friendly error messages for exceptions
|
|
90
|
+
create Web as
|
|
91
|
+
from environment
|
|
92
|
+
select {
|
|
93
|
+
serve HTTP requests and produce HTTP responses
|
|
81
94
|
};
|
|
82
95
|
|
|
83
|
-
|
|
96
|
+
create Python as
|
|
97
|
+
from environment
|
|
98
|
+
select {
|
|
99
|
+
language: Python,
|
|
100
|
+
runtime: CPython 3.12
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
create TypeScript as
|
|
104
|
+
from environment
|
|
105
|
+
select {
|
|
106
|
+
language: TypeScript,
|
|
107
|
+
runtime: Node.js 20
|
|
108
|
+
};
|
|
84
109
|
|
|
85
|
-
|
|
110
|
+
/* Exports for the concrete realizations. */
|
|
111
|
+
export TodoId;
|
|
112
|
+
export TodoTitle;
|
|
113
|
+
export Todo;
|
|
114
|
+
export HttpRequest;
|
|
115
|
+
export HttpResponse;
|
|
116
|
+
export AddTodo;
|
|
117
|
+
export ListTodos;
|
|
118
|
+
export CompleteTodo;
|
|
119
|
+
export TodoApp;
|
|
120
|
+
export TodoCli;
|
|
121
|
+
export TodoWeb;
|
|
122
|
+
export Cli;
|
|
123
|
+
export Web;
|
|
124
|
+
export Python;
|
|
125
|
+
export TypeScript;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- The TypeScript CLI environment.
|
|
2
|
+
--
|
|
3
|
+
-- A subobject of the shared Cli environment intersected with the shared
|
|
4
|
+
-- TypeScript environment.
|
|
5
|
+
|
|
6
|
+
import Cli from "todo.spex";
|
|
7
|
+
import TypeScript from "todo.spex";
|
|
8
|
+
|
|
9
|
+
create TypeScriptCliEnv as
|
|
10
|
+
from Cli intersect TypeScript
|
|
11
|
+
select {
|
|
12
|
+
dependencies: Node.js standard library,
|
|
13
|
+
storage: JSON file
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export TypeScriptCliEnv;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- The TypeScript realization of the Todo CLI.
|
|
2
|
+
--
|
|
3
|
+
-- Imports the shared TodoCli representation, the todo operations, and the
|
|
4
|
+
-- TypeScript CLI environment. The argument parsing and JSON file storage
|
|
5
|
+
-- are defined as separate objects, and the concept is realized as a product
|
|
6
|
+
-- of them in the realize statement.
|
|
7
|
+
|
|
8
|
+
import TodoCli from "todo.spex";
|
|
9
|
+
import Todo from "todo.spex";
|
|
10
|
+
import TodoId from "todo.spex";
|
|
11
|
+
import TodoTitle from "todo.spex";
|
|
12
|
+
import AddTodo from "todo.spex";
|
|
13
|
+
import ListTodos from "todo.spex";
|
|
14
|
+
import CompleteTodo from "todo.spex";
|
|
15
|
+
import TypeScriptCliEnv from "typescript_cli_env.spex";
|
|
16
|
+
|
|
17
|
+
/* Argument parsing. */
|
|
18
|
+
create CliCommand as (
|
|
19
|
+
command: string,
|
|
20
|
+
arguments: string[]
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
create ParseArgs as
|
|
24
|
+
from (args: string[]) -> CliCommand
|
|
25
|
+
select {
|
|
26
|
+
1. read the command and its arguments from @args
|
|
27
|
+
2. map the command to the add, list, or complete command
|
|
28
|
+
3. return the parsed command
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/* JSON file storage. */
|
|
32
|
+
create LoadTodosFile as
|
|
33
|
+
from (path: string) -> Todo[]
|
|
34
|
+
select {
|
|
35
|
+
1. check whether the file at @path exists with the filesystem module
|
|
36
|
+
2. read the file at @path
|
|
37
|
+
3. parse the JSON content into todos
|
|
38
|
+
4. return an empty list if the file does not exist
|
|
39
|
+
5. throw an exception if the JSON is invalid
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
create SaveTodosFile as
|
|
43
|
+
from (path: string, todos: Todo[]) -> unit
|
|
44
|
+
select {
|
|
45
|
+
1. serialize @todos as formatted JSON
|
|
46
|
+
2. write the JSON to @path
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/* The main entry exponential. */
|
|
50
|
+
create TypeScriptTodoCli as
|
|
51
|
+
from (args: string[]) -> unit
|
|
52
|
+
select {
|
|
53
|
+
1. parse the command with @ParseArgs
|
|
54
|
+
2. load the todos from the JSON file with @LoadTodosFile
|
|
55
|
+
3. dispatch the command:
|
|
56
|
+
- add: call @AddTodo with the given @TodoTitle, then persist with @SaveTodosFile
|
|
57
|
+
- list: call @ListTodos and print the result to stdout
|
|
58
|
+
- complete: call @CompleteTodo with the given @TodoId, then persist with @SaveTodosFile
|
|
59
|
+
4. print user-friendly error messages for exceptions
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
realize TodoCli as (
|
|
63
|
+
parse: ParseArgs,
|
|
64
|
+
load: LoadTodosFile,
|
|
65
|
+
save: SaveTodosFile,
|
|
66
|
+
main: TypeScriptTodoCli
|
|
67
|
+
) in TypeScriptCliEnv;
|
|
68
|
+
|
|
69
|
+
generate TypeScriptTodoCli;
|
|
70
|
+
package executable mytotscli as TypeScriptTodoCli;
|