@abaplint/transpiler-cli 2.14.1 → 2.14.2
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 +22 -6
- package/build/bundle.js +387 -37
- package/build/types.d.ts +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ basename of `folder` for a local-only library. Set `libs[].name` to override it:
|
|
|
22
22
|
{
|
|
23
23
|
"input_folder": ["src", "extra"],
|
|
24
24
|
"output_folder": "output",
|
|
25
|
+
"incremental_output": true,
|
|
25
26
|
"libs": [
|
|
26
27
|
{"url": "https://github.com/open-abap/open-abap-core"},
|
|
27
28
|
{"folder": "./deps/utilities", "name": "utilities"}
|
|
@@ -55,15 +56,28 @@ are rejected by default; a project object replaces a library object of the same
|
|
|
55
56
|
|
|
56
57
|
Class locals, test classes, function groups, MIME data, and
|
|
57
58
|
source maps follow their owning object. Source maps include ABAP source content,
|
|
58
|
-
including libraries loaded from temporary Git checkouts
|
|
59
|
-
|
|
59
|
+
including libraries loaded from temporary Git checkouts; they use stable
|
|
60
|
+
library-qualified paths and do not expose the temporary checkout location.
|
|
61
|
+
Imports and registered MIME filenames reference the new paths.
|
|
60
62
|
|
|
61
63
|
Shared initialization scripts and test runners remain at the output root.
|
|
62
64
|
Run programs with `node output/project/zapp.prog.mjs`; run project unit tests
|
|
63
65
|
with `node output/index.mjs`. When migrating from flat output, rebuild a clean
|
|
64
66
|
generated output directory and update scripts that reference individual
|
|
65
|
-
modules or assets.
|
|
66
|
-
|
|
67
|
+
modules or assets.
|
|
68
|
+
|
|
69
|
+
Set `incremental_output` to `true` to skip writing unchanged files, replace
|
|
70
|
+
changed generated files, and remove obsolete files recorded in
|
|
71
|
+
`output/.abap-transpile-manifest.json`. The CLI preserves files it did not
|
|
72
|
+
generate, including custom setup scripts and other user data. Existing output
|
|
73
|
+
from runs before this option was enabled has no ownership inventory; do one
|
|
74
|
+
clean build when adopting the option to remove legacy stale files. Set the
|
|
75
|
+
option to `false` (the default) to restore unconditional writes. Files
|
|
76
|
+
generated while it is disabled are not added to the inventory.
|
|
77
|
+
|
|
78
|
+
The manifest is updated atomically and retained as a recovery aid if a run
|
|
79
|
+
fails. This does not make the entire output tree transactional. Use one
|
|
80
|
+
transpiler process per output directory.
|
|
67
81
|
|
|
68
82
|
The transpiler library retains flat output unless its caller supplies an output
|
|
69
83
|
folder map as the third argument to `Transpiler.run()`. This layout is enabled
|
|
@@ -93,5 +107,7 @@ DDIC types, available methods, and runtime behavior, or cause syntax errors in
|
|
|
93
107
|
code expecting the skipped version. This option does not check that copies are
|
|
94
108
|
equivalent or suppress syntax checking. Put the intended provider first and
|
|
95
109
|
run your application's tests. For selective control, use `libs[].exclude_filter`
|
|
96
|
-
to exclude only the unwanted object's files instead.
|
|
97
|
-
|
|
110
|
+
to exclude only the unwanted object's files instead. With `incremental_output`
|
|
111
|
+
enabled, changing providers removes obsolete files tracked in the manifest.
|
|
112
|
+
Use a clean generated output directory when changing providers in legacy mode
|
|
113
|
+
or when untracked stale output remains from earlier builds.
|
package/build/bundle.js
CHANGED
|
@@ -71,6 +71,7 @@ class TranspilerConfig {
|
|
|
71
71
|
input_folder: "src",
|
|
72
72
|
input_filter: [],
|
|
73
73
|
output_folder: "output",
|
|
74
|
+
incremental_output: false,
|
|
74
75
|
libs: [
|
|
75
76
|
{ "url": "https://github.com/open-abap/open-abap-core" },
|
|
76
77
|
],
|
|
@@ -314,14 +315,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
314
315
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
315
316
|
};
|
|
316
317
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
317
|
-
const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
|
|
318
|
-
const path = __importStar(__webpack_require__(/*! path */ "path"));
|
|
319
318
|
const progress_1 = __importDefault(__webpack_require__(/*! progress */ "./node_modules/progress/index.js"));
|
|
320
319
|
const Transpiler = __importStar(__webpack_require__(/*! @abaplint/transpiler */ "./node_modules/@abaplint/transpiler/build/src/index.js"));
|
|
321
320
|
const config_1 = __webpack_require__(/*! ./config */ "./build/config.js");
|
|
322
321
|
const file_operations_1 = __webpack_require__(/*! ./file_operations */ "./build/file_operations.js");
|
|
323
322
|
const libraries_1 = __webpack_require__(/*! ./libraries */ "./build/libraries.js");
|
|
324
323
|
const write_objects_1 = __webpack_require__(/*! ./write_objects */ "./build/write_objects.js");
|
|
324
|
+
const output_writer_1 = __webpack_require__(/*! ./output_writer */ "./build/output_writer.js");
|
|
325
325
|
class Progress {
|
|
326
326
|
bar;
|
|
327
327
|
set(total, _text) {
|
|
@@ -351,23 +351,23 @@ async function run() {
|
|
|
351
351
|
console.log("\nBuilding");
|
|
352
352
|
const { output, sources } = await build(config, files);
|
|
353
353
|
console.log("\nOutput");
|
|
354
|
-
const
|
|
355
|
-
if (!fs.existsSync(outputFolder)) {
|
|
356
|
-
fs.mkdirSync(outputFolder, { recursive: true });
|
|
357
|
-
}
|
|
358
|
-
await (0, write_objects_1.writeObjects)(output.objects, config, sources);
|
|
359
|
-
console.log(output.objects.length + " objects written to disk");
|
|
354
|
+
const artifacts = (0, write_objects_1.collectObjectFiles)(output.objects, config, sources);
|
|
360
355
|
if (config.write_unit_tests === true) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
356
|
+
artifacts.push({ path: "index.mjs", contents: output.unitTestScript });
|
|
357
|
+
artifacts.push({ path: "_unit_open.mjs", contents: output.unitTestScriptOpen });
|
|
358
|
+
}
|
|
359
|
+
artifacts.push({ path: "init.mjs", contents: output.initializationScript });
|
|
360
|
+
artifacts.push({ path: "_init.mjs", contents: output.initializationScript2 });
|
|
361
|
+
artifacts.push({ path: "_top.mjs", contents: `import runtime from "@abaplint/runtime";
|
|
362
|
+
globalThis.abap = new runtime.ABAP();` });
|
|
363
|
+
const written = await (0, output_writer_1.writeOutput)(config.output_folder, artifacts, config.incremental_output === true);
|
|
364
|
+
if (config.incremental_output === true) {
|
|
365
|
+
console.log("Output files: " + written.created + " created, " +
|
|
366
|
+
written.updated + " updated, " + written.unchanged + " unchanged, " + written.deleted + " deleted");
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
console.log(`Output files: ${written.updated} written`);
|
|
364
370
|
}
|
|
365
|
-
// breaking change? rename this output file,
|
|
366
|
-
fs.writeFileSync(outputFolder + path.sep + "init.mjs", output.initializationScript);
|
|
367
|
-
// new static referenced imports,
|
|
368
|
-
fs.writeFileSync(outputFolder + path.sep + "_init.mjs", output.initializationScript2);
|
|
369
|
-
fs.writeFileSync(outputFolder + path.sep + "_top.mjs", `import runtime from "@abaplint/runtime";
|
|
370
|
-
globalThis.abap = new runtime.ABAP();`);
|
|
371
371
|
}
|
|
372
372
|
run().then(() => {
|
|
373
373
|
process.exit();
|
|
@@ -491,6 +491,11 @@ const abaplint = __importStar(__webpack_require__(/*! @abaplint/core */ "./node_
|
|
|
491
491
|
const file_operations_1 = __webpack_require__(/*! ./file_operations */ "./build/file_operations.js");
|
|
492
492
|
const git_clone_1 = __webpack_require__(/*! ./git_clone */ "./build/git_clone.js");
|
|
493
493
|
const lib_folder_1 = __webpack_require__(/*! ./lib_folder */ "./build/lib_folder.js");
|
|
494
|
+
function logicalLibrarySource(library, directory, filename) {
|
|
495
|
+
const relative = path.relative(directory, filename).split(path.sep)
|
|
496
|
+
.map(segment => encodeURIComponent(segment)).join("/");
|
|
497
|
+
return "abaplint://" + encodeURIComponent(library) + "/" + relative;
|
|
498
|
+
}
|
|
494
499
|
function libraryNames(libs) {
|
|
495
500
|
const used = new Set(["project"]);
|
|
496
501
|
return libs.map(lib => {
|
|
@@ -514,6 +519,7 @@ async function loadLibraries(config) {
|
|
|
514
519
|
const names = libraryNames(libs);
|
|
515
520
|
const result = [];
|
|
516
521
|
for (const [index, lib] of libs.entries()) {
|
|
522
|
+
const libraryName = names[index];
|
|
517
523
|
let dir;
|
|
518
524
|
let cleanupFolder = false;
|
|
519
525
|
const folder = (0, lib_folder_1.resolveLibFolder)(lib.folder, process.cwd());
|
|
@@ -547,8 +553,15 @@ async function loadLibraries(config) {
|
|
|
547
553
|
filesToRead.add(filename);
|
|
548
554
|
}
|
|
549
555
|
}
|
|
550
|
-
const
|
|
551
|
-
|
|
556
|
+
const filenames = [...filesToRead];
|
|
557
|
+
const files = await file_operations_1.FileOperations.readAllFiles(filenames, config.output_folder);
|
|
558
|
+
if (cleanupFolder) {
|
|
559
|
+
for (const [index, file] of files.entries()) {
|
|
560
|
+
file.sourceMapPath = logicalLibrarySource(libraryName, dir, filenames[index]);
|
|
561
|
+
delete file.relative;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
result.push({ name: libraryName, files });
|
|
552
565
|
console.log("\t" + files.length + " files added from lib");
|
|
553
566
|
}
|
|
554
567
|
finally {
|
|
@@ -604,6 +617,347 @@ function libraryRegistry(files, libraries, skipDuplicateDependencies = false) {
|
|
|
604
617
|
|
|
605
618
|
/***/ },
|
|
606
619
|
|
|
620
|
+
/***/ "./build/output_writer.js"
|
|
621
|
+
/*!********************************!*\
|
|
622
|
+
!*** ./build/output_writer.js ***!
|
|
623
|
+
\********************************/
|
|
624
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
625
|
+
|
|
626
|
+
"use strict";
|
|
627
|
+
|
|
628
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
629
|
+
if (k2 === undefined) k2 = k;
|
|
630
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
631
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
632
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
633
|
+
}
|
|
634
|
+
Object.defineProperty(o, k2, desc);
|
|
635
|
+
}) : (function(o, m, k, k2) {
|
|
636
|
+
if (k2 === undefined) k2 = k;
|
|
637
|
+
o[k2] = m[k];
|
|
638
|
+
}));
|
|
639
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
640
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
641
|
+
}) : function(o, v) {
|
|
642
|
+
o["default"] = v;
|
|
643
|
+
});
|
|
644
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
645
|
+
var ownKeys = function(o) {
|
|
646
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
647
|
+
var ar = [];
|
|
648
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
649
|
+
return ar;
|
|
650
|
+
};
|
|
651
|
+
return ownKeys(o);
|
|
652
|
+
};
|
|
653
|
+
return function (mod) {
|
|
654
|
+
if (mod && mod.__esModule) return mod;
|
|
655
|
+
var result = {};
|
|
656
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
657
|
+
__setModuleDefault(result, mod);
|
|
658
|
+
return result;
|
|
659
|
+
};
|
|
660
|
+
})();
|
|
661
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
662
|
+
exports.writeOutput = writeOutput;
|
|
663
|
+
const fsPromises = __importStar(__webpack_require__(/*! node:fs/promises */ "node:fs/promises"));
|
|
664
|
+
const os = __importStar(__webpack_require__(/*! node:os */ "node:os"));
|
|
665
|
+
const path = __importStar(__webpack_require__(/*! node:path */ "node:path"));
|
|
666
|
+
const file_operations_1 = __webpack_require__(/*! ./file_operations */ "./build/file_operations.js");
|
|
667
|
+
const MANIFEST = ".abap-transpile-manifest.json";
|
|
668
|
+
function concurrency() {
|
|
669
|
+
return Math.max(1, Math.min(8, os.cpus().length));
|
|
670
|
+
}
|
|
671
|
+
async function forEachLimit(items, task, limit = concurrency()) {
|
|
672
|
+
let next = 0;
|
|
673
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
674
|
+
while (next < items.length) {
|
|
675
|
+
const index = next++;
|
|
676
|
+
await task(items[index]);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
const results = await Promise.allSettled(workers);
|
|
680
|
+
const failed = results.find(result => result.status === "rejected");
|
|
681
|
+
if (failed?.status === "rejected") {
|
|
682
|
+
throw failed.reason;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function normalizeRelative(filename, label) {
|
|
686
|
+
if (filename.length === 0 || filename.startsWith("/") || filename.includes("\\")
|
|
687
|
+
|| /^[a-zA-Z]:/.test(filename)) {
|
|
688
|
+
throw new Error("Invalid " + label + " path: " + filename);
|
|
689
|
+
}
|
|
690
|
+
const segments = filename.split("/");
|
|
691
|
+
if (segments.some(segment => segment.length === 0 || segment === "." || segment === "..")) {
|
|
692
|
+
throw new Error("Invalid " + label + " path: " + filename);
|
|
693
|
+
}
|
|
694
|
+
return segments.join("/");
|
|
695
|
+
}
|
|
696
|
+
function validatePaths(filenames, label) {
|
|
697
|
+
const paths = new Map();
|
|
698
|
+
for (const filename of filenames) {
|
|
699
|
+
if (typeof filename !== "string") {
|
|
700
|
+
throw new Error("Invalid " + label + " path");
|
|
701
|
+
}
|
|
702
|
+
const relative = normalizeRelative(filename, label);
|
|
703
|
+
const key = relative.toLowerCase();
|
|
704
|
+
if (key === MANIFEST || key.startsWith(MANIFEST + "/")) {
|
|
705
|
+
throw new Error("Reserved " + label + " path: " + relative);
|
|
706
|
+
}
|
|
707
|
+
const existing = paths.get(key);
|
|
708
|
+
if (existing !== undefined) {
|
|
709
|
+
throw new Error("Conflicting " + label + " paths: " + existing + " and " + relative);
|
|
710
|
+
}
|
|
711
|
+
paths.set(key, relative);
|
|
712
|
+
}
|
|
713
|
+
for (const filename of paths.values()) {
|
|
714
|
+
const segments = filename.split("/");
|
|
715
|
+
for (let index = 1; index < segments.length; index++) {
|
|
716
|
+
if (paths.has(segments.slice(0, index).join("/").toLowerCase())) {
|
|
717
|
+
throw new Error("Conflicting " + label + " file and directory paths: " + filename);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return [...paths.values()];
|
|
722
|
+
}
|
|
723
|
+
function validateArtifactPaths(artifacts) {
|
|
724
|
+
const paths = validatePaths(artifacts.map(artifact => artifact.path.replace(/\\/g, "/")), "output");
|
|
725
|
+
return artifacts.map((artifact, index) => ({
|
|
726
|
+
...artifact,
|
|
727
|
+
relative: paths[index],
|
|
728
|
+
byteLength: Buffer.byteLength(artifact.contents, file_operations_1.FileOperations.isBinaryFilename(paths[index]) ? "latin1" : "utf8"),
|
|
729
|
+
binary: file_operations_1.FileOperations.isBinaryFilename(paths[index]),
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
function parseManifest(contents) {
|
|
733
|
+
let parsed;
|
|
734
|
+
try {
|
|
735
|
+
parsed = JSON.parse(contents);
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
throw new Error("Invalid incremental output manifest JSON: " + String(error));
|
|
739
|
+
}
|
|
740
|
+
if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || !("files" in parsed)
|
|
741
|
+
|| parsed.version !== 1 || !Array.isArray(parsed.files)) {
|
|
742
|
+
throw new Error("Unsupported incremental output manifest");
|
|
743
|
+
}
|
|
744
|
+
return { version: 1, files: validatePaths(parsed.files, "incremental output manifest") };
|
|
745
|
+
}
|
|
746
|
+
function serializeManifest(files) {
|
|
747
|
+
return JSON.stringify({ version: 1, files: [...files].sort() }, null, 2) + "\n";
|
|
748
|
+
}
|
|
749
|
+
async function lstatOrMissing(filename) {
|
|
750
|
+
try {
|
|
751
|
+
return await fsPromises.lstat(filename);
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
if (error.code === "ENOENT") {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
throw error;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function isBelowRoot(root, filename) {
|
|
761
|
+
const relative = path.relative(root, filename);
|
|
762
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative);
|
|
763
|
+
}
|
|
764
|
+
async function inspectPaths(root, relativePaths, rootStats, ioConcurrency) {
|
|
765
|
+
const metadata = new Map();
|
|
766
|
+
const nodes = new Map();
|
|
767
|
+
for (const filename of relativePaths) {
|
|
768
|
+
const segments = filename.split("/");
|
|
769
|
+
for (let index = 1; index <= segments.length; index++) {
|
|
770
|
+
const relative = segments.slice(0, index).join("/");
|
|
771
|
+
const isDirectory = index < segments.length;
|
|
772
|
+
nodes.set(relative, (nodes.get(relative) ?? false) || isDirectory);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (rootStats !== undefined) {
|
|
776
|
+
const byDepth = new Map();
|
|
777
|
+
for (const relative of nodes.keys()) {
|
|
778
|
+
const depth = relative.split("/").length;
|
|
779
|
+
const group = byDepth.get(depth) ?? [];
|
|
780
|
+
group.push(relative);
|
|
781
|
+
byDepth.set(depth, group);
|
|
782
|
+
}
|
|
783
|
+
for (const depth of [...byDepth.keys()].sort((a, b) => a - b)) {
|
|
784
|
+
const candidates = byDepth.get(depth).filter(relative => {
|
|
785
|
+
const parent = path.posix.dirname(relative);
|
|
786
|
+
return depth === 1 || metadata.get(parent)?.isDirectory() === true;
|
|
787
|
+
});
|
|
788
|
+
await forEachLimit(candidates, async (relative) => {
|
|
789
|
+
const absolute = path.resolve(root, ...relative.split("/"));
|
|
790
|
+
const stat = await lstatOrMissing(absolute);
|
|
791
|
+
if (stat?.isSymbolicLink()) {
|
|
792
|
+
throw new Error("Output path must not traverse a symlink: " + relative);
|
|
793
|
+
}
|
|
794
|
+
if (stat !== undefined && nodes.get(relative) === true && !stat.isDirectory()) {
|
|
795
|
+
throw new Error("Output path parent is not a directory: " + relative);
|
|
796
|
+
}
|
|
797
|
+
if (stat !== undefined && nodes.get(relative) === false && !stat.isFile()) {
|
|
798
|
+
throw new Error("Generated output path is not a regular file: " + relative);
|
|
799
|
+
}
|
|
800
|
+
metadata.set(relative, stat);
|
|
801
|
+
}, ioConcurrency);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
for (const relative of nodes.keys()) {
|
|
806
|
+
metadata.set(relative, undefined);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return metadata;
|
|
810
|
+
}
|
|
811
|
+
async function atomicWriteManifest(root, contents, current) {
|
|
812
|
+
if (current === contents) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
await fsPromises.mkdir(root, { recursive: true });
|
|
816
|
+
const temporary = path.join(root, MANIFEST + ".tmp-" + process.pid + "-" + Math.random().toString(36).slice(2));
|
|
817
|
+
try {
|
|
818
|
+
await fsPromises.writeFile(temporary, contents, { flag: "wx", encoding: "utf8" });
|
|
819
|
+
await fsPromises.rename(temporary, path.join(root, MANIFEST));
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
try {
|
|
823
|
+
await fsPromises.unlink(temporary);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
// The temporary file can be absent when creation failed.
|
|
827
|
+
}
|
|
828
|
+
throw error;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
async function writeOutput(rootFolder, artifacts, incremental, ioConcurrency = concurrency()) {
|
|
832
|
+
const root = path.resolve(rootFolder);
|
|
833
|
+
const prepared = validateArtifactPaths(artifacts);
|
|
834
|
+
const result = {
|
|
835
|
+
created: 0, updated: 0, unchanged: 0, deleted: 0,
|
|
836
|
+
};
|
|
837
|
+
if (!incremental) {
|
|
838
|
+
const files = prepared.map(file => ({ path: path.resolve(root, file.relative), contents: file.contents }));
|
|
839
|
+
await file_operations_1.FileOperations.writeFiles(files);
|
|
840
|
+
result.updated = files.length;
|
|
841
|
+
return result;
|
|
842
|
+
}
|
|
843
|
+
const manifestPath = path.join(root, MANIFEST);
|
|
844
|
+
const rootStats = await lstatOrMissing(root);
|
|
845
|
+
if (rootStats?.isSymbolicLink()) {
|
|
846
|
+
throw new Error("Output folder must not be a symlink: " + root);
|
|
847
|
+
}
|
|
848
|
+
if (rootStats !== undefined && !rootStats.isDirectory()) {
|
|
849
|
+
throw new Error("Output folder is not a directory: " + root);
|
|
850
|
+
}
|
|
851
|
+
let manifestContents;
|
|
852
|
+
let previous = [];
|
|
853
|
+
if (rootStats !== undefined) {
|
|
854
|
+
const manifestStat = await lstatOrMissing(manifestPath);
|
|
855
|
+
if (manifestStat?.isSymbolicLink() || (manifestStat !== undefined && !manifestStat.isFile())) {
|
|
856
|
+
throw new Error("Incremental output manifest must be a regular file");
|
|
857
|
+
}
|
|
858
|
+
if (manifestStat !== undefined) {
|
|
859
|
+
const contents = await fsPromises.readFile(manifestPath, "utf8");
|
|
860
|
+
manifestContents = contents;
|
|
861
|
+
previous = parseManifest(contents).files;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
// A path can belong to both inventories; each inventory rejects its own duplicates.
|
|
865
|
+
validatePaths([...new Set([...prepared.map(file => file.relative), ...previous])], "incremental output");
|
|
866
|
+
const previousPaths = new Set(previous);
|
|
867
|
+
const current = new Set(prepared.map(file => file.relative));
|
|
868
|
+
const stale = previous.filter(filename => !current.has(filename));
|
|
869
|
+
const additions = prepared.some(file => !previousPaths.has(file.relative));
|
|
870
|
+
const pathsToInspect = [...new Set([...prepared.map(file => file.relative), ...stale])];
|
|
871
|
+
const metadata = await inspectPaths(root, pathsToInspect, rootStats, ioConcurrency);
|
|
872
|
+
await forEachLimit(prepared, async (file) => {
|
|
873
|
+
const stat = metadata.get(file.relative);
|
|
874
|
+
file.existed = stat !== undefined;
|
|
875
|
+
if (stat === undefined || stat.size !== file.byteLength) {
|
|
876
|
+
file.changed = true;
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
let existing;
|
|
880
|
+
try {
|
|
881
|
+
existing = await fsPromises.readFile(path.resolve(root, ...file.relative.split("/")));
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
if (error.code === "ENOENT") {
|
|
885
|
+
file.existed = false;
|
|
886
|
+
file.changed = true;
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
const generated = Buffer.from(file.contents, file.binary ? "latin1" : "utf8");
|
|
892
|
+
file.changed = !existing.equals(generated);
|
|
893
|
+
}, ioConcurrency);
|
|
894
|
+
if (additions) {
|
|
895
|
+
const union = serializeManifest(new Set([...previous, ...current]));
|
|
896
|
+
await atomicWriteManifest(root, union, manifestContents);
|
|
897
|
+
manifestContents = union;
|
|
898
|
+
}
|
|
899
|
+
const toWrite = prepared.filter(file => file.changed);
|
|
900
|
+
const directories = new Map();
|
|
901
|
+
for (const file of toWrite) {
|
|
902
|
+
const segments = file.relative.split("/");
|
|
903
|
+
for (let index = 1; index < segments.length; index++) {
|
|
904
|
+
const relative = segments.slice(0, index).join("/");
|
|
905
|
+
if (metadata.get(relative) === undefined) {
|
|
906
|
+
directories.set(relative, index);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const directoryDepths = [...new Set(directories.values())].sort((a, b) => a - b);
|
|
911
|
+
for (const depth of directoryDepths) {
|
|
912
|
+
const atDepth = [...directories.entries()].filter(([, candidateDepth]) => candidateDepth === depth)
|
|
913
|
+
.map(([relative]) => path.resolve(root, ...relative.split("/")));
|
|
914
|
+
await forEachLimit(atDepth, async (directory) => {
|
|
915
|
+
await fsPromises.mkdir(directory);
|
|
916
|
+
}, ioConcurrency);
|
|
917
|
+
}
|
|
918
|
+
await forEachLimit(toWrite, async (file) => {
|
|
919
|
+
await fsPromises.writeFile(path.resolve(root, ...file.relative.split("/")), file.contents, file.binary ? { encoding: "latin1" } : undefined);
|
|
920
|
+
}, ioConcurrency);
|
|
921
|
+
const staleFiles = stale.map(filename => path.resolve(root, ...filename.split("/")));
|
|
922
|
+
const removedDirectories = new Set();
|
|
923
|
+
for (const filename of staleFiles) {
|
|
924
|
+
try {
|
|
925
|
+
await fsPromises.unlink(filename);
|
|
926
|
+
result.deleted++;
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
if (error.code !== "ENOENT") {
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
let directory = path.dirname(filename);
|
|
934
|
+
while (isBelowRoot(root, directory)) {
|
|
935
|
+
removedDirectories.add(directory);
|
|
936
|
+
directory = path.dirname(directory);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
await forEachLimit([...removedDirectories].sort((a, b) => b.length - a.length), async (directory) => {
|
|
940
|
+
try {
|
|
941
|
+
await fsPromises.rmdir(directory);
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
const code = error.code;
|
|
945
|
+
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") {
|
|
946
|
+
throw error;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
const finalManifest = serializeManifest(current);
|
|
951
|
+
await atomicWriteManifest(root, finalManifest, manifestContents);
|
|
952
|
+
result.created = prepared.filter(file => file.changed && !file.existed).length;
|
|
953
|
+
result.updated = prepared.filter(file => file.changed && file.existed).length;
|
|
954
|
+
result.unchanged = prepared.filter(file => !file.changed).length;
|
|
955
|
+
return result;
|
|
956
|
+
}
|
|
957
|
+
//# sourceMappingURL=output_writer.js.map
|
|
958
|
+
|
|
959
|
+
/***/ },
|
|
960
|
+
|
|
607
961
|
/***/ "./build/write_objects.js"
|
|
608
962
|
/*!********************************!*\
|
|
609
963
|
!*** ./build/write_objects.js ***!
|
|
@@ -646,28 +1000,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
646
1000
|
};
|
|
647
1001
|
})();
|
|
648
1002
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
649
|
-
exports.
|
|
1003
|
+
exports.collectObjectFiles = collectObjectFiles;
|
|
650
1004
|
const path = __importStar(__webpack_require__(/*! path */ "path"));
|
|
651
1005
|
const url_1 = __webpack_require__(/*! url */ "url");
|
|
652
1006
|
const transpiler_1 = __webpack_require__(/*! @abaplint/transpiler */ "./node_modules/@abaplint/transpiler/build/src/index.js");
|
|
653
|
-
|
|
654
|
-
async function writeObjects(outputFiles, config, files) {
|
|
1007
|
+
function collectObjectFiles(outputFiles, config, files) {
|
|
655
1008
|
const root = path.resolve(config.output_folder);
|
|
656
1009
|
const filesToWrite = [];
|
|
657
|
-
const destinations = new Set();
|
|
658
1010
|
const sources = new Map(files.map(file => [file.filename.toLowerCase(), file]));
|
|
659
1011
|
const add = (filename, contents) => {
|
|
660
|
-
|
|
661
|
-
const relative = path.relative(root, target);
|
|
662
|
-
if (relative === "" || relative.startsWith(".." + path.sep) || relative === ".." || path.isAbsolute(relative)) {
|
|
663
|
-
throw new Error("Output file escapes output_folder: " + filename);
|
|
664
|
-
}
|
|
665
|
-
const key = target.toLowerCase();
|
|
666
|
-
if (destinations.has(key)) {
|
|
667
|
-
throw new Error("Conflicting output file: " + filename);
|
|
668
|
-
}
|
|
669
|
-
destinations.add(key);
|
|
670
|
-
filesToWrite.push({ path: target, contents });
|
|
1012
|
+
filesToWrite.push({ path: filename.replace(/\\/g, "/"), contents });
|
|
671
1013
|
};
|
|
672
1014
|
for (const output of outputFiles) {
|
|
673
1015
|
const type = output.object.type.toUpperCase();
|
|
@@ -688,21 +1030,24 @@ async function writeObjects(outputFiles, config, files) {
|
|
|
688
1030
|
if (file === undefined) {
|
|
689
1031
|
continue;
|
|
690
1032
|
}
|
|
691
|
-
if (file.
|
|
1033
|
+
if (file.sourceMapPath !== undefined) {
|
|
1034
|
+
sourcePaths[filename] = file.sourceMapPath;
|
|
1035
|
+
}
|
|
1036
|
+
else if (file.relative !== undefined) {
|
|
692
1037
|
const source = path.resolve(root, file.relative, file.filename);
|
|
693
1038
|
const relative = path.relative(mapDirectory, source);
|
|
694
1039
|
// Sources on another Windows drive cannot be expressed as relative paths.
|
|
695
1040
|
sourcePaths[filename] = path.isAbsolute(relative) ? (0, url_1.pathToFileURL)(source).href
|
|
696
1041
|
: relative.split(path.sep).map(segment => encodeURIComponent(segment)).join("/");
|
|
697
1042
|
}
|
|
698
|
-
//
|
|
1043
|
+
// Embedding source text keeps maps useful after temporary checkouts are removed.
|
|
699
1044
|
sourceContents[filename] = file.contents;
|
|
700
1045
|
}
|
|
701
1046
|
add(name, output.chunk.getMap(path.basename(output.filename), { generatedLineOffset, sourcePaths, sourceContents }));
|
|
702
1047
|
}
|
|
703
1048
|
add(output.filename, contents);
|
|
704
1049
|
}
|
|
705
|
-
|
|
1050
|
+
return filesToWrite;
|
|
706
1051
|
}
|
|
707
1052
|
//# sourceMappingURL=write_objects.js.map
|
|
708
1053
|
|
|
@@ -127819,6 +128164,11 @@ run().then(() => {
|
|
|
127819
128164
|
}
|
|
127820
128165
|
methods.push(m.name);
|
|
127821
128166
|
}
|
|
128167
|
+
if (methods.length === 0) {
|
|
128168
|
+
// FOR TESTING helper class without test methods, nothing to run; it might also
|
|
128169
|
+
// live in locals_imp, which is not exported from the .testclasses module
|
|
128170
|
+
continue;
|
|
128171
|
+
}
|
|
127822
128172
|
tests.push({
|
|
127823
128173
|
obj,
|
|
127824
128174
|
filename: `${(0, output_layout_1.importPath)("index.mjs", this.layout.objectModule(obj, ".testclasses"))}`,
|
package/build/types.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ITranspilerConfig {
|
|
|
12
12
|
exclude_filter?: string[];
|
|
13
13
|
/** @minLength 1 */
|
|
14
14
|
output_folder: string;
|
|
15
|
+
/** Skip unchanged generated files and remove tracked obsolete output */
|
|
16
|
+
incremental_output?: boolean;
|
|
15
17
|
/** experimental */
|
|
16
18
|
converter?: {
|
|
17
19
|
/** @uniqueItems true */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/transpiler-cli",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.2",
|
|
4
4
|
"description": "Transpiler - Command Line Interface",
|
|
5
5
|
"funding": "https://github.com/sponsors/larshp",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@abaplint/core": "^2.120.71",
|
|
31
|
-
"@abaplint/transpiler": "^2.14.
|
|
31
|
+
"@abaplint/transpiler": "^2.14.2",
|
|
32
32
|
"@types/glob": "^9.0.0",
|
|
33
33
|
"@types/node": "^26.6.2",
|
|
34
34
|
"@types/progress": "^2.0.7",
|