skir 0.0.6 → 0.0.7
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/dist/command_line_parser.d.ts.map +1 -1
- package/dist/command_line_parser.js +76 -36
- package/dist/command_line_parser.js.map +1 -1
- package/dist/command_line_parser.test.js +119 -31
- package/dist/command_line_parser.test.js.map +1 -1
- package/dist/compiler.js +45 -51
- package/dist/compiler.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -5
- package/dist/config.js.map +1 -1
- package/dist/config_parser.d.ts +6 -0
- package/dist/config_parser.d.ts.map +1 -1
- package/dist/config_parser.js +52 -31
- package/dist/config_parser.js.map +1 -1
- package/dist/config_parser.test.js +53 -20
- package/dist/config_parser.test.js.map +1 -1
- package/dist/error_renderer.d.ts +1 -0
- package/dist/error_renderer.d.ts.map +1 -1
- package/dist/error_renderer.js +6 -3
- package/dist/error_renderer.js.map +1 -1
- package/dist/exit_error.d.ts +8 -0
- package/dist/exit_error.d.ts.map +1 -0
- package/dist/exit_error.js +8 -0
- package/dist/exit_error.js.map +1 -0
- package/dist/io.d.ts +2 -0
- package/dist/io.d.ts.map +1 -1
- package/dist/io.js +22 -3
- package/dist/io.js.map +1 -1
- package/dist/module_collector.d.ts.map +1 -1
- package/dist/module_collector.js +12 -7
- package/dist/module_collector.js.map +1 -1
- package/dist/module_set.js +4 -4
- package/dist/parser.js +6 -6
- package/dist/project_initializer.d.ts.map +1 -1
- package/dist/project_initializer.js +97 -15
- package/dist/project_initializer.js.map +1 -1
- package/package.json +8 -6
- package/src/command_line_parser.ts +95 -40
- package/src/compiler.ts +59 -57
- package/src/config.ts +4 -6
- package/src/config_parser.ts +61 -32
- package/src/error_renderer.ts +11 -3
- package/src/exit_error.ts +6 -0
- package/src/io.ts +22 -3
- package/src/module_collector.ts +21 -7
- package/src/module_set.ts +4 -4
- package/src/parser.ts +6 -6
- package/src/project_initializer.ts +97 -15
|
@@ -1,31 +1,113 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
1
|
+
import * as FileSystem from "fs";
|
|
2
|
+
import * as Paths from "path";
|
|
3
|
+
import { rewritePathForRendering } from "./io.js";
|
|
3
4
|
export function initializeProject(rootDir) {
|
|
4
|
-
const skirYmlPath =
|
|
5
|
+
const skirYmlPath = Paths.join(rootDir, "skir.yml");
|
|
5
6
|
// Check if skir.yml already exists
|
|
6
|
-
if (
|
|
7
|
+
if (FileSystem.existsSync(skirYmlPath)) {
|
|
7
8
|
console.log("A skir.yml file already exists in this directory. Skipping project initialization.");
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
// Create skir.yml file
|
|
11
|
-
|
|
12
|
+
FileSystem.writeFileSync(skirYmlPath, SKIR_YML_CONTENT, "utf-8");
|
|
12
13
|
// Check if skir-src directory exists
|
|
13
|
-
const skirSrcDir =
|
|
14
|
-
if (!
|
|
14
|
+
const skirSrcDir = Paths.join(rootDir, "skir-src");
|
|
15
|
+
if (!FileSystem.existsSync(skirSrcDir)) {
|
|
15
16
|
// Create skir-src directory
|
|
16
|
-
|
|
17
|
+
FileSystem.mkdirSync(skirSrcDir, { recursive: true });
|
|
17
18
|
// Create hello_world.skir file
|
|
18
|
-
const helloWorldPath =
|
|
19
|
-
|
|
19
|
+
const helloWorldPath = Paths.join(skirSrcDir, "hello_world.skir");
|
|
20
|
+
FileSystem.writeFileSync(helloWorldPath, HELLO_WORLD_SKIR_CONTENT, "utf-8");
|
|
20
21
|
}
|
|
21
|
-
console.log(`Done. Please edit: ${
|
|
22
|
+
console.log(`Done. Please edit: ${rewritePathForRendering(skirYmlPath)}`);
|
|
22
23
|
}
|
|
23
|
-
const SKIR_YML_CONTENT =
|
|
24
|
+
const SKIR_YML_CONTENT = `# Configuration file for Skir code generator
|
|
25
|
+
#
|
|
26
|
+
# Documentation: https://skir.build/
|
|
27
|
+
#
|
|
28
|
+
# Cheat sheet:
|
|
29
|
+
# npx skir gen Generate code from .skir files
|
|
30
|
+
# npx skir gen --watch Watch for changes and regenerate automatically
|
|
31
|
+
# npx skir format Format all .skir files
|
|
32
|
+
# npx skir snapshot Take a snapshot of the source directory, verify no
|
|
33
|
+
# breaking changes since last snapshot
|
|
24
34
|
|
|
35
|
+
# Directory containing .skir files
|
|
36
|
+
srcDir: skir-src
|
|
37
|
+
|
|
38
|
+
# Uncomment and configure the generators for your target language(s).
|
|
25
39
|
generators:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
# # --------------------------------------------------------------------------
|
|
41
|
+
# # C++ code generator
|
|
42
|
+
# # Home: https://github.com/gepheum/skir-cc-gen
|
|
43
|
+
# # To install runtime dependencies, follow instructions in repository README
|
|
44
|
+
# # --------------------------------------------------------------------------
|
|
45
|
+
# - mod: skir-cc-gen
|
|
46
|
+
# outDir: ./skirout
|
|
47
|
+
# config:
|
|
48
|
+
# # Set to true if you use GoogleTest
|
|
49
|
+
# writeGoogleTestHeaders: false
|
|
50
|
+
|
|
51
|
+
# # --------------------------------------------------------------------------
|
|
52
|
+
# # Dart code generator
|
|
53
|
+
# # Home: https://github.com/gepheum/skir-dart-gen
|
|
54
|
+
# # To install runtime dependencies: dart pub add skir_client
|
|
55
|
+
# # --------------------------------------------------------------------------
|
|
56
|
+
# - mod: skir-dart-gen
|
|
57
|
+
# outDir: ./skirout
|
|
58
|
+
# config: {}
|
|
59
|
+
|
|
60
|
+
# # --------------------------------------------------------------------------
|
|
61
|
+
# # Java code generator
|
|
62
|
+
# # Home: https://github.com/gepheum/skir-java-gen
|
|
63
|
+
# # Add the following line to your build.gradle dependencies:
|
|
64
|
+
# # implementation("build.skir:skir-client:latest.release")
|
|
65
|
+
# # --------------------------------------------------------------------------
|
|
66
|
+
# - mod: skir-java-gen
|
|
67
|
+
# outDir: ./src/main/java/skirout
|
|
68
|
+
# config: {}
|
|
69
|
+
# # Alternatively:
|
|
70
|
+
# # outDir: ./src/main/java/skirout/my/project
|
|
71
|
+
# # config:
|
|
72
|
+
# # packagePrefix: "my.project."
|
|
73
|
+
|
|
74
|
+
# # --------------------------------------------------------------------------
|
|
75
|
+
# # Kotlin code generator
|
|
76
|
+
# # Home: https://github.com/gepheum/skir-kotlin-gen
|
|
77
|
+
# # Add the following line to your build.gradle dependencies:
|
|
78
|
+
# # implementation("build.skir:skir-client:latest.release")
|
|
79
|
+
# # --------------------------------------------------------------------------
|
|
80
|
+
# - mod: skir-kotlin-gen
|
|
81
|
+
# outDir: ./src/main/kotlin/skirout
|
|
82
|
+
# config: {}
|
|
83
|
+
# # Alternatively:
|
|
84
|
+
# # outDir: ./src/main/kotlin/skirout/my/project
|
|
85
|
+
# # config:
|
|
86
|
+
# # packagePrefix: "my.project."
|
|
87
|
+
|
|
88
|
+
# # --------------------------------------------------------------------------
|
|
89
|
+
# # Python code generator
|
|
90
|
+
# # Home: https://github.com/gepheum/skir-python-gen
|
|
91
|
+
# # To install runtime dependencies: pip install skir-client
|
|
92
|
+
# # --------------------------------------------------------------------------
|
|
93
|
+
# - mod: skir-python-gen
|
|
94
|
+
# outDir: ./skirout
|
|
95
|
+
# config: {}
|
|
96
|
+
# # Alternatively:
|
|
97
|
+
# # outDir: ./my/project/skirout
|
|
98
|
+
# # config:
|
|
99
|
+
# # packagePrefix: "my.project."
|
|
100
|
+
|
|
101
|
+
# # --------------------------------------------------------------------------
|
|
102
|
+
# # TypeScript/JavaScript code generator
|
|
103
|
+
# # Home: https://github.com/gepheum/skir-typescript-gen
|
|
104
|
+
# # To install runtime dependencies: npm i skir-client
|
|
105
|
+
# # --------------------------------------------------------------------------
|
|
106
|
+
# - mod: skir-typescript-gen
|
|
107
|
+
# outDir: ./src/skirout
|
|
108
|
+
# config:
|
|
109
|
+
# # Use ".js" for ES modules, "" for CommonJS
|
|
110
|
+
# importPathExtension: ".js"
|
|
29
111
|
`;
|
|
30
112
|
const HELLO_WORLD_SKIR_CONTENT = `/// A point in 2D space.
|
|
31
113
|
struct Point {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project_initializer.js","sourceRoot":"","sources":["../src/project_initializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"project_initializer.js","sourceRoot":"","sources":["../src/project_initializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC;AACjC,OAAO,KAAK,KAAK,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAElD,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEpD,mCAAmC;IACnC,IAAI,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CACT,oFAAoF,CACrF,CAAC;QACF,OAAO;IACT,CAAC;IAED,uBAAuB;IACvB,UAAU,CAAC,aAAa,CAAC,WAAW,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAEjE,qCAAqC;IACrC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACvC,4BAA4B;QAC5B,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtD,+BAA+B;QAC/B,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAClE,UAAU,CAAC,aAAa,CAAC,cAAc,EAAE,wBAAwB,EAAE,OAAO,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,uBAAuB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuFxB,CAAC;AAEF,MAAM,wBAAwB,GAAG;;;;;;;CAOhC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skir",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"homepage": "https://github.com/gepheum/skir#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -54,17 +54,19 @@
|
|
|
54
54
|
"zod": "^4.2.1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
+
"@eslint/js": "^9.39.2",
|
|
57
58
|
"@types/mocha": "^10.0.1",
|
|
58
59
|
"@types/node": "^20.6.0",
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
60
|
-
"@typescript-eslint/parser": "^
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
61
|
+
"@typescript-eslint/parser": "^8.50.1",
|
|
61
62
|
"buckwheat": "^1.1.2",
|
|
62
|
-
"eslint": "^
|
|
63
|
-
"mocha": "^
|
|
63
|
+
"eslint": "^9.39.2",
|
|
64
|
+
"mocha": "^11.7.5",
|
|
64
65
|
"prettier": "^3.2.4",
|
|
65
66
|
"prettier-plugin-organize-imports": "^4.2.0",
|
|
66
67
|
"ts-node": "^10.9.2",
|
|
67
68
|
"tsx": "^4.21.0",
|
|
68
|
-
"typescript": "^5.2.2"
|
|
69
|
+
"typescript": "^5.2.2",
|
|
70
|
+
"typescript-eslint": "^8.50.1"
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -80,30 +80,33 @@ export function parseCommandLine(args: string[]): ParsedArgs {
|
|
|
80
80
|
const COMMAND_BASE = "npx skir";
|
|
81
81
|
|
|
82
82
|
const HELP_TEXT = `
|
|
83
|
-
Usage: ${COMMAND_BASE} <command> [
|
|
83
|
+
Usage: ${COMMAND_BASE} <command> [options]
|
|
84
84
|
|
|
85
85
|
Commands:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
format
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
view: Display the last snapshot
|
|
93
|
-
init Initialize a new Skir project with a minimal skir.yml file
|
|
94
|
-
help Display this help message
|
|
86
|
+
init Initialize a new project with a minimal skir.yml file
|
|
87
|
+
gen Generate code from .skir files to target languages
|
|
88
|
+
format Format all .skir files in the source directory
|
|
89
|
+
snapshot Take a snapshot of the source directory, look for
|
|
90
|
+
breaking changes since the last snapshot
|
|
91
|
+
help Display this help message
|
|
95
92
|
|
|
96
93
|
Options:
|
|
97
|
-
--root, -r <path>
|
|
94
|
+
--root, -r <path> Path to the directory containing the skir.yml file
|
|
95
|
+
--watch, -w Automatically run code generation when .skir files change
|
|
96
|
+
(gen only)
|
|
97
|
+
--check Fail if code is not properly formatted (format) or if there
|
|
98
|
+
are breaking changes (snapshot)
|
|
99
|
+
--view Display the last snapshot (snapshot only)
|
|
98
100
|
|
|
99
101
|
Examples:
|
|
102
|
+
${COMMAND_BASE} init
|
|
100
103
|
${COMMAND_BASE} gen
|
|
101
|
-
${COMMAND_BASE} gen watch
|
|
104
|
+
${COMMAND_BASE} gen --watch
|
|
102
105
|
${COMMAND_BASE} format --root path/to/root/dir
|
|
103
|
-
${COMMAND_BASE} format check -r path/to/root/dir
|
|
106
|
+
${COMMAND_BASE} format --check -r path/to/root/dir
|
|
104
107
|
${COMMAND_BASE} snapshot
|
|
105
|
-
${COMMAND_BASE} snapshot check
|
|
106
|
-
${COMMAND_BASE} snapshot view --root path/to/root/dir
|
|
108
|
+
${COMMAND_BASE} snapshot --check
|
|
109
|
+
${COMMAND_BASE} snapshot --view --root path/to/root/dir
|
|
107
110
|
`;
|
|
108
111
|
|
|
109
112
|
export class CommandLineParseError extends Error {
|
|
@@ -115,7 +118,9 @@ export class CommandLineParseError extends Error {
|
|
|
115
118
|
|
|
116
119
|
type ParsedOptions = {
|
|
117
120
|
root?: string;
|
|
118
|
-
|
|
121
|
+
watch?: boolean;
|
|
122
|
+
check?: boolean;
|
|
123
|
+
view?: boolean;
|
|
119
124
|
unknown: string[];
|
|
120
125
|
};
|
|
121
126
|
|
|
@@ -128,7 +133,21 @@ function parseOptions(args: string[]): ParsedOptions {
|
|
|
128
133
|
const arg = args[i];
|
|
129
134
|
if (!arg) continue;
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
// Check for --option=value syntax
|
|
137
|
+
if (arg.startsWith("--root=") || arg.startsWith("-r=")) {
|
|
138
|
+
const value = arg.substring(arg.indexOf("=") + 1);
|
|
139
|
+
if (!value) {
|
|
140
|
+
throw new CommandLineParseError(
|
|
141
|
+
`Option ${arg.split("=")[0]} requires a value`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (options.root !== undefined) {
|
|
145
|
+
throw new CommandLineParseError(
|
|
146
|
+
`Option ${arg.split("=")[0]} specified multiple times`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
options.root = value;
|
|
150
|
+
} else if (arg === "--root" || arg === "-r") {
|
|
132
151
|
if (i + 1 >= args.length) {
|
|
133
152
|
throw new CommandLineParseError(`Option ${arg} requires a value`);
|
|
134
153
|
}
|
|
@@ -139,14 +158,32 @@ function parseOptions(args: string[]): ParsedOptions {
|
|
|
139
158
|
}
|
|
140
159
|
options.root = args[i + 1];
|
|
141
160
|
i++; // Skip the next argument as it's the value
|
|
161
|
+
} else if (arg === "--watch" || arg === "-w") {
|
|
162
|
+
if (options.watch) {
|
|
163
|
+
throw new CommandLineParseError(
|
|
164
|
+
`Option ${arg} specified multiple times`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
options.watch = true;
|
|
168
|
+
} else if (arg === "--check") {
|
|
169
|
+
if (options.check) {
|
|
170
|
+
throw new CommandLineParseError(
|
|
171
|
+
`Option --check specified multiple times`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
options.check = true;
|
|
175
|
+
} else if (arg === "--view") {
|
|
176
|
+
if (options.view) {
|
|
177
|
+
throw new CommandLineParseError(
|
|
178
|
+
`Option --view specified multiple times`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
options.view = true;
|
|
142
182
|
} else if (arg.startsWith("-")) {
|
|
143
183
|
options.unknown.push(arg);
|
|
144
184
|
} else {
|
|
145
|
-
// Positional argument -
|
|
146
|
-
|
|
147
|
-
throw new CommandLineParseError(`Unexpected argument: ${arg}`);
|
|
148
|
-
}
|
|
149
|
-
options.subcommand = arg;
|
|
185
|
+
// Positional argument - not allowed anymore
|
|
186
|
+
throw new CommandLineParseError(`Unexpected argument: ${arg}`);
|
|
150
187
|
}
|
|
151
188
|
}
|
|
152
189
|
|
|
@@ -156,64 +193,82 @@ function parseOptions(args: string[]): ParsedOptions {
|
|
|
156
193
|
function buildGenCommand(options: ParsedOptions): ParsedArgs {
|
|
157
194
|
validateNoUnknownOptions(options, "gen");
|
|
158
195
|
|
|
159
|
-
if (options.
|
|
196
|
+
if (options.check) {
|
|
197
|
+
throw new CommandLineParseError(
|
|
198
|
+
`Option --check is not valid for 'gen' command`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (options.view) {
|
|
160
202
|
throw new CommandLineParseError(
|
|
161
|
-
`
|
|
203
|
+
`Option --view is not valid for 'gen' command`,
|
|
162
204
|
);
|
|
163
205
|
}
|
|
164
206
|
|
|
165
207
|
return {
|
|
166
208
|
kind: "gen",
|
|
167
209
|
root: options.root,
|
|
168
|
-
subcommand: options.
|
|
210
|
+
subcommand: options.watch ? "watch" : undefined,
|
|
169
211
|
};
|
|
170
212
|
}
|
|
171
213
|
|
|
172
214
|
function buildFormatCommand(options: ParsedOptions): ParsedArgs {
|
|
173
215
|
validateNoUnknownOptions(options, "format");
|
|
174
216
|
|
|
175
|
-
if (options.
|
|
217
|
+
if (options.watch) {
|
|
176
218
|
throw new CommandLineParseError(
|
|
177
|
-
`
|
|
219
|
+
`Option --watch is not valid for 'format' command`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (options.view) {
|
|
223
|
+
throw new CommandLineParseError(
|
|
224
|
+
`Option --view is not valid for 'format' command`,
|
|
178
225
|
);
|
|
179
226
|
}
|
|
180
227
|
|
|
181
228
|
return {
|
|
182
229
|
kind: "format",
|
|
183
230
|
root: options.root,
|
|
184
|
-
subcommand: options.
|
|
231
|
+
subcommand: options.check ? "check" : undefined,
|
|
185
232
|
};
|
|
186
233
|
}
|
|
187
234
|
|
|
188
235
|
function buildSnapshotCommand(options: ParsedOptions): ParsedArgs {
|
|
189
236
|
validateNoUnknownOptions(options, "snapshot");
|
|
190
237
|
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
238
|
+
if (options.watch) {
|
|
239
|
+
throw new CommandLineParseError(
|
|
240
|
+
`Option --watch is not valid for 'snapshot' command`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (options.check && options.view) {
|
|
196
244
|
throw new CommandLineParseError(
|
|
197
|
-
`
|
|
245
|
+
`Options --check and --view cannot be used together`,
|
|
198
246
|
);
|
|
199
247
|
}
|
|
200
248
|
|
|
201
249
|
return {
|
|
202
250
|
kind: "snapshot",
|
|
203
251
|
root: options.root,
|
|
204
|
-
subcommand:
|
|
205
|
-
options.subcommand === "check" || options.subcommand === "view"
|
|
206
|
-
? options.subcommand
|
|
207
|
-
: undefined,
|
|
252
|
+
subcommand: options.check ? "check" : options.view ? "view" : undefined,
|
|
208
253
|
};
|
|
209
254
|
}
|
|
210
255
|
|
|
211
256
|
function buildInitCommand(options: ParsedOptions): ParsedArgs {
|
|
212
257
|
validateNoUnknownOptions(options, "init");
|
|
213
258
|
|
|
214
|
-
if (options.
|
|
259
|
+
if (options.watch) {
|
|
260
|
+
throw new CommandLineParseError(
|
|
261
|
+
`Option --watch is not valid for 'init' command`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (options.check) {
|
|
265
|
+
throw new CommandLineParseError(
|
|
266
|
+
`Option --check is not valid for 'init' command`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (options.view) {
|
|
215
270
|
throw new CommandLineParseError(
|
|
216
|
-
`
|
|
271
|
+
`Option --view is not valid for 'init' command`,
|
|
217
272
|
);
|
|
218
273
|
}
|
|
219
274
|
|
package/src/compiler.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
2
|
+
import * as FileSystem from "fs/promises";
|
|
3
3
|
import { glob } from "glob";
|
|
4
|
-
import * as
|
|
4
|
+
import * as Paths from "path";
|
|
5
5
|
import type { CodeGenerator } from "skir-internal";
|
|
6
6
|
import Watcher from "watcher";
|
|
7
7
|
import { parseCommandLine } from "./command_line_parser.js";
|
|
@@ -14,8 +14,13 @@ import {
|
|
|
14
14
|
renderErrors,
|
|
15
15
|
renderSkirConfigErrors,
|
|
16
16
|
} from "./error_renderer.js";
|
|
17
|
+
import { ExitError } from "./exit_error.js";
|
|
17
18
|
import { formatModule } from "./formatter.js";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
isDirectory,
|
|
21
|
+
REAL_FILE_SYSTEM,
|
|
22
|
+
rewritePathForRendering,
|
|
23
|
+
} from "./io.js";
|
|
19
24
|
import { collectModules } from "./module_collector.js";
|
|
20
25
|
import { ModuleSet } from "./module_set.js";
|
|
21
26
|
import { initializeProject } from "./project_initializer.js";
|
|
@@ -35,14 +40,12 @@ async function makeGeneratorBundle(
|
|
|
35
40
|
): Promise<GeneratorBundle> {
|
|
36
41
|
const generator = await importCodeGenerator(config.mod);
|
|
37
42
|
let skiroutDirs: string[];
|
|
38
|
-
if (config.outDir ===
|
|
39
|
-
skiroutDirs = ["skirout"];
|
|
40
|
-
} else if (typeof config.outDir === "string") {
|
|
43
|
+
if (typeof config.outDir === "string") {
|
|
41
44
|
skiroutDirs = [config.outDir];
|
|
42
45
|
} else {
|
|
43
46
|
skiroutDirs = config.outDir;
|
|
44
47
|
}
|
|
45
|
-
skiroutDirs = skiroutDirs.map((d) =>
|
|
48
|
+
skiroutDirs = skiroutDirs.map((d) => Paths.join(root, d));
|
|
46
49
|
return {
|
|
47
50
|
generator: generator,
|
|
48
51
|
config: config.config,
|
|
@@ -133,7 +136,7 @@ class WatchModeMainLoop {
|
|
|
133
136
|
const successMessage = `Generation succeeded at ${date}`;
|
|
134
137
|
console.log(makeGreen(successMessage));
|
|
135
138
|
console.log("\nWaiting for changes in files matching:");
|
|
136
|
-
const glob =
|
|
139
|
+
const glob = Paths.resolve(Paths.join(this.srcDir, "/**/*.skir"));
|
|
137
140
|
console.log(` ${glob}`);
|
|
138
141
|
}
|
|
139
142
|
return true;
|
|
@@ -150,11 +153,11 @@ class WatchModeMainLoop {
|
|
|
150
153
|
const { skiroutDirs } = this;
|
|
151
154
|
const preExistingAbsolutePaths = new Set<string>();
|
|
152
155
|
for (const skiroutDir of skiroutDirs) {
|
|
153
|
-
await
|
|
156
|
+
await FileSystem.mkdir(skiroutDir, { recursive: true });
|
|
154
157
|
|
|
155
158
|
// Collect all the files in all the skirout dirs.
|
|
156
159
|
(
|
|
157
|
-
await glob(
|
|
160
|
+
await glob(Paths.join(skiroutDir, "**/*"), { withFileTypes: true })
|
|
158
161
|
).forEach((p) => preExistingAbsolutePaths.add(p.fullpath()));
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -169,7 +172,9 @@ class WatchModeMainLoop {
|
|
|
169
172
|
for (const file of files) {
|
|
170
173
|
const { path } = file;
|
|
171
174
|
if (pathToFile.has(path)) {
|
|
172
|
-
throw new
|
|
175
|
+
throw new ExitError(
|
|
176
|
+
"Multiple generators produce " + rewritePathForRendering(path),
|
|
177
|
+
);
|
|
173
178
|
}
|
|
174
179
|
pathToFile.set(path, file);
|
|
175
180
|
pathToGenerator.set(path, generator);
|
|
@@ -179,10 +184,10 @@ class WatchModeMainLoop {
|
|
|
179
184
|
for (
|
|
180
185
|
let pathToKeep = path;
|
|
181
186
|
pathToKeep !== ".";
|
|
182
|
-
pathToKeep =
|
|
187
|
+
pathToKeep = Paths.dirname(pathToKeep)
|
|
183
188
|
) {
|
|
184
189
|
preExistingAbsolutePaths.delete(
|
|
185
|
-
|
|
190
|
+
Paths.resolve(Paths.join(skiroutDir, pathToKeep)),
|
|
186
191
|
);
|
|
187
192
|
}
|
|
188
193
|
}
|
|
@@ -196,9 +201,9 @@ class WatchModeMainLoop {
|
|
|
196
201
|
const oldFile = lastWriteBatch.pathToFile.get(p);
|
|
197
202
|
const generator = pathToGenerator.get(p)!;
|
|
198
203
|
for (const skiroutDir of generator.skiroutDirs) {
|
|
199
|
-
const fsPath =
|
|
204
|
+
const fsPath = Paths.join(skiroutDir, p);
|
|
200
205
|
if (oldFile?.code === newFile.code) {
|
|
201
|
-
const mtime = (await
|
|
206
|
+
const mtime = (await FileSystem.stat(fsPath)).mtime;
|
|
202
207
|
if (
|
|
203
208
|
mtime !== null &&
|
|
204
209
|
mtime.getDate() <= lastWriteBatch.writeTime.getDate()
|
|
@@ -206,8 +211,8 @@ class WatchModeMainLoop {
|
|
|
206
211
|
return;
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
|
-
await
|
|
210
|
-
await
|
|
214
|
+
await FileSystem.mkdir(Paths.dirname(fsPath), { recursive: true });
|
|
215
|
+
await FileSystem.writeFile(fsPath, newFile.code, "utf-8");
|
|
211
216
|
}
|
|
212
217
|
}),
|
|
213
218
|
);
|
|
@@ -218,8 +223,8 @@ class WatchModeMainLoop {
|
|
|
218
223
|
.sort((a, b) => b.localeCompare(a, "en-US"))
|
|
219
224
|
.map(async (p) => {
|
|
220
225
|
try {
|
|
221
|
-
await
|
|
222
|
-
} catch (
|
|
226
|
+
await FileSystem.rm(p, { force: true, recursive: true });
|
|
227
|
+
} catch (_e) {
|
|
223
228
|
// Ignore error.
|
|
224
229
|
}
|
|
225
230
|
}),
|
|
@@ -240,25 +245,19 @@ class WatchModeMainLoop {
|
|
|
240
245
|
};
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
async function isDirectory(path: string): Promise<boolean> {
|
|
244
|
-
try {
|
|
245
|
-
return (await fs.lstat(path)).isDirectory();
|
|
246
|
-
} catch (e) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
248
|
function checkNoOverlappingSkiroutDirs(skiroutDirs: readonly string[]): void {
|
|
252
249
|
for (let i = 0; i < skiroutDirs.length; ++i) {
|
|
253
250
|
for (let j = i + 1; j < skiroutDirs.length; ++j) {
|
|
254
|
-
const dirA =
|
|
255
|
-
const dirB =
|
|
251
|
+
const dirA = Paths.normalize(skiroutDirs[i]!);
|
|
252
|
+
const dirB = Paths.normalize(skiroutDirs[j]!);
|
|
256
253
|
|
|
257
254
|
if (
|
|
258
|
-
dirA.startsWith(dirB +
|
|
259
|
-
dirB.startsWith(dirA +
|
|
255
|
+
dirA.startsWith(dirB + Paths.sep) ||
|
|
256
|
+
dirB.startsWith(dirA + Paths.sep)
|
|
260
257
|
) {
|
|
261
|
-
throw new
|
|
258
|
+
throw new ExitError(
|
|
259
|
+
`Overlapping skirout directories: ${dirA} and ${dirB}`,
|
|
260
|
+
);
|
|
262
261
|
}
|
|
263
262
|
}
|
|
264
263
|
}
|
|
@@ -270,7 +269,7 @@ interface ModuleFormatResult {
|
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
async function format(root: string, mode: "fix" | "check"): Promise<void> {
|
|
273
|
-
const skirFiles = await glob(
|
|
272
|
+
const skirFiles = await glob(Paths.join(root, "**/*.skir"), {
|
|
274
273
|
withFileTypes: true,
|
|
275
274
|
});
|
|
276
275
|
const pathToFormatResult = new Map<string, ModuleFormatResult>();
|
|
@@ -280,7 +279,9 @@ async function format(root: string, mode: "fix" | "check"): Promise<void> {
|
|
|
280
279
|
}
|
|
281
280
|
const unformattedCode = REAL_FILE_SYSTEM.readTextFile(skirFile.fullpath());
|
|
282
281
|
if (unformattedCode === undefined) {
|
|
283
|
-
throw new
|
|
282
|
+
throw new ExitError(
|
|
283
|
+
"Cannot read " + rewritePathForRendering(skirFile.fullpath()),
|
|
284
|
+
);
|
|
284
285
|
}
|
|
285
286
|
const tokens = tokenizeModule(unformattedCode, "");
|
|
286
287
|
if (tokens.errors.length) {
|
|
@@ -295,7 +296,7 @@ async function format(root: string, mode: "fix" | "check"): Promise<void> {
|
|
|
295
296
|
}
|
|
296
297
|
let numFilesNotFormatted = 0;
|
|
297
298
|
for (const [path, result] of pathToFormatResult) {
|
|
298
|
-
const relativePath =
|
|
299
|
+
const relativePath = Paths.relative(root, path).replace(/\\/g, "/");
|
|
299
300
|
if (mode === "fix") {
|
|
300
301
|
if (result.alreadyFormatted) {
|
|
301
302
|
console.log(`${makeGray(relativePath)} (unchanged)`);
|
|
@@ -333,14 +334,14 @@ async function main(): Promise<void> {
|
|
|
333
334
|
|
|
334
335
|
const root = args.root || ".";
|
|
335
336
|
|
|
336
|
-
if (!(await isDirectory(root
|
|
337
|
+
if (!(await isDirectory(root))) {
|
|
337
338
|
console.error(makeRed(`Not a directory: ${root}`));
|
|
338
339
|
process.exit(1);
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
switch (args.kind) {
|
|
342
343
|
case "init": {
|
|
343
|
-
initializeProject(root
|
|
344
|
+
initializeProject(root);
|
|
344
345
|
return;
|
|
345
346
|
}
|
|
346
347
|
case "help":
|
|
@@ -349,14 +350,7 @@ async function main(): Promise<void> {
|
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
!paths.isAbsolute(skirConfigPath) &&
|
|
355
|
-
!/^\.{1,2}[/\\]$/.test(skirConfigPath)
|
|
356
|
-
) {
|
|
357
|
-
// To make it clear that it's a path, prepend "./"
|
|
358
|
-
skirConfigPath = `.${paths.sep}${skirConfigPath}`;
|
|
359
|
-
}
|
|
353
|
+
const skirConfigPath = rewritePathForRendering(Paths.join(root, "skir.yml"));
|
|
360
354
|
const skirConfigCode = REAL_FILE_SYSTEM.readTextFile(skirConfigPath);
|
|
361
355
|
if (skirConfigCode === undefined) {
|
|
362
356
|
console.error(makeRed(`Cannot find ${skirConfigPath}`));
|
|
@@ -366,12 +360,16 @@ async function main(): Promise<void> {
|
|
|
366
360
|
const skirConfigResult = await parseSkirConfig(skirConfigCode, "import-mods");
|
|
367
361
|
if (skirConfigResult.errors.length > 0) {
|
|
368
362
|
console.error(makeRed("Invalid skir config"));
|
|
369
|
-
|
|
363
|
+
const { maybeForgotToEditAfterInit } = skirConfigResult;
|
|
364
|
+
renderSkirConfigErrors(skirConfigResult.errors, {
|
|
365
|
+
skirConfigPath,
|
|
366
|
+
maybeForgotToEditAfterInit,
|
|
367
|
+
});
|
|
370
368
|
process.exit(1);
|
|
371
369
|
}
|
|
372
370
|
const skirConfig = skirConfigResult.skirConfig!;
|
|
373
371
|
|
|
374
|
-
const srcDir =
|
|
372
|
+
const srcDir = Paths.join(root, skirConfig.srcDir || ".");
|
|
375
373
|
|
|
376
374
|
switch (args.kind) {
|
|
377
375
|
case "format": {
|
|
@@ -383,15 +381,9 @@ async function main(): Promise<void> {
|
|
|
383
381
|
// Run the skir code generators in watch mode or once.
|
|
384
382
|
const generatorBundles: GeneratorBundle[] = await Promise.all(
|
|
385
383
|
skirConfig.generators.map((config) =>
|
|
386
|
-
makeGeneratorBundle(config, root
|
|
384
|
+
makeGeneratorBundle(config, root),
|
|
387
385
|
),
|
|
388
386
|
);
|
|
389
|
-
// Sort for consistency.
|
|
390
|
-
generatorBundles.sort((a, b) => {
|
|
391
|
-
const aId = a.generator.id;
|
|
392
|
-
const bId = b.generator.id;
|
|
393
|
-
return aId.localeCompare(bId, "en-US");
|
|
394
|
-
});
|
|
395
387
|
// Look for duplicates.
|
|
396
388
|
for (let i = 0; i < generatorBundles.length - 1; ++i) {
|
|
397
389
|
const { id } = generatorBundles[i]!.generator;
|
|
@@ -417,11 +409,11 @@ async function main(): Promise<void> {
|
|
|
417
409
|
case "snapshot": {
|
|
418
410
|
if (args.subcommand === "view") {
|
|
419
411
|
viewSnapshot({
|
|
420
|
-
rootDir: root
|
|
412
|
+
rootDir: root,
|
|
421
413
|
});
|
|
422
414
|
} else {
|
|
423
415
|
takeSnapshot({
|
|
424
|
-
rootDir: root
|
|
416
|
+
rootDir: root,
|
|
425
417
|
srcDir: srcDir,
|
|
426
418
|
check: args.subcommand === "check",
|
|
427
419
|
});
|
|
@@ -435,4 +427,14 @@ async function main(): Promise<void> {
|
|
|
435
427
|
}
|
|
436
428
|
}
|
|
437
429
|
|
|
438
|
-
|
|
430
|
+
try {
|
|
431
|
+
await main();
|
|
432
|
+
} catch (e) {
|
|
433
|
+
if (e instanceof Error) {
|
|
434
|
+
console.error(makeRed(e.message));
|
|
435
|
+
if (e instanceof ExitError) {
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
throw e;
|
|
440
|
+
}
|