@lewishowles/cli-style 0.13.0 → 0.15.0
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 +24 -0
- package/README.md +72 -10
- package/adapters/python/LICENSE +21 -0
- package/adapters/python/README.md +29 -0
- package/adapters/python/cli_style/__init__.py +73 -0
- package/adapters/python/cli_style/_wrappers.py +503 -0
- package/adapters/python/cli_style/core.py +136 -0
- package/adapters/python/pyproject.toml +32 -0
- package/adapters/swift/Package.swift +16 -0
- package/adapters/swift/Sources/CliStyle/CliStyle.swift +223 -0
- package/adapters/swift/Sources/CliStyle/Wrappers.swift +582 -0
- package/package.json +4 -2
- package/scripts/build-binary.sh +17 -3
- package/scripts/generate-adapters.js +485 -0
- package/scripts/package-binary.sh +6 -1
- package/src/catalogue/renderer-catalogue.js +958 -52
- package/src/cli/adapter-path-command.js +3 -3
- package/src/cli/render-command.js +6 -0
- package/src/gallery/render-gallery.js +40 -0
- package/src/index.js +3 -0
- package/src/patterns/audit-finding.js +4 -4
- package/src/patterns/command-result.js +2 -2
- package/src/patterns/diagnostic-report.js +6 -3
- package/src/patterns/helpers.js +11 -4
- package/src/patterns/next-step-block.js +2 -4
- package/src/patterns/task-summary.js +4 -4
- package/src/primitives/bullet-list.js +150 -0
- package/src/theme/bullets.js +41 -0
- package/adapters/python/cli_style.py +0 -428
- package/adapters/swift/CliStyle.swift +0 -482
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { rendererCatalogue } from "../src/catalogue/renderer-catalogue.js";
|
|
6
|
+
|
|
7
|
+
const scriptsDirectoryPath = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const repositoryRootPath = join(scriptsDirectoryPath, "..");
|
|
9
|
+
|
|
10
|
+
// Generated adapter modules are kept beside their hand-written language cores.
|
|
11
|
+
const pythonOutputPath = join(repositoryRootPath, "adapters/python/cli_style/_wrappers.py");
|
|
12
|
+
|
|
13
|
+
const swiftOutputPath = join(repositoryRootPath, "adapters/swift/Sources/CliStyle/Wrappers.swift");
|
|
14
|
+
|
|
15
|
+
// Package metadata and licence files checked alongside generated adapter modules.
|
|
16
|
+
const packageJsonPath = join(repositoryRootPath, "package.json");
|
|
17
|
+
const pythonProjectPath = join(repositoryRootPath, "adapters/python/pyproject.toml");
|
|
18
|
+
const repositoryLicensePath = join(repositoryRootPath, "LICENSE");
|
|
19
|
+
const pythonLicensePath = join(repositoryRootPath, "adapters/python/LICENSE");
|
|
20
|
+
|
|
21
|
+
// Map catalogue types to the closest useful Python annotations.
|
|
22
|
+
const pythonTypes = {
|
|
23
|
+
boolean: "bool",
|
|
24
|
+
number: "int | float",
|
|
25
|
+
object: "dict[str, Any]",
|
|
26
|
+
"object[]": "list[dict[str, Any]]",
|
|
27
|
+
"number[]": "list[int | float]",
|
|
28
|
+
string: "str",
|
|
29
|
+
"string[]": "list[str]",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Map catalogue types to the closest useful Swift annotations.
|
|
33
|
+
const swiftTypes = {
|
|
34
|
+
boolean: "Bool",
|
|
35
|
+
number: "Int",
|
|
36
|
+
"object[]": "[[String: Any]]",
|
|
37
|
+
"number[]": "[Double]",
|
|
38
|
+
string: "String",
|
|
39
|
+
"string[]": "[String]",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Keep language-specific syntax in one descriptor so the wrapper helpers stay shared.
|
|
43
|
+
const languageDescriptors = {
|
|
44
|
+
python: {
|
|
45
|
+
booleanLiterals: {
|
|
46
|
+
false: "False",
|
|
47
|
+
true: "True",
|
|
48
|
+
},
|
|
49
|
+
defaultArrayPayload: (name) => `[] if ${name} is None else ${name}`,
|
|
50
|
+
nullableType: (type) => `${type} | None`,
|
|
51
|
+
nullLiteral: "None",
|
|
52
|
+
parameterName: (parameter) =>
|
|
53
|
+
parameter.singular ?? parameter.languageNames?.python ?? toSnakeCase(parameter.name),
|
|
54
|
+
singularPayload: (name) => `[${name}] if ${name} else []`,
|
|
55
|
+
types: pythonTypes,
|
|
56
|
+
},
|
|
57
|
+
swift: {
|
|
58
|
+
booleanLiterals: {
|
|
59
|
+
false: "false",
|
|
60
|
+
true: "true",
|
|
61
|
+
},
|
|
62
|
+
defaultArrayPayload: (name) => `${name} ?? []`,
|
|
63
|
+
nullableType: (type) => `${type}?`,
|
|
64
|
+
nullLiteral: "nil",
|
|
65
|
+
parameterName: (parameter) =>
|
|
66
|
+
parameter.singular ?? parameter.languageNames?.swift ?? parameter.name,
|
|
67
|
+
singularPayload: (name) => `${name}.isEmpty ? [] : [${name}]`,
|
|
68
|
+
types: swiftTypes,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert a JavaScript API name to the snake-case name used by Python.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} value
|
|
76
|
+
* JavaScript identifier or renderer name.
|
|
77
|
+
* @returns {string}
|
|
78
|
+
* Python identifier.
|
|
79
|
+
*/
|
|
80
|
+
function toSnakeCase(value) {
|
|
81
|
+
return value
|
|
82
|
+
.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
83
|
+
.replaceAll("-", "_")
|
|
84
|
+
.toLowerCase();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Return whether an adapter caller must pass the parameter, either because the
|
|
89
|
+
* renderer needs it or because the existing Python signature always required it.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} parameter
|
|
92
|
+
* Catalogue parameter metadata.
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
* Whether the parameter is required by the adapter contract.
|
|
95
|
+
*/
|
|
96
|
+
function isRequiredParameter(parameter) {
|
|
97
|
+
return parameter.adapterRequired || parameter.required;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Return the annotation for one catalogue parameter in the target language.
|
|
102
|
+
*
|
|
103
|
+
* @param {object} parameter
|
|
104
|
+
* Catalogue parameter metadata.
|
|
105
|
+
* @param {object} descriptor
|
|
106
|
+
* Target language syntax and type mappings.
|
|
107
|
+
* @returns {string}
|
|
108
|
+
* Language-specific type annotation.
|
|
109
|
+
*/
|
|
110
|
+
function getType(parameter, descriptor) {
|
|
111
|
+
const type = descriptor.types[parameter.type];
|
|
112
|
+
|
|
113
|
+
if (type === undefined) {
|
|
114
|
+
throw new Error(`Unsupported catalogue type: ${parameter.type}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (parameter.singular) {
|
|
118
|
+
const singularType = descriptor.types[parameter.type.replace("[]", "")];
|
|
119
|
+
|
|
120
|
+
if (singularType === undefined) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Unsupported scalar type for singular parameter "${parameter.name}": ${parameter.type}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return singularType;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
!isRequiredParameter(parameter) &&
|
|
131
|
+
(parameter.default === null || Array.isArray(parameter.default))
|
|
132
|
+
) {
|
|
133
|
+
return descriptor.nullableType(type);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return type;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Return the default for one optional parameter in the target language.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} parameter
|
|
143
|
+
* Catalogue parameter metadata.
|
|
144
|
+
* @param {object} descriptor
|
|
145
|
+
* Target language syntax and type mappings.
|
|
146
|
+
* @returns {string}
|
|
147
|
+
* Language-specific default expression.
|
|
148
|
+
*/
|
|
149
|
+
function getDefault(parameter, descriptor) {
|
|
150
|
+
if (parameter.singular) {
|
|
151
|
+
return '""';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (parameter.default === null || Array.isArray(parameter.default)) {
|
|
155
|
+
return descriptor.nullLiteral;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof parameter.default === "boolean") {
|
|
159
|
+
return descriptor.booleanLiterals[parameter.default];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return JSON.stringify(parameter.default);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Return the parameter declaration in the target language.
|
|
167
|
+
*
|
|
168
|
+
* @param {object} parameter
|
|
169
|
+
* Catalogue parameter metadata.
|
|
170
|
+
* @param {object} descriptor
|
|
171
|
+
* Target language syntax and type mappings.
|
|
172
|
+
* @returns {string}
|
|
173
|
+
* Language-specific parameter declaration.
|
|
174
|
+
*/
|
|
175
|
+
function renderParameter(parameter, descriptor) {
|
|
176
|
+
const name = descriptor.parameterName(parameter);
|
|
177
|
+
const declaration = `${name}: ${getType(parameter, descriptor)}`;
|
|
178
|
+
|
|
179
|
+
if (isRequiredParameter(parameter)) {
|
|
180
|
+
return declaration;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return `${declaration} = ${getDefault(parameter, descriptor)}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Return the expression that maps a parameter into renderer data.
|
|
188
|
+
*
|
|
189
|
+
* @param {object} parameter
|
|
190
|
+
* Catalogue parameter metadata.
|
|
191
|
+
* @param {object} descriptor
|
|
192
|
+
* Target language syntax and type mappings.
|
|
193
|
+
* @returns {string}
|
|
194
|
+
* Language-specific expression for the renderer payload.
|
|
195
|
+
*/
|
|
196
|
+
function renderPayloadValue(parameter, descriptor) {
|
|
197
|
+
const name = descriptor.parameterName(parameter);
|
|
198
|
+
|
|
199
|
+
if (isRequiredParameter(parameter)) {
|
|
200
|
+
return name;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (parameter.singular) {
|
|
204
|
+
return descriptor.singularPayload(name);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (Array.isArray(parameter.default)) {
|
|
208
|
+
return descriptor.defaultArrayPayload(name);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return name;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Render one generated Python wrapper from catalogue metadata.
|
|
216
|
+
*
|
|
217
|
+
* @param {object} renderer
|
|
218
|
+
* Renderer catalogue entry.
|
|
219
|
+
* @returns {string}
|
|
220
|
+
* Python wrapper source.
|
|
221
|
+
*/
|
|
222
|
+
function renderWrapper(renderer) {
|
|
223
|
+
const descriptor = languageDescriptors.python;
|
|
224
|
+
const parameters = renderer.params.filter((parameter) => !parameter.adapterOmit);
|
|
225
|
+
|
|
226
|
+
const parameterLines = parameters.map(
|
|
227
|
+
(parameter) => `\t${renderParameter(parameter, descriptor)},`,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const payloadParameters = parameters.filter(
|
|
231
|
+
(parameter) => isRequiredParameter(parameter) || parameter.default !== null,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const nullableParameters = parameters.filter(
|
|
235
|
+
(parameter) => !isRequiredParameter(parameter) && parameter.default === null,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const payloadLines = payloadParameters.map(
|
|
239
|
+
(parameter) =>
|
|
240
|
+
`\t\t${JSON.stringify(parameter.name)}: ${renderPayloadValue(parameter, descriptor)},`,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const nullablePayloadLines = nullableParameters.flatMap((parameter) => {
|
|
244
|
+
const name = descriptor.parameterName(parameter);
|
|
245
|
+
|
|
246
|
+
return [`\tif ${name} is not None:`, `\t\tdata[${JSON.stringify(parameter.name)}] = ${name}`];
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return [
|
|
250
|
+
`def ${toSnakeCase(renderer.api)}(`,
|
|
251
|
+
...parameterLines,
|
|
252
|
+
"\t**kwargs: Any,",
|
|
253
|
+
") -> str:",
|
|
254
|
+
`\t"""Return ${renderer.name} output; extra keyword arguments go to render()."""`,
|
|
255
|
+
"\tdata = {",
|
|
256
|
+
...payloadLines,
|
|
257
|
+
"\t}",
|
|
258
|
+
...(nullablePayloadLines.length > 0 ? ["", ...nullablePayloadLines] : []),
|
|
259
|
+
"",
|
|
260
|
+
`\treturn render(${JSON.stringify(renderer.name)}, data, **kwargs)`,
|
|
261
|
+
].join("\n");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Render the complete generated Python wrapper module.
|
|
266
|
+
*
|
|
267
|
+
* @returns {string}
|
|
268
|
+
* Generated Python module source.
|
|
269
|
+
*/
|
|
270
|
+
function renderWrappers() {
|
|
271
|
+
const wrappers = rendererCatalogue.map(renderWrapper).join("\n\n\n");
|
|
272
|
+
|
|
273
|
+
return [
|
|
274
|
+
"# Generated by scripts/generate-adapters.js. Do not edit directly.",
|
|
275
|
+
"",
|
|
276
|
+
"from __future__ import annotations",
|
|
277
|
+
"",
|
|
278
|
+
"from typing import Any",
|
|
279
|
+
"",
|
|
280
|
+
"from .core import render",
|
|
281
|
+
"",
|
|
282
|
+
wrappers,
|
|
283
|
+
"",
|
|
284
|
+
].join("\n");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Render one generated Swift wrapper from catalogue metadata.
|
|
289
|
+
*
|
|
290
|
+
* @param {object} renderer
|
|
291
|
+
* Renderer catalogue entry.
|
|
292
|
+
* @returns {string}
|
|
293
|
+
* Swift wrapper source.
|
|
294
|
+
*/
|
|
295
|
+
function renderSwiftWrapper(renderer) {
|
|
296
|
+
const descriptor = languageDescriptors.swift;
|
|
297
|
+
const parameters = renderer.params.filter((parameter) => !parameter.adapterOmit);
|
|
298
|
+
|
|
299
|
+
const parameterLines = parameters.map(
|
|
300
|
+
(parameter) => `\t\t${renderParameter(parameter, descriptor)},`,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const payloadParameters = parameters.filter(
|
|
304
|
+
(parameter) => isRequiredParameter(parameter) || parameter.default !== null,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const nullableParameters = parameters.filter(
|
|
308
|
+
(parameter) => !isRequiredParameter(parameter) && parameter.default === null,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const payloadLines = payloadParameters.map(
|
|
312
|
+
(parameter) =>
|
|
313
|
+
`\t\t\t${JSON.stringify(parameter.name)}: ${renderPayloadValue(parameter, descriptor)},`,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const nullablePayloadLines = nullableParameters.flatMap((parameter) => {
|
|
317
|
+
const name = descriptor.parameterName(parameter);
|
|
318
|
+
|
|
319
|
+
return [
|
|
320
|
+
`\t\tif let ${name} = ${name} {`,
|
|
321
|
+
`\t\t\tdata[${JSON.stringify(parameter.name)}] = ${name}`,
|
|
322
|
+
"\t\t}",
|
|
323
|
+
];
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const dataDeclaration = nullablePayloadLines.length > 0 ? "var" : "let";
|
|
327
|
+
|
|
328
|
+
return [
|
|
329
|
+
"\t/**",
|
|
330
|
+
`\t * Return ${renderer.name} output from the cli-style binary; \`options\` sets the profile, width, and binary.`,
|
|
331
|
+
"\t */",
|
|
332
|
+
`\tpublic static func ${renderer.api}(`,
|
|
333
|
+
...parameterLines,
|
|
334
|
+
"\t\toptions: CliStyleOptions = .init()",
|
|
335
|
+
"\t) throws -> String {",
|
|
336
|
+
`\t\t${dataDeclaration} data: [String: Any] = [`,
|
|
337
|
+
...payloadLines,
|
|
338
|
+
"\t\t]",
|
|
339
|
+
...(nullablePayloadLines.length > 0 ? ["", ...nullablePayloadLines] : []),
|
|
340
|
+
"",
|
|
341
|
+
`\t\treturn try render(${JSON.stringify(renderer.name)}, data: data, options: options)`,
|
|
342
|
+
"\t}",
|
|
343
|
+
].join("\n");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Render the complete generated Swift wrapper module.
|
|
348
|
+
*
|
|
349
|
+
* @returns {string}
|
|
350
|
+
* Generated Swift module source.
|
|
351
|
+
*/
|
|
352
|
+
function renderSwiftWrappers() {
|
|
353
|
+
const wrappers = rendererCatalogue.map(renderSwiftWrapper).join("\n\n");
|
|
354
|
+
|
|
355
|
+
return [
|
|
356
|
+
"// Generated by scripts/generate-adapters.js. Do not edit directly.",
|
|
357
|
+
"",
|
|
358
|
+
"import Foundation",
|
|
359
|
+
"",
|
|
360
|
+
"extension CliStyle {",
|
|
361
|
+
"",
|
|
362
|
+
wrappers,
|
|
363
|
+
"",
|
|
364
|
+
"}",
|
|
365
|
+
"",
|
|
366
|
+
].join("\n");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Build the whole module before writing so a catalogue error leaves the old file intact.
|
|
370
|
+
const generatedPythonSource = renderWrappers();
|
|
371
|
+
const generatedSwiftSource = renderSwiftWrappers();
|
|
372
|
+
|
|
373
|
+
// Each generated wrapper paired with the committed file it is written to, or checked against.
|
|
374
|
+
const generatedFiles = [
|
|
375
|
+
{
|
|
376
|
+
path: pythonOutputPath,
|
|
377
|
+
source: generatedPythonSource,
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
path: swiftOutputPath,
|
|
381
|
+
source: generatedSwiftSource,
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Read a licence file, treating an absent file as a reportable state rather than a crash.
|
|
387
|
+
*
|
|
388
|
+
* @param {string} path
|
|
389
|
+
* The licence file to read.
|
|
390
|
+
* @returns {Buffer|null}
|
|
391
|
+
* The file contents, or null when the file is not there.
|
|
392
|
+
*/
|
|
393
|
+
function readLicenseFile(path) {
|
|
394
|
+
try {
|
|
395
|
+
return readFileSync(path);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (error.code === "ENOENT") {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// `--check` reports committed wrappers that differ from the generated source and writes nothing.
|
|
406
|
+
const checkOnly = process.argv.includes("--check");
|
|
407
|
+
|
|
408
|
+
if (checkOnly) {
|
|
409
|
+
// Both packages are released from one tag, so a version edited in one file and
|
|
410
|
+
// forgotten in the other would publish a Python package claiming a version that
|
|
411
|
+
// was never released.
|
|
412
|
+
const packageManifest = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
413
|
+
const packageVersion = packageManifest.version;
|
|
414
|
+
|
|
415
|
+
// The version is the only field read out of pyproject.toml, and the file is ours, so
|
|
416
|
+
// a pattern match avoids taking on a TOML parser for one line.
|
|
417
|
+
const pythonVersionMatch = readFileSync(pythonProjectPath, "utf8").match(
|
|
418
|
+
/^version\s*=\s*"([^"]+)"/m,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const pythonVersion = pythonVersionMatch?.[1];
|
|
422
|
+
const versionsMatch = packageVersion === pythonVersion;
|
|
423
|
+
|
|
424
|
+
// The Python package carries its own copy of the licence, because a wheel built from
|
|
425
|
+
// the published source archive cannot reach the one at the repository root.
|
|
426
|
+
const repositoryLicense = readLicenseFile(repositoryLicensePath);
|
|
427
|
+
const pythonLicense = readLicenseFile(pythonLicensePath);
|
|
428
|
+
|
|
429
|
+
const missingLicensePaths = [
|
|
430
|
+
["LICENSE", repositoryLicense],
|
|
431
|
+
["adapters/python/LICENSE", pythonLicense],
|
|
432
|
+
]
|
|
433
|
+
.filter(([, contents]) => contents === null)
|
|
434
|
+
.map(([path]) => path);
|
|
435
|
+
|
|
436
|
+
const licensesMatch = missingLicensePaths.length === 0 && repositoryLicense.equals(pythonLicense);
|
|
437
|
+
|
|
438
|
+
// A committed wrapper is stale when its contents differ from the generated source; a missing file counts as stale.
|
|
439
|
+
const staleFiles = generatedFiles.filter(({ path, source }) => {
|
|
440
|
+
try {
|
|
441
|
+
return readFileSync(path, "utf8") !== source;
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error.code === "ENOENT") {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (!versionsMatch) {
|
|
452
|
+
console.error("Package versions do not match:");
|
|
453
|
+
console.error(`- package.json: ${packageVersion ?? "missing"}`);
|
|
454
|
+
console.error(`- adapters/python/pyproject.toml: ${pythonVersion ?? "missing"}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (missingLicensePaths.length > 0) {
|
|
458
|
+
console.error("Licence files are missing:");
|
|
459
|
+
|
|
460
|
+
for (const path of missingLicensePaths) {
|
|
461
|
+
console.error(`- ${path}`);
|
|
462
|
+
}
|
|
463
|
+
} else if (!licensesMatch) {
|
|
464
|
+
console.error("Licence files do not match:");
|
|
465
|
+
console.error("- LICENSE");
|
|
466
|
+
console.error("- adapters/python/LICENSE");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (staleFiles.length > 0) {
|
|
470
|
+
console.error("Generated adapters are stale:");
|
|
471
|
+
|
|
472
|
+
for (const { path } of staleFiles) {
|
|
473
|
+
console.error(`- ${path}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (staleFiles.length > 0 || !versionsMatch || !licensesMatch) {
|
|
478
|
+
process.exitCode = 1;
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
for (const { path, source } of generatedFiles) {
|
|
482
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
483
|
+
writeFileSync(path, source);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
# Archive layout:
|
|
5
5
|
# bin/cli-style
|
|
6
6
|
# adapters/bash/cli-style.sh
|
|
7
|
-
# adapters/python/cli_style
|
|
7
|
+
# adapters/python/cli_style/
|
|
8
|
+
# adapters/python/pyproject.toml
|
|
9
|
+
# adapters/python/README.md
|
|
10
|
+
# adapters/python/LICENSE
|
|
11
|
+
# adapters/swift/Package.swift
|
|
12
|
+
# adapters/swift/Sources/
|
|
8
13
|
|
|
9
14
|
set -euo pipefail
|
|
10
15
|
|