@reventlessdev/reventless-spec 3.0.0-alpha.143 → 3.0.0-alpha.144
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/CHANGELOG.md +13 -0
- package/package.json +2 -2
- package/run-certify-trait.mjs +2 -1
- package/run-generator.mjs +2 -1
- package/run-graft-trait.mjs +2 -1
- package/run-platform-generator.mjs +2 -1
- package/run-trait-manifest.mjs +2 -1
- package/src/CliArgs.res +209 -0
- package/src/CliArgs.res.mjs +325 -0
- package/src/generator/CertifyTrait.res +96 -93
- package/src/generator/CertifyTrait.res.mjs +92 -75
- package/src/generator/GraftTrait.res +147 -148
- package/src/generator/GraftTrait.res.mjs +131 -128
- package/src/generator/PlatformGenerator.res +50 -31
- package/src/generator/PlatformGenerator.res.mjs +107 -76
- package/src/generator/PluginGenerator.res +29 -29
- package/src/generator/PluginGenerator.res.mjs +87 -55
- package/src/generator/TraitManifestCli.res +69 -68
- package/src/generator/TraitManifestCli.res.mjs +67 -51
- package/src/lifecycle/CheckLifecycleModel.res +203 -189
- package/src/lifecycle/CheckLifecycleModel.res.mjs +146 -141
- package/src/types/PrepareAccounts.res +38 -57
- package/src/types/PrepareAccounts.res.mjs +28 -82
|
@@ -46,30 +46,6 @@ let usage = `Usage: graft-trait <trait-package> --into <srcDir> --tests <testsDi
|
|
|
46
46
|
|
|
47
47
|
// ── Arguments ────────────────────────────────────────────────────────────────
|
|
48
48
|
|
|
49
|
-
/** `--key value` pairs, in order, with the leading positional taken off first.
|
|
50
|
-
A flag with no value (`--dry-run`) reads as `true`, so the config schema can
|
|
51
|
-
take it as a boolean without the CLI knowing which keys are flags. */
|
|
52
|
-
let parseFlags = (args: array<string>): dict<JSON.t> => {
|
|
53
|
-
let out = Dict.make()
|
|
54
|
-
let rec go = i =>
|
|
55
|
-
switch args->Array.get(i) {
|
|
56
|
-
| None => ()
|
|
57
|
-
| Some(arg) if arg->String.startsWith("--") =>
|
|
58
|
-
let key = arg->String.slice(~start=2, ~end=arg->String.length)
|
|
59
|
-
switch args->Array.get(i + 1) {
|
|
60
|
-
| Some(value) if !(value->String.startsWith("--")) =>
|
|
61
|
-
out->Dict.set(key, JSON.Encode.string(value))
|
|
62
|
-
go(i + 2)
|
|
63
|
-
| _ =>
|
|
64
|
-
out->Dict.set(key, JSON.Encode.bool(true))
|
|
65
|
-
go(i + 1)
|
|
66
|
-
}
|
|
67
|
-
| Some(_) => go(i + 1)
|
|
68
|
-
}
|
|
69
|
-
go(0)
|
|
70
|
-
out
|
|
71
|
-
}
|
|
72
|
-
|
|
73
49
|
/** A comma-separated value becomes an array. Applied only to keys the trait's
|
|
74
50
|
schema declares as arrays, so a name that happens to contain a comma is not
|
|
75
51
|
split behind the author's back. */
|
|
@@ -106,134 +82,157 @@ let arrayFieldNames = (schema: S.t<unknown>): array<string> =>
|
|
|
106
82
|
|
|
107
83
|
// ── Entry point ──────────────────────────────────────────────────────────────
|
|
108
84
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
85
|
+
/** The trait, where its graft goes, and every other `--key value` as a field of
|
|
86
|
+
the trait's config — a string, or `true` for a key given no value. */
|
|
87
|
+
type args = {
|
|
88
|
+
traitPackage: string,
|
|
89
|
+
into: string,
|
|
90
|
+
tests: string,
|
|
91
|
+
dryRun: bool,
|
|
92
|
+
fields: dict<JSON.t>,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let parseArgs = (argv: array<string>): result<args, string> =>
|
|
96
|
+
CliArgs.parse(~strings=["into", "tests"], ~bools=["dry-run"], ~pairs=true, argv)
|
|
97
|
+
->Result.flatMap(a => a->CliArgs.atMost(1))
|
|
98
|
+
->Result.flatMap(a =>
|
|
99
|
+
switch (
|
|
100
|
+
a->CliArgs.positionals->Array.get(0),
|
|
101
|
+
a->CliArgs.string("into"),
|
|
102
|
+
a->CliArgs.string("tests"),
|
|
103
|
+
) {
|
|
104
|
+
| (None | Some(""), _, _) => Error("<trait-package> is required.")
|
|
105
|
+
| (Some(traitPackage), Some(into), Some(tests)) =>
|
|
106
|
+
// A key given no value reads as `true`, so the config schema can take it
|
|
107
|
+
// as a boolean without the tool knowing which keys are flags.
|
|
108
|
+
let fields = Dict.make()
|
|
109
|
+
a
|
|
110
|
+
->CliArgs.pairs
|
|
111
|
+
->Array.forEach(((key, value)) =>
|
|
112
|
+
fields->Dict.set(
|
|
113
|
+
key,
|
|
114
|
+
switch value {
|
|
115
|
+
| Text(text) => JSON.Encode.string(text)
|
|
116
|
+
| Present => JSON.Encode.bool(true)
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
Ok({traitPackage, into, tests, dryRun: a->CliArgs.bool("dry-run"), fields})
|
|
121
|
+
| (Some(_), _, _) => Error("--into and --tests are both required.")
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
let cli: CliArgs.cli<args> = {bin: "graft-trait", usage, parse: parseArgs}
|
|
126
|
+
|
|
127
|
+
let main = () =>
|
|
128
|
+
CliArgs.run(cli, async ({traitPackage, into, tests, dryRun, fields}) => {
|
|
129
|
+
// Resolved by name, from the consumer's own `node_modules`. A trait
|
|
130
|
+
// that ships no scaffold fails here, saying so, rather than half-way
|
|
131
|
+
// through writing files.
|
|
132
|
+
// `@scope/trait-attachments` → `Attachments_Scaffold`;
|
|
133
|
+
// `@scope/trait-address-geocoding` → `AddressGeocoding_Scaffold`.
|
|
134
|
+
let scaffoldModule =
|
|
135
|
+
traitPackage
|
|
136
|
+
->String.split("/")
|
|
137
|
+
->Array.last
|
|
138
|
+
->Option.getOr("")
|
|
139
|
+
->String.replace("trait-", "")
|
|
140
|
+
->String.split("-")
|
|
141
|
+
->Array.map(part =>
|
|
142
|
+
part->String.charAt(0)->String.toUpperCase ++
|
|
143
|
+
part->String.slice(~start=1, ~end=part->String.length)
|
|
144
|
+
)
|
|
145
|
+
->Array.join("") ++ "_Scaffold"
|
|
146
|
+
let specifier = `${traitPackage}/src/${scaffoldModule}.res.mjs`
|
|
147
|
+
// Resolved from the **caller's** directory, not this file's. The trait
|
|
148
|
+
// is a dependency of the plugin being grafted, and deliberately not one
|
|
149
|
+
// of this package — a bare `import()` here would look in
|
|
150
|
+
// `reventless-spec`'s own tree and never find it. Same reason
|
|
151
|
+
// `HostShellDist` reaches for the host shell this way.
|
|
152
|
+
let modulePath = try NodeModule.createRequire(
|
|
153
|
+
NodeProcess.cwd() ++ "/index.js",
|
|
154
|
+
)->NodeModule.requireResolve(specifier) catch {
|
|
155
|
+
| _ => specifier
|
|
156
|
+
}
|
|
157
|
+
let scaffold: scaffoldExports = try await dynImport(
|
|
158
|
+
NodeUrl.pathToFileURL(modulePath)["href"],
|
|
159
|
+
) catch {
|
|
160
|
+
| _ =>
|
|
161
|
+
fail(
|
|
162
|
+
`${traitPackage} ships no scaffold (looked for ${specifier}).\n` ++
|
|
163
|
+
` Not every trait has one — a trait whose graft is all patches has nothing to write.\n` ++ ` Write the graft by hand instead; its README says what the host must declare.`,
|
|
164
|
+
)
|
|
165
|
+
%raw(`undefined`)
|
|
115
166
|
}
|
|
116
|
-
| Some(traitPackage) => {
|
|
117
|
-
let flags = parseFlags(argv->Array.slice(~start=1, ~end=argv->Array.length))
|
|
118
|
-
let stringFlag = key => flags->Dict.get(key)->Option.flatMap(JSON.Decode.string)
|
|
119
|
-
let into = stringFlag("into")
|
|
120
|
-
let tests = stringFlag("tests")
|
|
121
|
-
let dryRun = flags->Dict.get("dry-run")->Option.isSome
|
|
122
|
-
|
|
123
|
-
switch (into, tests) {
|
|
124
|
-
| (None, _) | (_, None) => fail("--into and --tests are both required.\n\n" ++ usage)
|
|
125
|
-
| (Some(into), Some(tests)) => {
|
|
126
|
-
// Resolved by name, from the consumer's own `node_modules`. A trait
|
|
127
|
-
// that ships no scaffold fails here, saying so, rather than half-way
|
|
128
|
-
// through writing files.
|
|
129
|
-
// `@scope/trait-attachments` → `Attachments_Scaffold`;
|
|
130
|
-
// `@scope/trait-address-geocoding` → `AddressGeocoding_Scaffold`.
|
|
131
|
-
let scaffoldModule =
|
|
132
|
-
traitPackage
|
|
133
|
-
->String.split("/")
|
|
134
|
-
->Array.last
|
|
135
|
-
->Option.getOr("")
|
|
136
|
-
->String.replace("trait-", "")
|
|
137
|
-
->String.split("-")
|
|
138
|
-
->Array.map(part =>
|
|
139
|
-
part->String.charAt(0)->String.toUpperCase ++
|
|
140
|
-
part->String.slice(~start=1, ~end=part->String.length)
|
|
141
|
-
)
|
|
142
|
-
->Array.join("") ++ "_Scaffold"
|
|
143
|
-
let specifier = `${traitPackage}/src/${scaffoldModule}.res.mjs`
|
|
144
|
-
// Resolved from the **caller's** directory, not this file's. The trait
|
|
145
|
-
// is a dependency of the plugin being grafted, and deliberately not one
|
|
146
|
-
// of this package — a bare `import()` here would look in
|
|
147
|
-
// `reventless-spec`'s own tree and never find it. Same reason
|
|
148
|
-
// `HostShellDist` reaches for the host shell this way.
|
|
149
|
-
let modulePath = try NodeModule.createRequire(
|
|
150
|
-
NodeProcess.cwd() ++ "/index.js",
|
|
151
|
-
)->NodeModule.requireResolve(specifier) catch {
|
|
152
|
-
| _ => specifier
|
|
153
|
-
}
|
|
154
|
-
let scaffold: scaffoldExports = try await dynImport(
|
|
155
|
-
NodeUrl.pathToFileURL(modulePath)["href"],
|
|
156
|
-
) catch {
|
|
157
|
-
| _ =>
|
|
158
|
-
fail(
|
|
159
|
-
`${traitPackage} ships no scaffold (looked for ${specifier}).\n` ++
|
|
160
|
-
` Not every trait has one — a trait whose graft is all patches has nothing to write.\n` ++ ` Write the graft by hand instead; its README says what the host must declare.`,
|
|
161
|
-
)
|
|
162
|
-
%raw(`undefined`)
|
|
163
|
-
}
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
["into", "tests", "dry-run"]->Array.forEach(k => raw->Dict.delete(k))
|
|
178
|
-
|
|
179
|
-
let config = try raw
|
|
180
|
-
->JSON.Encode.object
|
|
181
|
-
->Util_Sury.fromJson(scaffold.configSchema) catch {
|
|
182
|
-
| exn => {
|
|
183
|
-
fail(
|
|
184
|
-
`${traitPackage} refused this config: ${Util_Sury.exnMessage(
|
|
185
|
-
exn,
|
|
186
|
-
)}\n` ++ ` Every --key is a field of the trait's own config; it decides what it needs.`,
|
|
187
|
-
)
|
|
188
|
-
%raw(`undefined`)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
168
|
+
// The trait validates its own config, so the CLI never learns a
|
|
169
|
+
// trait's vocabulary — and a misspelled key is named here.
|
|
170
|
+
let listFields = arrayFieldNames(scaffold.configSchema)
|
|
171
|
+
let raw = Dict.make()
|
|
172
|
+
fields
|
|
173
|
+
->Dict.toArray
|
|
174
|
+
->Array.forEach(((key, value)) =>
|
|
175
|
+
switch (listFields->Array.includes(key), value->JSON.Decode.string) {
|
|
176
|
+
| (true, Some(text)) => raw->Dict.set(key, splitList(text))
|
|
177
|
+
| _ => raw->Dict.set(key, value)
|
|
178
|
+
}
|
|
179
|
+
)
|
|
191
180
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// generator can reproduce.
|
|
207
|
-
Console.log(`Skipped (exists): ${path}`)
|
|
208
|
-
} else {
|
|
209
|
-
NodeFs.mkdirSync(NodePath.dirname(path), {recursive: true})
|
|
210
|
-
NodeFs.writeFileSync(path, contents)
|
|
211
|
-
Console.log(`Wrote: ${path}`)
|
|
212
|
-
}
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
if patches->Array.length > 0 {
|
|
216
|
-
Console.log("")
|
|
217
|
-
Console.log(
|
|
218
|
-
"── Paste these; they go into files you already own ──────────────",
|
|
219
|
-
)
|
|
220
|
-
patches->Array.forEach(({into, at, contents}) => {
|
|
221
|
-
Console.log("")
|
|
222
|
-
Console.log(`# ${into} — ${at}`)
|
|
223
|
-
Console.log(contents)
|
|
224
|
-
})
|
|
225
|
-
Console.log("")
|
|
226
|
-
Console.log(
|
|
227
|
-
"Printed rather than written: placing an arm in an existing ordered `switch`\n" ++
|
|
228
|
-
"is an AST operation, and a text splice into the wrong arm is a bug the\n" ++ "compiler cannot see.",
|
|
229
|
-
)
|
|
230
|
-
}
|
|
181
|
+
let config = try raw
|
|
182
|
+
->JSON.Encode.object
|
|
183
|
+
->Util_Sury.fromJson(scaffold.configSchema) catch {
|
|
184
|
+
| exn => {
|
|
185
|
+
fail(
|
|
186
|
+
`${traitPackage} refused this config: ${Util_Sury.exnMessage(
|
|
187
|
+
exn,
|
|
188
|
+
)}\n` ++ ` Every --key is a field of the trait's own config; it decides what it needs.`,
|
|
189
|
+
)
|
|
190
|
+
%raw(`undefined`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let {files, patches} = scaffold.emit(~config, ~into, ~tests)
|
|
231
195
|
|
|
232
|
-
|
|
233
|
-
|
|
196
|
+
files->Array.forEach(({path, contents}) =>
|
|
197
|
+
if dryRun {
|
|
198
|
+
Console.log(
|
|
199
|
+
`Would write: ${path} (${contents
|
|
200
|
+
->String.split("\n")
|
|
201
|
+
->Array.length
|
|
202
|
+
->Int.toString} lines)`,
|
|
203
|
+
)
|
|
204
|
+
} else if NodeFs.existsSync(path) {
|
|
205
|
+
// Never overwrite. From the moment a graft lands it is the host's
|
|
206
|
+
// source, edited with the policy the trait deliberately does not
|
|
207
|
+
// write — silently replacing it would delete exactly the part no
|
|
208
|
+
// generator can reproduce.
|
|
209
|
+
Console.log(`Skipped (exists): ${path}`)
|
|
210
|
+
} else {
|
|
211
|
+
NodeFs.mkdirSync(NodePath.dirname(path), {recursive: true})
|
|
212
|
+
NodeFs.writeFileSync(path, contents)
|
|
213
|
+
Console.log(`Wrote: ${path}`)
|
|
234
214
|
}
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if patches->Array.length > 0 {
|
|
218
|
+
Console.log("")
|
|
219
|
+
Console.log(
|
|
220
|
+
"── Paste these; they go into files you already own ──────────────",
|
|
221
|
+
)
|
|
222
|
+
patches->Array.forEach(({into, at, contents}) => {
|
|
223
|
+
Console.log("")
|
|
224
|
+
Console.log(`# ${into} — ${at}`)
|
|
225
|
+
Console.log(contents)
|
|
226
|
+
})
|
|
227
|
+
Console.log("")
|
|
228
|
+
Console.log(
|
|
229
|
+
"Printed rather than written: placing an arm in an existing ordered `switch`\n" ++
|
|
230
|
+
"is an AST operation, and a text splice into the wrong arm is a bug the\n" ++ "compiler cannot see.",
|
|
231
|
+
)
|
|
235
232
|
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
233
|
|
|
239
|
-
|
|
234
|
+
NodeProcess.exit(0)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// No top-level call: `../../run-graft-trait.mjs` invokes [main], so a test can
|
|
238
|
+
// import this module.
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import * as Nodefs from "node:fs";
|
|
4
4
|
import * as Nodeurl from "node:url";
|
|
5
5
|
import * as Nodepath from "node:path";
|
|
6
|
-
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
7
6
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
8
7
|
import * as Nodemodule from "node:module";
|
|
9
8
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
10
9
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
11
|
+
import * as CliArgs$Reventless from "../CliArgs.res.mjs";
|
|
11
12
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
12
13
|
import * as Util_Sury$Reventless from "../util/Util_Sury.res.mjs";
|
|
13
14
|
|
|
@@ -26,35 +27,6 @@ let usage = `Usage: graft-trait <trait-package> --into <srcDir> --tests <testsDi
|
|
|
26
27
|
Every other --key value is a field of the trait's config. The trait validates
|
|
27
28
|
them, so run it once to be told what it wants.`;
|
|
28
29
|
|
|
29
|
-
function parseFlags(args) {
|
|
30
|
-
let out = {};
|
|
31
|
-
let go = _i => {
|
|
32
|
-
while (true) {
|
|
33
|
-
let i = _i;
|
|
34
|
-
let arg = args[i];
|
|
35
|
-
if (arg === undefined) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (arg.startsWith("--")) {
|
|
39
|
-
let key = arg.slice(2, arg.length);
|
|
40
|
-
let value = args[i + 1 | 0];
|
|
41
|
-
if (value !== undefined && !value.startsWith("--")) {
|
|
42
|
-
out[key] = value;
|
|
43
|
-
_i = i + 2 | 0;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
out[key] = true;
|
|
47
|
-
_i = i + 1 | 0;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
_i = i + 1 | 0;
|
|
51
|
-
continue;
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
go(0);
|
|
55
|
-
return out;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
30
|
function splitList(value) {
|
|
59
31
|
return value.split(",").map(part => part.trim()).filter(part => part !== "").map(prim => prim);
|
|
60
32
|
}
|
|
@@ -82,112 +54,143 @@ function arrayFieldNames(schema) {
|
|
|
82
54
|
}
|
|
83
55
|
}
|
|
84
56
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let tests = stringFlag("tests");
|
|
99
|
-
let dryRun = Stdlib_Option.isSome(flags["dry-run"]);
|
|
100
|
-
if (into === undefined) {
|
|
101
|
-
return fail("--into and --tests are both required.\n\n" + usage);
|
|
102
|
-
}
|
|
103
|
-
if (tests === undefined) {
|
|
104
|
-
return fail("--into and --tests are both required.\n\n" + usage);
|
|
105
|
-
}
|
|
106
|
-
let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1, part.length)).join("") + "_Scaffold";
|
|
107
|
-
let specifier = traitPackage + `/src/` + scaffoldModule + `.res.mjs`;
|
|
108
|
-
let modulePath;
|
|
109
|
-
try {
|
|
110
|
-
modulePath = Nodemodule.createRequire(process.cwd() + "/index.js").resolve(specifier);
|
|
111
|
-
} catch (exn) {
|
|
112
|
-
modulePath = specifier;
|
|
113
|
-
}
|
|
114
|
-
let scaffold;
|
|
115
|
-
try {
|
|
116
|
-
scaffold = await import(Nodeurl.pathToFileURL(modulePath).href);
|
|
117
|
-
} catch (exn$1) {
|
|
118
|
-
fail(traitPackage + ` ships no scaffold (looked for ` + specifier + `).\n Not every trait has one — a trait whose graft is all patches has nothing to write.\n Write the graft by hand instead; its README says what the host must declare.`);
|
|
119
|
-
scaffold = undefined;
|
|
120
|
-
}
|
|
121
|
-
let listFields = arrayFieldNames(scaffold.configSchema);
|
|
122
|
-
let raw = {};
|
|
123
|
-
Object.entries(flags).forEach(param => {
|
|
124
|
-
let value = param[1];
|
|
125
|
-
let key = param[0];
|
|
126
|
-
let match = listFields.includes(key);
|
|
127
|
-
let match$1 = Stdlib_JSON.Decode.string(value);
|
|
128
|
-
if (match && match$1 !== undefined) {
|
|
129
|
-
raw[key] = splitList(match$1);
|
|
130
|
-
} else {
|
|
131
|
-
raw[key] = value;
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
[
|
|
135
|
-
"into",
|
|
136
|
-
"tests",
|
|
137
|
-
"dry-run"
|
|
138
|
-
].forEach(k => Stdlib_Dict.$$delete(raw, k));
|
|
139
|
-
let config;
|
|
140
|
-
try {
|
|
141
|
-
config = Util_Sury$Reventless.fromJson(raw, scaffold.configSchema);
|
|
142
|
-
} catch (raw_exn) {
|
|
143
|
-
let exn$2 = Primitive_exceptions.internalToException(raw_exn);
|
|
144
|
-
fail(traitPackage + ` refused this config: ` + Util_Sury$Reventless.exnMessage(exn$2, undefined) + `\n Every --key is a field of the trait's own config; it decides what it needs.`);
|
|
145
|
-
config = undefined;
|
|
146
|
-
}
|
|
147
|
-
let match = scaffold.emit(config, into, tests);
|
|
148
|
-
let patches = match.patches;
|
|
149
|
-
match.files.forEach(param => {
|
|
150
|
-
let contents = param.contents;
|
|
151
|
-
let path = param.path;
|
|
152
|
-
if (dryRun) {
|
|
153
|
-
console.log(`Would write: ` + path + ` (` + contents.split("\n").length.toString() + ` lines)`);
|
|
154
|
-
} else if (Nodefs.existsSync(path)) {
|
|
155
|
-
console.log(`Skipped (exists): ` + path);
|
|
156
|
-
} else {
|
|
157
|
-
Nodefs.mkdirSync(Nodepath.dirname(path), {
|
|
158
|
-
recursive: true
|
|
159
|
-
});
|
|
160
|
-
Nodefs.writeFileSync(path, contents, "utf8");
|
|
161
|
-
console.log(`Wrote: ` + path);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
if (patches.length !== 0) {
|
|
165
|
-
console.log("");
|
|
166
|
-
console.log("── Paste these; they go into files you already own ──────────────");
|
|
167
|
-
patches.forEach(param => {
|
|
168
|
-
console.log("");
|
|
169
|
-
console.log(`# ` + param.into + ` — ` + param.at);
|
|
170
|
-
console.log(param.contents);
|
|
171
|
-
});
|
|
172
|
-
console.log("");
|
|
173
|
-
console.log("Printed rather than written: placing an arm in an existing ordered `switch`\nis an AST operation, and a text splice into the wrong arm is a bug the\ncompiler cannot see.");
|
|
174
|
-
}
|
|
175
|
-
process.exit(0);
|
|
176
|
-
return;
|
|
57
|
+
function parseArgs(argv) {
|
|
58
|
+
return Stdlib_Result.flatMap(Stdlib_Result.flatMap(CliArgs$Reventless.parse([
|
|
59
|
+
"into",
|
|
60
|
+
"tests"
|
|
61
|
+
], ["dry-run"], undefined, true, argv), a => CliArgs$Reventless.atMost(a, 1)), a => {
|
|
62
|
+
let match = CliArgs$Reventless.positionals(a)[0];
|
|
63
|
+
let match$1 = CliArgs$Reventless.string(a, "into");
|
|
64
|
+
let match$2 = CliArgs$Reventless.string(a, "tests");
|
|
65
|
+
if (match === undefined) {
|
|
66
|
+
return {
|
|
67
|
+
TAG: "Error",
|
|
68
|
+
_0: "<trait-package> is required."
|
|
69
|
+
};
|
|
177
70
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
71
|
+
if (match === "") {
|
|
72
|
+
return {
|
|
73
|
+
TAG: "Error",
|
|
74
|
+
_0: "<trait-package> is required."
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (match$1 === undefined) {
|
|
78
|
+
return {
|
|
79
|
+
TAG: "Error",
|
|
80
|
+
_0: "--into and --tests are both required."
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (match$2 === undefined) {
|
|
84
|
+
return {
|
|
85
|
+
TAG: "Error",
|
|
86
|
+
_0: "--into and --tests are both required."
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
let fields = {};
|
|
90
|
+
CliArgs$Reventless.pairs(a).forEach(param => {
|
|
91
|
+
let value = param[1];
|
|
92
|
+
let tmp;
|
|
93
|
+
tmp = typeof value !== "object" ? true : value._0;
|
|
94
|
+
fields[param[0]] = tmp;
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
TAG: "Ok",
|
|
98
|
+
_0: {
|
|
99
|
+
traitPackage: match,
|
|
100
|
+
into: match$1,
|
|
101
|
+
tests: match$2,
|
|
102
|
+
dryRun: CliArgs$Reventless.bool(a, "dry-run"),
|
|
103
|
+
fields: fields
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
});
|
|
181
107
|
}
|
|
182
108
|
|
|
183
|
-
|
|
109
|
+
let cli = {
|
|
110
|
+
bin: "graft-trait",
|
|
111
|
+
usage: usage,
|
|
112
|
+
parse: parseArgs
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function main() {
|
|
116
|
+
return CliArgs$Reventless.run(cli, undefined, undefined, async param => {
|
|
117
|
+
let dryRun = param.dryRun;
|
|
118
|
+
let traitPackage = param.traitPackage;
|
|
119
|
+
let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1, part.length)).join("") + "_Scaffold";
|
|
120
|
+
let specifier = traitPackage + `/src/` + scaffoldModule + `.res.mjs`;
|
|
121
|
+
let modulePath;
|
|
122
|
+
try {
|
|
123
|
+
modulePath = Nodemodule.createRequire(process.cwd() + "/index.js").resolve(specifier);
|
|
124
|
+
} catch (exn) {
|
|
125
|
+
modulePath = specifier;
|
|
126
|
+
}
|
|
127
|
+
let scaffold;
|
|
128
|
+
try {
|
|
129
|
+
scaffold = await import(Nodeurl.pathToFileURL(modulePath).href);
|
|
130
|
+
} catch (exn$1) {
|
|
131
|
+
fail(traitPackage + ` ships no scaffold (looked for ` + specifier + `).\n Not every trait has one — a trait whose graft is all patches has nothing to write.\n Write the graft by hand instead; its README says what the host must declare.`);
|
|
132
|
+
scaffold = undefined;
|
|
133
|
+
}
|
|
134
|
+
let listFields = arrayFieldNames(scaffold.configSchema);
|
|
135
|
+
let raw = {};
|
|
136
|
+
Object.entries(param.fields).forEach(param => {
|
|
137
|
+
let value = param[1];
|
|
138
|
+
let key = param[0];
|
|
139
|
+
let match = listFields.includes(key);
|
|
140
|
+
let match$1 = Stdlib_JSON.Decode.string(value);
|
|
141
|
+
if (match && match$1 !== undefined) {
|
|
142
|
+
raw[key] = splitList(match$1);
|
|
143
|
+
} else {
|
|
144
|
+
raw[key] = value;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
let config;
|
|
148
|
+
try {
|
|
149
|
+
config = Util_Sury$Reventless.fromJson(raw, scaffold.configSchema);
|
|
150
|
+
} catch (raw_exn) {
|
|
151
|
+
let exn$2 = Primitive_exceptions.internalToException(raw_exn);
|
|
152
|
+
fail(traitPackage + ` refused this config: ` + Util_Sury$Reventless.exnMessage(exn$2, undefined) + `\n Every --key is a field of the trait's own config; it decides what it needs.`);
|
|
153
|
+
config = undefined;
|
|
154
|
+
}
|
|
155
|
+
let match = scaffold.emit(config, param.into, param.tests);
|
|
156
|
+
let patches = match.patches;
|
|
157
|
+
match.files.forEach(param => {
|
|
158
|
+
let contents = param.contents;
|
|
159
|
+
let path = param.path;
|
|
160
|
+
if (dryRun) {
|
|
161
|
+
console.log(`Would write: ` + path + ` (` + contents.split("\n").length.toString() + ` lines)`);
|
|
162
|
+
} else if (Nodefs.existsSync(path)) {
|
|
163
|
+
console.log(`Skipped (exists): ` + path);
|
|
164
|
+
} else {
|
|
165
|
+
Nodefs.mkdirSync(Nodepath.dirname(path), {
|
|
166
|
+
recursive: true
|
|
167
|
+
});
|
|
168
|
+
Nodefs.writeFileSync(path, contents, "utf8");
|
|
169
|
+
console.log(`Wrote: ` + path);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (patches.length !== 0) {
|
|
173
|
+
console.log("");
|
|
174
|
+
console.log("── Paste these; they go into files you already own ──────────────");
|
|
175
|
+
patches.forEach(param => {
|
|
176
|
+
console.log("");
|
|
177
|
+
console.log(`# ` + param.into + ` — ` + param.at);
|
|
178
|
+
console.log(param.contents);
|
|
179
|
+
});
|
|
180
|
+
console.log("");
|
|
181
|
+
console.log("Printed rather than written: placing an arm in an existing ordered `switch`\nis an AST operation, and a text splice into the wrong arm is a bug the\ncompiler cannot see.");
|
|
182
|
+
}
|
|
183
|
+
process.exit(0);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
184
186
|
|
|
185
187
|
export {
|
|
186
188
|
fail,
|
|
187
189
|
usage,
|
|
188
|
-
parseFlags,
|
|
189
190
|
splitList,
|
|
190
191
|
arrayFieldNames,
|
|
192
|
+
parseArgs,
|
|
193
|
+
cli,
|
|
191
194
|
main,
|
|
192
195
|
}
|
|
193
|
-
/*
|
|
196
|
+
/* node:fs Not a pure module */
|