Notably, this gets us `@actions/github` 6.0.1, which is necessary for `github.context.runAttempt` (so I have also updated `package.json` accordingly). On that note... great job following semver, GitHub! Between this and the non-breaking major version change on `@actions/cache`, I'm starting to feel like nobody on the Actions team understands how software versioning works.
415 lines
15 KiB
JavaScript
415 lines
15 KiB
JavaScript
// Copyright 2021-2025 Buf Technologies, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
import { protoInt64, ScalarType, } from "@bufbuild/protobuf";
|
|
import { createImportSymbol } from "./import-symbol.js";
|
|
import { makeImportPathRelative } from "./import-path.js";
|
|
import { createJsDocTextFromDesc, formatJsDocBlock } from "./jsdoc.js";
|
|
export function createGeneratedFile(name, importPath, jsImportStyle, rewriteImport, resolveDescImport, resolveShapeImport, resolveJsonImport, resolveValidImport, createPreamble, runtime) {
|
|
let preamble;
|
|
const el = [];
|
|
return {
|
|
preamble(file) {
|
|
preamble = createPreamble(file);
|
|
},
|
|
print(printableOrFragments, ...rest) {
|
|
let printables;
|
|
if (printableOrFragments != null &&
|
|
Object.prototype.hasOwnProperty.call(printableOrFragments, "raw")) {
|
|
// If called with a tagged template literal
|
|
printables = buildPrintablesFromFragments(printableOrFragments, rest);
|
|
}
|
|
else {
|
|
// If called with just an array of Printables
|
|
printables =
|
|
printableOrFragments != null
|
|
? [printableOrFragments, ...rest]
|
|
: rest;
|
|
}
|
|
printableToEl({
|
|
el,
|
|
runtime,
|
|
resolveDescImport,
|
|
resolveShapeImport,
|
|
resolveJsonImport,
|
|
resolveValidImport,
|
|
}, printables);
|
|
el.push("\n");
|
|
},
|
|
export(declaration, name) {
|
|
return {
|
|
kind: "es_export_stmt",
|
|
name,
|
|
declaration,
|
|
};
|
|
},
|
|
string(string) {
|
|
return {
|
|
kind: "es_string",
|
|
value: string,
|
|
};
|
|
},
|
|
array(elements) {
|
|
const p = [];
|
|
p.push("[");
|
|
for (const [index, element] of elements.entries()) {
|
|
p.push(element);
|
|
if (index < elements.length - 1) {
|
|
p.push(", ");
|
|
}
|
|
}
|
|
p.push("]");
|
|
return p;
|
|
},
|
|
jsDoc(textOrSchema, indentation) {
|
|
return {
|
|
kind: "es_jsdoc",
|
|
text: typeof textOrSchema == "string"
|
|
? textOrSchema
|
|
: createJsDocTextFromDesc(textOrSchema),
|
|
indentation,
|
|
};
|
|
},
|
|
importSchema(schema, typeOnly = false) {
|
|
return resolveDescImport(schema, typeOnly);
|
|
},
|
|
importShape(schema) {
|
|
return resolveShapeImport(schema);
|
|
},
|
|
importJson(desc) {
|
|
return resolveJsonImport(desc);
|
|
},
|
|
importValid(desc) {
|
|
return resolveValidImport(desc);
|
|
},
|
|
import(name, from, typeOnly = false) {
|
|
return createImportSymbol(name, from, typeOnly);
|
|
},
|
|
jsImportStyle,
|
|
runtime,
|
|
getFileInfo() {
|
|
return {
|
|
name,
|
|
content: elToContent(el, importPath, rewriteImport, jsImportStyle == "legacy_commonjs"),
|
|
preamble,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
function elToContent(el, importerPath, rewriteImportPath, legacyCommonJs) {
|
|
if (el.length == 0) {
|
|
return "";
|
|
}
|
|
const c = [];
|
|
if (legacyCommonJs) {
|
|
c.push(`"use strict";\n`);
|
|
c.push(`Object.defineProperty(exports, "__esModule", { value: true });\n`);
|
|
c.push("\n");
|
|
}
|
|
const symbolToIdentifier = processImports(el, importerPath, rewriteImportPath, (typeOnly, from, names) => {
|
|
if (legacyCommonJs) {
|
|
const p = names.map(({ name, alias }) => alias == undefined ? name : `${name}: ${alias}`);
|
|
const what = `{ ${p.join(", ")} }`;
|
|
c.push(`const ${what} = require(${escapeString(from)});\n`);
|
|
}
|
|
else {
|
|
const p = names.map(({ name, alias }) => alias == undefined ? name : `${name} as ${alias}`);
|
|
const what = `{ ${p.join(", ")} }`;
|
|
if (typeOnly) {
|
|
c.push(`import type ${what} from ${escapeString(from)};\n`);
|
|
}
|
|
else {
|
|
c.push(`import ${what} from ${escapeString(from)};\n`);
|
|
}
|
|
}
|
|
});
|
|
if (c.length > 0) {
|
|
c.push("\n");
|
|
}
|
|
const legacyCommonJsExportNames = [];
|
|
for (const e of el) {
|
|
if (typeof e == "string") {
|
|
c.push(e);
|
|
}
|
|
else {
|
|
switch (e.kind) {
|
|
case "es_symbol": {
|
|
const ident = symbolToIdentifier.get(e.id);
|
|
if (ident != undefined) {
|
|
c.push(ident);
|
|
}
|
|
break;
|
|
}
|
|
case "es_export_stmt":
|
|
if (legacyCommonJs) {
|
|
legacyCommonJsExportNames.push(e.name);
|
|
}
|
|
else {
|
|
c.push("export ");
|
|
}
|
|
if (e.declaration !== undefined && e.declaration.length > 0) {
|
|
c.push(e.declaration, " ");
|
|
}
|
|
c.push(e.name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (legacyCommonJs) {
|
|
if (legacyCommonJsExportNames.length > 0) {
|
|
c.push("\n");
|
|
}
|
|
for (const name of legacyCommonJsExportNames) {
|
|
c.push("exports.", name, " = ", name, ";\n");
|
|
}
|
|
}
|
|
return c.join("");
|
|
}
|
|
function printableToEl(opt, printables) {
|
|
const { el } = opt;
|
|
for (const p of printables) {
|
|
if (Array.isArray(p)) {
|
|
printableToEl(opt, p);
|
|
}
|
|
else {
|
|
switch (typeof p) {
|
|
case "string":
|
|
el.push(p);
|
|
break;
|
|
case "number":
|
|
if (Number.isNaN(p)) {
|
|
el.push("globalThis.NaN");
|
|
}
|
|
else if (p === Number.POSITIVE_INFINITY) {
|
|
el.push("globalThis.Infinity");
|
|
}
|
|
else if (p === Number.NEGATIVE_INFINITY) {
|
|
el.push("-globalThis.Infinity");
|
|
}
|
|
else {
|
|
el.push(p.toString(10));
|
|
}
|
|
break;
|
|
case "boolean":
|
|
el.push(p.toString());
|
|
break;
|
|
case "bigint":
|
|
if (p == protoInt64.zero) {
|
|
// Loose comparison will match between 0n and 0.
|
|
el.push(opt.runtime.protoInt64, ".zero");
|
|
}
|
|
else {
|
|
el.push(opt.runtime.protoInt64, p > 0 ? ".uParse(" : ".parse(", escapeString(p.toString()), ")");
|
|
}
|
|
break;
|
|
case "object":
|
|
if (p instanceof Uint8Array) {
|
|
if (p.length === 0) {
|
|
el.push("new Uint8Array(0)");
|
|
}
|
|
else {
|
|
el.push("new Uint8Array([");
|
|
const strings = [];
|
|
for (const n of p) {
|
|
strings.push("0x" + n.toString(16).toUpperCase().padStart(2, "0"));
|
|
}
|
|
el.push(strings.join(", "));
|
|
el.push("])");
|
|
}
|
|
break;
|
|
}
|
|
switch (p.kind) {
|
|
case "es_symbol":
|
|
case "es_export_stmt":
|
|
el.push(p);
|
|
break;
|
|
case "es_desc_ref":
|
|
el.push(opt.resolveDescImport(p.desc, p.typeOnly));
|
|
break;
|
|
case "es_shape_ref":
|
|
el.push(opt.resolveShapeImport(p.desc));
|
|
break;
|
|
case "es_json_type_ref":
|
|
el.push(opt.resolveJsonImport(p.desc));
|
|
break;
|
|
case "es_valid_type_ref":
|
|
el.push(opt.resolveValidImport(p.desc));
|
|
break;
|
|
case "es_jsdoc":
|
|
el.push(formatJsDocBlock(p.text, p.indentation));
|
|
break;
|
|
case "es_string":
|
|
el.push(escapeString(p.value));
|
|
break;
|
|
case "es_proto_int64":
|
|
if (p.longAsString) {
|
|
el.push(escapeString(p.value.toString()));
|
|
}
|
|
else {
|
|
if (p.value == protoInt64.zero) {
|
|
// Loose comparison will match between 0n and 0.
|
|
el.push(opt.runtime.protoInt64, ".zero");
|
|
}
|
|
else {
|
|
switch (p.type) {
|
|
case ScalarType.UINT64:
|
|
case ScalarType.FIXED64:
|
|
el.push(opt.runtime.protoInt64, ".uParse(", escapeString(p.value.toString()), ")");
|
|
break;
|
|
default:
|
|
el.push(opt.runtime.protoInt64, ".parse(", escapeString(p.value.toString()), ")");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
throw `cannot print ${typeof p}`;
|
|
}
|
|
break;
|
|
default:
|
|
throw `cannot print ${typeof p}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function buildPrintablesFromFragments(fragments, values) {
|
|
const printables = [];
|
|
fragments.forEach((fragment, i) => {
|
|
printables.push(fragment);
|
|
if (fragments.length - 1 !== i) {
|
|
printables.push(values[i]);
|
|
}
|
|
});
|
|
return printables;
|
|
}
|
|
function processImports(el, importerPath, rewriteImportPath, makeImportStatement) {
|
|
// identifiers to use in the output
|
|
const symbolToIdentifier = new Map();
|
|
// symbols that need a value import (as opposed to a type-only import)
|
|
const symbolToIsValue = new Map();
|
|
// taken in this file
|
|
const identifiersTaken = new Set();
|
|
// foreign symbols need an import
|
|
const foreignSymbols = [];
|
|
// Walk through all symbols used and populate the collections above.
|
|
for (const s of el) {
|
|
if (typeof s != "object") {
|
|
continue;
|
|
}
|
|
switch (s.kind) {
|
|
case "es_symbol":
|
|
symbolToIdentifier.set(s.id, s.name);
|
|
if (!s.typeOnly) {
|
|
// a symbol is only type-imported as long as all uses are type-only
|
|
symbolToIsValue.set(s.id, true);
|
|
}
|
|
if (s.from === importerPath) {
|
|
identifiersTaken.add(s.name);
|
|
}
|
|
else {
|
|
foreignSymbols.push(s);
|
|
}
|
|
break;
|
|
case "es_export_stmt":
|
|
identifiersTaken.add(s.name);
|
|
break;
|
|
}
|
|
}
|
|
// Walk through all foreign symbols and make their identifiers unique.
|
|
const handledSymbols = new Set();
|
|
for (const s of foreignSymbols) {
|
|
if (handledSymbols.has(s.id)) {
|
|
continue;
|
|
}
|
|
handledSymbols.add(s.id);
|
|
if (!identifiersTaken.has(s.name)) {
|
|
identifiersTaken.add(s.name);
|
|
continue;
|
|
}
|
|
let i = 1;
|
|
let alias;
|
|
for (;;) {
|
|
// We choose '$' because it is invalid in proto identifiers.
|
|
alias = `${s.name}$${i}`;
|
|
if (!identifiersTaken.has(alias)) {
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
identifiersTaken.add(alias);
|
|
symbolToIdentifier.set(s.id, alias);
|
|
}
|
|
const sourceToImport = new Map();
|
|
for (const s of foreignSymbols) {
|
|
let i = sourceToImport.get(s.from);
|
|
if (i == undefined) {
|
|
i = {
|
|
types: new Map(),
|
|
values: new Map(),
|
|
};
|
|
sourceToImport.set(s.from, i);
|
|
}
|
|
let alias = symbolToIdentifier.get(s.id);
|
|
if (alias == s.name) {
|
|
alias = undefined;
|
|
}
|
|
if (symbolToIsValue.get(s.id)) {
|
|
i.values.set(s.name, alias);
|
|
}
|
|
else {
|
|
i.types.set(s.name, alias);
|
|
}
|
|
}
|
|
// Make import statements.
|
|
const handledSource = new Set();
|
|
const buildNames = (map) => {
|
|
const names = [];
|
|
map.forEach((value, key) => names.push({ name: key, alias: value }));
|
|
names.sort((a, b) => a.name.localeCompare(b.name));
|
|
return names;
|
|
};
|
|
for (const s of foreignSymbols) {
|
|
if (handledSource.has(s.from)) {
|
|
continue;
|
|
}
|
|
handledSource.add(s.from);
|
|
const i = sourceToImport.get(s.from);
|
|
if (i == undefined) {
|
|
// should never happen
|
|
continue;
|
|
}
|
|
const from = makeImportPathRelative(importerPath, rewriteImportPath(s.from));
|
|
if (i.types.size > 0) {
|
|
makeImportStatement(true, from, buildNames(i.types));
|
|
}
|
|
if (i.values.size > 0) {
|
|
makeImportStatement(false, from, buildNames(i.values));
|
|
}
|
|
}
|
|
return symbolToIdentifier;
|
|
}
|
|
function escapeString(value) {
|
|
return ('"' +
|
|
value
|
|
.split("\\")
|
|
.join("\\\\")
|
|
.split('"')
|
|
.join('\\"')
|
|
.split("\r")
|
|
.join("\\r")
|
|
.split("\n")
|
|
.join("\\n") +
|
|
'"');
|
|
}
|