cosmemilton-acbr-node 0.0.0-stage → 0.1.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/LICENSE +502 -0
- package/NOTICE +8 -0
- package/README.md +105 -2
- package/acbr.config.json +16 -0
- package/acbr.lock.json +3475 -0
- package/dist/cli.js +1652 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.cjs +3349 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +3100 -0
- package/dist/index.js.map +7 -0
- package/dist/runtime.cjs +350 -0
- package/dist/runtime.cjs.map +7 -0
- package/dist/runtime.js +312 -0
- package/dist/runtime.js.map +7 -0
- package/dist/types/generated/acbr/index.d.ts +14 -0
- package/dist/types/generated/acbr/models.d.ts +3007 -0
- package/dist/types/src/cli.d.ts +20 -0
- package/dist/types/src/config.d.ts +30 -0
- package/dist/types/src/generator.d.ts +93 -0
- package/dist/types/src/index.d.ts +16 -0
- package/dist/types/src/native-build.d.ts +25 -0
- package/dist/types/src/offline-check.d.ts +10 -0
- package/dist/types/src/offline-fixture.d.ts +104 -0
- package/dist/types/src/runtime.d.ts +26 -0
- package/dist/types/src/source.d.ts +31 -0
- package/dist/types/src/types.d.ts +103 -0
- package/docs/API.md +32 -0
- package/docs/ARQUITETURA.md +15 -0
- package/docs/CI.md +54 -0
- package/docs/CLI.md +32 -0
- package/docs/DEPENDENCIAS-NATIVAS.md +93 -0
- package/docs/VALIDACAO.md +65 -0
- package/docs/licenses/fpc/COPYING.FPC +25 -0
- package/docs/licenses/fpc/COPYING.GPL.txt +340 -0
- package/docs/licenses/fpc/COPYING.LGPL.txt +504 -0
- package/docs/licenses/fpc/ORIGIN.json +21 -0
- package/docs/licenses/lazarus/COPYING.LGPL.txt +481 -0
- package/docs/licenses/lazarus/COPYING.modifiedLGPL.txt +26 -0
- package/docs/licenses/lazarus/COPYING.txt +46 -0
- package/docs/licenses/lazarus/ORIGIN.json +1 -0
- package/native/NativeErrors.pas +14 -0
- package/native/NativeMotor.pas +220 -0
- package/native/NativeParent.pas +61 -0
- package/native/NativeProtocol.pas +64 -0
- package/native/NativeTLS.pas +70 -0
- package/native/acbr-worker.lpr +26 -0
- package/native/generated/AcbrModels.pas +4287 -0
- package/package.json +72 -4
- package/scripts/build-native-deps.mjs +237 -0
- package/tools/extract-model.lpr +119 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1652 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/generator.ts
|
|
13
|
+
var generator_exports = {};
|
|
14
|
+
__export(generator_exports, {
|
|
15
|
+
buildModelIR: () => buildModelIR,
|
|
16
|
+
compareContracts: () => compareContracts,
|
|
17
|
+
generateModel: () => generateModel,
|
|
18
|
+
renderClientIndex: () => renderClientIndex,
|
|
19
|
+
renderPascal: () => renderPascal,
|
|
20
|
+
renderTypeScript: () => renderTypeScript
|
|
21
|
+
});
|
|
22
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
23
|
+
import { execFile } from "node:child_process";
|
|
24
|
+
import { promisify } from "node:util";
|
|
25
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4, readdir as readdir4 } from "node:fs/promises";
|
|
26
|
+
import path2 from "node:path";
|
|
27
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
28
|
+
function buildModelIR(raw) {
|
|
29
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
30
|
+
for (const unit of raw.units) for (const type of unit.types) declarations.set((unit.name + "." + type.name).toLowerCase(), { unit, type });
|
|
31
|
+
function find(name, unit) {
|
|
32
|
+
for (const key of [unit.name + "." + name, ...[...unit.uses].reverse().map((u) => u + "." + name), name]) {
|
|
33
|
+
const item = declarations.get(key.toLowerCase());
|
|
34
|
+
if (item) return item;
|
|
35
|
+
}
|
|
36
|
+
throw new Error("Tipo Pascal n\xE3o resolvido: " + unit.name + "." + name);
|
|
37
|
+
}
|
|
38
|
+
function resolve5(name, unit, trail = /* @__PURE__ */ new Set()) {
|
|
39
|
+
if (primitives[name.toLowerCase()]) return { primitive: name.toLowerCase() };
|
|
40
|
+
const found = find(name, unit), key = (found.unit.name + "." + found.type.name).toLowerCase();
|
|
41
|
+
if (trail.has(key)) throw new Error("Alias Pascal c\xEDclico: " + key);
|
|
42
|
+
if (found.type.kind === "alias") {
|
|
43
|
+
trail.add(key);
|
|
44
|
+
return resolve5(found.type.target, found.unit, trail);
|
|
45
|
+
}
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
const rootUnit = raw.units.find((u) => u.types.some((t) => t.name === raw.root));
|
|
49
|
+
if (!rootUnit) throw new Error("Modelo raiz ausente: " + raw.root);
|
|
50
|
+
const generated = /* @__PURE__ */ new Map();
|
|
51
|
+
function visit(name, unit) {
|
|
52
|
+
const found = resolve5(name, unit);
|
|
53
|
+
if ("primitive" in found) return primitives[found.primitive];
|
|
54
|
+
const { type, unit: declUnit } = found, key = type.name;
|
|
55
|
+
if (generated.has(key)) {
|
|
56
|
+
const node2 = generated.get(key);
|
|
57
|
+
if (node2.unit !== declUnit.name) throw new Error("Tipos hom\xF4nimos incompat\xEDveis: " + key);
|
|
58
|
+
return { kind: node2.kind === "object" ? "object" : node2.kind === "list" ? "list" : "enum", ref: key };
|
|
59
|
+
}
|
|
60
|
+
if (type.kind === "enum") {
|
|
61
|
+
const values = type.values.map((v) => v.name);
|
|
62
|
+
if (!values.length) throw new Error("Enum sem valores: " + key);
|
|
63
|
+
generated.set(key, { name: key, unit: declUnit.name, kind: "enum", values });
|
|
64
|
+
return { kind: "enum", ref: key };
|
|
65
|
+
}
|
|
66
|
+
if (type.kind !== "class") throw new Error("Declara\xE7\xE3o Pascal n\xE3o suportada: " + declUnit.name + "." + key + " (" + (type.declarationKind ?? type.kind) + ")");
|
|
67
|
+
const creator = type.methods?.find((m) => m.name.toLowerCase() === "new" && m.arguments === 0);
|
|
68
|
+
const indexed = type.properties?.find((p) => p.indexed && p.name.toLowerCase() === "items");
|
|
69
|
+
if (creator || indexed) {
|
|
70
|
+
if (!creator || !indexed || !indexed.readable) throw new Error("Cole\xE7\xE3o sem contrato New/Items tipado: " + key);
|
|
71
|
+
const item = resolve5(creator.result, declUnit), itemProperty = resolve5(indexed.type, declUnit);
|
|
72
|
+
if ("primitive" in item || "primitive" in itemProperty || item.type.name !== itemProperty.type.name) throw new Error("Cole\xE7\xE3o com tipos New/Items divergentes: " + key);
|
|
73
|
+
generated.set(key, { name: key, unit: declUnit.name, kind: "list", item: item.type.name, factory: creator.name });
|
|
74
|
+
if (visit(creator.result, declUnit).kind !== "object") throw new Error("Item de cole\xE7\xE3o n\xE3o \xE9 objeto: " + key);
|
|
75
|
+
return { kind: "list", ref: key };
|
|
76
|
+
}
|
|
77
|
+
const node = { name: key, unit: declUnit.name, kind: "object", properties: [] };
|
|
78
|
+
generated.set(key, node);
|
|
79
|
+
let inherited = [];
|
|
80
|
+
if (type.ancestor && !bases.has(type.ancestor.toLowerCase())) {
|
|
81
|
+
const ancestor = visit(type.ancestor, declUnit);
|
|
82
|
+
if (ancestor.kind !== "object") throw new Error("Heran\xE7a n\xE3o suportada: " + key);
|
|
83
|
+
inherited = generated.get(ancestor.ref).properties;
|
|
84
|
+
}
|
|
85
|
+
const props = new Map(inherited.map((p) => [p.name.toLowerCase(), p]));
|
|
86
|
+
for (const p of type.properties ?? []) {
|
|
87
|
+
if (p.indexed) throw new Error("Propriedade indexada fora de cole\xE7\xE3o: " + key + "." + p.name);
|
|
88
|
+
if (!p.readable) throw new Error("Propriedade sem leitura: " + key + "." + p.name);
|
|
89
|
+
const resolved = visit(p.type, declUnit);
|
|
90
|
+
const readonly = !p.writable && !["object", "list"].includes(resolved.kind) || key === raw.root && ["signature", "procnfe", "infnfesupl"].includes(p.name.toLowerCase());
|
|
91
|
+
props.set(p.name.toLowerCase(), { name: p.name, type: p.type, ...resolved, readonly, ...p.default ? { declaredDefault: p.default } : {} });
|
|
92
|
+
}
|
|
93
|
+
node.properties = [...props.values()].sort((a, b) => a.name.localeCompare(b.name, "en"));
|
|
94
|
+
return { kind: "object", ref: key };
|
|
95
|
+
}
|
|
96
|
+
visit(raw.root, rootUnit);
|
|
97
|
+
return { schemaVersion: 1, root: raw.root, types: [...generated.values()].sort((a, b) => a.name.localeCompare(b.name, "en")) };
|
|
98
|
+
}
|
|
99
|
+
function compareContracts(previous, current) {
|
|
100
|
+
const old = new Map(previous?.types.map((t) => [t.name, t]) ?? []), now = new Map(current.types.map((t) => [t.name, t]));
|
|
101
|
+
const additions = [], removals = [], changes = [];
|
|
102
|
+
for (const [name, type] of now) {
|
|
103
|
+
const before = old.get(name);
|
|
104
|
+
if (!before) {
|
|
105
|
+
additions.push(name);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (type.kind !== before.kind) {
|
|
109
|
+
changes.push(name + ":kind");
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (type.kind === "object") {
|
|
113
|
+
const a = new Map(before.properties.map((p) => [p.name, p])), b = new Map(type.properties.map((p) => [p.name, p]));
|
|
114
|
+
for (const [field, p] of b) {
|
|
115
|
+
if (!a.has(field)) additions.push(name + "." + field);
|
|
116
|
+
else if (stable(a.get(field)) !== stable(p)) changes.push(name + "." + field);
|
|
117
|
+
}
|
|
118
|
+
for (const field of a.keys()) if (!b.has(field)) removals.push(name + "." + field);
|
|
119
|
+
} else if (type.kind === "enum") {
|
|
120
|
+
for (const v of type.values) if (!before.values.includes(v)) additions.push(name + "." + v);
|
|
121
|
+
for (const v of before.values) if (!type.values.includes(v)) removals.push(name + "." + v);
|
|
122
|
+
} else if (type.item !== before.item) changes.push(name + ":item");
|
|
123
|
+
}
|
|
124
|
+
for (const name of old.keys()) if (!now.has(name)) removals.push(name);
|
|
125
|
+
return { additions: additions.sort(), removals: removals.sort(), changes: changes.sort(), breaking: removals.length > 0 || changes.length > 0 };
|
|
126
|
+
}
|
|
127
|
+
function renderTypeScript(ir, revision2, contractHash) {
|
|
128
|
+
const out = ["// Generated from ACBr AST. LGPL-2.1-or-later. Do not edit.", "export const ACBR_CONTRACT = " + stable({ sourceRevision: revision2, contractHash }) + " as const;", "export const SOURCE_REVISION=ACBR_CONTRACT.sourceRevision;", "export const CONTRACT_HASH=ACBR_CONTRACT.contractHash;"];
|
|
129
|
+
for (const t of ir.types) {
|
|
130
|
+
if (t.kind === "enum") {
|
|
131
|
+
out.push("export const " + t.name + " = Object.freeze(" + stable(Object.fromEntries(t.values.map((v) => [v, v]))) + " as const);", "export type " + t.name + " = typeof " + t.name + "[keyof typeof " + t.name + "];");
|
|
132
|
+
} else if (t.kind === "list") {
|
|
133
|
+
out.push("export type " + t.name + "Input = " + t.item + "Input[];", "export class " + t.name + " extends Array<" + t.item + "> {", " New(input: " + t.item + "Input = {}): " + t.item + " { const item=new " + t.item + "(input); this.push(item); return item; }", "}");
|
|
134
|
+
} else {
|
|
135
|
+
out.push("export interface " + t.name + "Input {");
|
|
136
|
+
for (const p of t.properties.filter((p2) => !p2.readonly)) out.push(" " + JSON.stringify(p.name) + "?: " + tsType(p) + ";");
|
|
137
|
+
out.push("}", "export class " + t.name + " {");
|
|
138
|
+
for (const p of t.properties) {
|
|
139
|
+
if (p.readonly) {
|
|
140
|
+
out.push(" readonly " + JSON.stringify(p.name) + "?: " + tsType(p) + ";");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (p.declaredDefault) out.push(" /** Pascal streaming default: " + p.declaredDefault.replaceAll("*/", "") + ". Omission preserves the native constructor. */");
|
|
144
|
+
if (p.kind === "object" || p.kind === "list") out.push(" " + JSON.stringify(p.name) + " = new " + p.ref + "();");
|
|
145
|
+
else out.push(" " + JSON.stringify(p.name) + "?: " + tsType(p) + ";");
|
|
146
|
+
}
|
|
147
|
+
out.push(" constructor(input: " + t.name + "Input = {}) {", " validateModel(" + JSON.stringify(t.name) + ", input);");
|
|
148
|
+
for (const p of t.properties.filter((p2) => !p2.readonly)) {
|
|
149
|
+
const access5 = "input[" + JSON.stringify(p.name) + "]", target = "this[" + JSON.stringify(p.name) + "]";
|
|
150
|
+
out.push(" if (" + access5 + " !== undefined) " + target + " = " + (p.kind === "object" ? "new " + p.ref + "(" + access5 + ")" : p.kind === "list" ? "Object.assign(new " + p.ref + "(), " + access5 + ".map(v=>new " + ir.types.find((t2) => t2.name === p.ref).item + "(v)))" : access5) + ";");
|
|
151
|
+
}
|
|
152
|
+
out.push(" }", "}");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
out.push("const MODEL_SCHEMA: Record<string, any> = " + stable(Object.fromEntries(ir.types.map((t) => [t.name, t]))) + ";");
|
|
156
|
+
out.push(String.raw`
|
|
157
|
+
export function validateModel(model: string, value: unknown, at=model): void {
|
|
158
|
+
const schema=MODEL_SCHEMA[model]; if(!schema)throw new TypeError('Modelo desconhecido: '+model);
|
|
159
|
+
if(schema.kind==='enum'){ if(typeof value!=='string'||!schema.values.includes(value))throw new TypeError(at+': enum inválido'); return; }
|
|
160
|
+
if(schema.kind==='list'){ if(!Array.isArray(value))throw new TypeError(at+': esperado array'); value.forEach((item,index)=>validateModel(schema.item,item,at+'['+index+']'));return; }
|
|
161
|
+
if(value===null||typeof value!=='object'||Array.isArray(value))throw new TypeError(at+': esperado objeto');
|
|
162
|
+
const fields=new Map<string,any>(schema.properties.filter((p:any)=>!p.readonly).map((p:any)=>[p.name,p]));
|
|
163
|
+
for(const [name,v] of Object.entries(value)){
|
|
164
|
+
if(v===undefined)continue;
|
|
165
|
+
const p=fields.get(name);if(!p)throw new TypeError(at+'.'+name+': propriedade desconhecida ou somente leitura');
|
|
166
|
+
if(v===undefined)continue;
|
|
167
|
+
const field=at+'.'+name;
|
|
168
|
+
if(p.ref){validateModel(p.ref,v,field);continue;}
|
|
169
|
+
if(p.kind==='integer'){if(typeof v!=='number'||!Number.isSafeInteger(v)||v<p.min||v>p.max)throw new TypeError(field+': inteiro fora do intervalo');}
|
|
170
|
+
else if(p.kind==='boolean'){if(typeof v!=='boolean')throw new TypeError(field+': esperado boolean');}
|
|
171
|
+
else if(p.kind==='decimal'){if(typeof v!=='string'||v.length>64||! /^-?\d+(?:\.\d+)?$/.test(v)||p.scale!==undefined&&(v.split('.')[1]?.length??0)>p.scale)throw new TypeError(field+': esperado decimal em string'+(p.scale!==undefined?' de até '+p.scale+' casas':''));}
|
|
172
|
+
else if(p.kind==='datetime'){
|
|
173
|
+
if(typeof v!=='string'||! /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)?$/.test(v)||Number.isNaN(Date.parse(v+(v.length===10?'T00:00:00Z':'Z'))))throw new TypeError(field+': esperado data/hora civil ISO (sem timezone)');
|
|
174
|
+
const date=new Date(v+(v.length===10?'T00:00:00Z':'Z'));if(date.toISOString().slice(0,10)!==v.slice(0,10))throw new TypeError(field+': data inválida');
|
|
175
|
+
}else if(typeof v!=='string')throw new TypeError(field+': esperado string');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export function validateTNFe(value: unknown): asserts value is TNFeInput { validateModel('TNFe',value); }
|
|
179
|
+
`);
|
|
180
|
+
return out.join("\n") + "\n";
|
|
181
|
+
}
|
|
182
|
+
function renderPascal(ir, revision2, contractHash) {
|
|
183
|
+
const classes = ir.types.filter((t) => t.kind === "object"), byName = new Map(ir.types.map((t) => [t.name, t]));
|
|
184
|
+
const units = [...new Set(ir.types.map((t) => t.unit))].sort();
|
|
185
|
+
const out = ["{ Generated from ACBr AST. LGPL-2.1-or-later. Do not edit. }", "unit AcbrModels;", "{$mode delphi}{$H+}", "interface", "uses SysUtils, Classes, fpjson, DateUtils, Math, " + units.join(", ") + ";", "const SourceRevision = " + revision2 + ";", " ContractHash = '" + contractHash + "';", "procedure preencherTNFe(documento:TJSONObject; destino:TNFe);", "implementation"];
|
|
186
|
+
out.push(String.raw`
|
|
187
|
+
procedure Bad(const P,M:string); begin raise Exception.Create(P+': '+M) end;
|
|
188
|
+
function Obj(D:TJSONData;const P:string):TJSONObject;
|
|
189
|
+
begin if (D=nil) or (D.JSONType<>jtObject) then Bad(P,'esperado objeto');Result:=TJSONObject(D) end;
|
|
190
|
+
function Arr(D:TJSONData;const P:string):TJSONArray;
|
|
191
|
+
begin if (D=nil) or (D.JSONType<>jtArray) then Bad(P,'esperado array');Result:=TJSONArray(D) end;
|
|
192
|
+
function Text(D:TJSONData;const P:string):string;
|
|
193
|
+
begin if (D=nil) or (D.JSONType<>jtString) then Bad(P,'esperado string');Result:=D.AsString end;
|
|
194
|
+
function IntValue(D:TJSONData;const P:string;MinV,MaxV:Int64):Int64;
|
|
195
|
+
var V:Extended;
|
|
196
|
+
begin
|
|
197
|
+
if (D=nil) or (D.JSONType<>jtNumber) then Bad(P,'esperado inteiro');
|
|
198
|
+
V:=D.AsFloat;if IsNan(V) or IsInfinite(V) or (Frac(V)<>0) or (V<MinV) or (V>MaxV) then Bad(P,'inteiro fora do intervalo');
|
|
199
|
+
Result:=D.AsInt64;
|
|
200
|
+
end;
|
|
201
|
+
function BoolValue(D:TJSONData;const P:string):boolean;
|
|
202
|
+
begin if (D=nil) or (D.JSONType<>jtBoolean) then Bad(P,'esperado boolean');Result:=D.AsBoolean end;
|
|
203
|
+
function DecimalText(D:TJSONData;const P:string;Scale:integer):string;
|
|
204
|
+
var I,PointPos:integer;
|
|
205
|
+
begin
|
|
206
|
+
Result:=Text(D,P);PointPos:=0;
|
|
207
|
+
if (Result='') or (Length(Result)>64) then Bad(P,'decimal inválido');
|
|
208
|
+
for I:=1 to Length(Result) do begin
|
|
209
|
+
if Result[I]='.' then begin if (PointPos>0) or (I=1) or ((I=2) and (Result[1]='-')) or (I=Length(Result)) then Bad(P,'decimal inválido');PointPos:=I end
|
|
210
|
+
else if (Result[I]='-') and (I=1) then begin if Length(Result)=1 then Bad(P,'decimal inválido') end
|
|
211
|
+
else if not (Result[I] in ['0'..'9']) then Bad(P,'decimal inválido');
|
|
212
|
+
end;
|
|
213
|
+
if (PointPos>0) and (Scale>=0) and (Length(Result)-PointPos>Scale) then Bad(P,'casas decimais excedidas');
|
|
214
|
+
end;
|
|
215
|
+
function FloatValue(D:TJSONData;const P:string):Extended;
|
|
216
|
+
var F:TFormatSettings;S:string;
|
|
217
|
+
begin F:=DefaultFormatSettings;F.DecimalSeparator:='.';F.ThousandSeparator:=#0;S:=DecimalText(D,P,-1);
|
|
218
|
+
if not TryStrToFloat(S,Result,F) or IsNan(Result) or IsInfinite(Result) then Bad(P,'decimal fora do intervalo');
|
|
219
|
+
end;
|
|
220
|
+
function CurrencyValue(D:TJSONData;const P:string):Currency;
|
|
221
|
+
var F:TFormatSettings;S:string;
|
|
222
|
+
begin F:=DefaultFormatSettings;F.DecimalSeparator:='.';F.ThousandSeparator:=#0;S:=DecimalText(D,P,4);
|
|
223
|
+
if not TryStrToCurr(S,Result,F) then Bad(P,'currency fora do intervalo');
|
|
224
|
+
end;
|
|
225
|
+
function DateValue(D:TJSONData;const P:string):TDateTime;
|
|
226
|
+
var S:string;I:integer;
|
|
227
|
+
begin
|
|
228
|
+
S:=Text(D,P);
|
|
229
|
+
if not (Length(S) in [10,19,21,22,23]) then Bad(P,'esperado data/hora civil ISO sem timezone');
|
|
230
|
+
for I:=1 to Length(S) do if not ((S[I] in ['0'..'9']) or ((I in [5,8]) and (S[I]='-')) or ((I=11) and (S[I]='T')) or ((I in [14,17]) and (S[I]=':')) or ((I=20) and (S[I]='.'))) then Bad(P,'data ISO inválida');
|
|
231
|
+
if not TryISO8601ToDate(S,Result,true) then Bad(P,'data ISO inválida');
|
|
232
|
+
end;
|
|
233
|
+
`);
|
|
234
|
+
const q = (s) => "'" + s.replaceAll("'", "''") + "'";
|
|
235
|
+
for (const t of classes) out.push("procedure Load_" + t.name + "(D:TJSONObject;destino:" + t.unit + "." + t.name + ";const P:string); forward;");
|
|
236
|
+
for (const t of classes) {
|
|
237
|
+
out.push("procedure Load_" + t.name + "(D:TJSONObject;destino:" + t.unit + "." + t.name + ";const P:string);", "var I,J:integer;V:TJSONData;A:TJSONArray;K,At:string;", "begin", " if destino=nil then Bad(P,'objeto ACBr n\xE3o inicializado');", " for I:=0 to D.Count-1 do begin", " K:=D.Names[I];V:=D.Items[I];At:=P+'.'+K;");
|
|
238
|
+
let index = 0;
|
|
239
|
+
for (const p of t.properties.filter((p2) => !p2.readonly)) {
|
|
240
|
+
out.push(" " + (index++ ? "else " : "") + "if K=" + q(p.name) + " then begin");
|
|
241
|
+
const target = "destino." + p.name;
|
|
242
|
+
if (p.kind === "object") out.push(" Load_" + p.ref + "(Obj(V,At)," + target + ",At);");
|
|
243
|
+
else if (p.kind === "list") {
|
|
244
|
+
const list = byName.get(p.ref);
|
|
245
|
+
out.push(" A:=Arr(V,At);if " + target + "=nil then Bad(At,'cole\xE7\xE3o n\xE3o inicializada');" + target + ".Clear;", " for J:=0 to A.Count-1 do Load_" + list.item + "(Obj(A.Items[J],At+'['+IntToStr(J)+']')," + target + "." + list.factory + ",At+'['+IntToStr(J)+']');");
|
|
246
|
+
} else if (p.kind === "enum") {
|
|
247
|
+
const enumeration = byName.get(p.ref);
|
|
248
|
+
out.push(...enumeration.values.map((v, i) => " " + (i ? "else " : "") + "if Text(V,At)=" + q(v) + " then " + target + ":=" + enumeration.unit + "." + v), " else Bad(At,'enum inv\xE1lido');");
|
|
249
|
+
} else out.push(" " + target + ":=" + { string: "Text(V,At)", integer: "IntValue(V,At," + p.min + "," + p.max + ")", boolean: "BoolValue(V,At)", datetime: "DateValue(V,At)", decimal: p.scale === 4 ? "CurrencyValue(V,At)" : "FloatValue(V,At)" }[p.kind] + ";");
|
|
250
|
+
out.push(" end");
|
|
251
|
+
}
|
|
252
|
+
out.push((index ? " else " : " ") + "Bad(At,'propriedade desconhecida ou somente leitura');", " end;", "end;");
|
|
253
|
+
}
|
|
254
|
+
out.push("procedure preencherTNFe(documento:TJSONObject;destino:TNFe);", "begin if documento=nil then Bad('TNFe','esperado objeto');Load_TNFe(documento,destino,'TNFe') end;", "end.");
|
|
255
|
+
return out.join("\n") + "\n";
|
|
256
|
+
}
|
|
257
|
+
function renderClientIndex() {
|
|
258
|
+
return [
|
|
259
|
+
"// Generated client bound to its own ACBr contract. Do not edit.",
|
|
260
|
+
"import { criarEmissor as criarRuntime, type OpcoesEmissor, type OpcoesOperacao } from " + JSON.stringify("cosmemilton-acbr-node/runtime") + ";",
|
|
261
|
+
"import { ACBR_CONTRACT, validateTNFe, type TNFeInput } from " + JSON.stringify("./models.js") + ";",
|
|
262
|
+
"export * from " + JSON.stringify("./models.js") + ";",
|
|
263
|
+
"export function criarEmissor(options: Omit<OpcoesEmissor, " + JSON.stringify("contrato") + " | " + JSON.stringify("validaDocumento") + ">) {",
|
|
264
|
+
" const emissor=criarRuntime({...options, contrato:ACBR_CONTRACT, validaDocumento:validateTNFe});",
|
|
265
|
+
" return {...emissor, gerarXml(documento:TNFeInput, opcoes?:OpcoesOperacao) { return emissor.gerarXml(documento as unknown as Record<string,unknown>, opcoes); }};",
|
|
266
|
+
"}"
|
|
267
|
+
].join("\n") + "\n";
|
|
268
|
+
}
|
|
269
|
+
async function fingerprint(dir) {
|
|
270
|
+
const files = [];
|
|
271
|
+
async function walk(d) {
|
|
272
|
+
for (const entry of await readdir4(d, { withFileTypes: true })) {
|
|
273
|
+
const f = path2.join(d, entry.name);
|
|
274
|
+
if (entry.isDirectory()) await walk(f);
|
|
275
|
+
else if (/\.(pas|pp|inc|xsd)$/i.test(entry.name)) files.push(f);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
await walk(dir);
|
|
279
|
+
const digest = createHash3("sha256");
|
|
280
|
+
for (const f of files.sort()) {
|
|
281
|
+
digest.update(path2.relative(dir, f).replaceAll("\\", "/"));
|
|
282
|
+
digest.update(await readFile5(f));
|
|
283
|
+
}
|
|
284
|
+
return digest.digest("hex");
|
|
285
|
+
}
|
|
286
|
+
async function generateModel(options) {
|
|
287
|
+
const root = path2.resolve(options.outputDir), cache = path2.join(root, ".acbr/generated"), bin = path2.join(root, ".acbr/bin");
|
|
288
|
+
const models = path2.resolve(root, options.modelOutputDir ?? "generated/acbr"), native = path2.resolve(root, options.nativeOutputDir ?? "native/generated");
|
|
289
|
+
await Promise.all([mkdir4(cache, { recursive: true }), mkdir4(bin, { recursive: true }), mkdir4(models, { recursive: true }), mkdir4(native, { recursive: true })]);
|
|
290
|
+
const extractor = options.extractorPath ?? fileURLToPath2(new URL("../tools/extract-model.lpr", import.meta.url));
|
|
291
|
+
const executable = path2.join(bin, "extract-model" + (process.platform === "win32" ? ".exe" : ""));
|
|
292
|
+
try {
|
|
293
|
+
await execute(options.fpcPath ?? "fpc", ["-Mobjfpc", "-Sh", "-FU" + bin, "-FE" + bin, extractor], { cwd: root, maxBuffer: 8 * 1024 * 1024 });
|
|
294
|
+
await execute(executable, [path2.resolve(options.acbrRoot), path2.join(cache, "raw-model.json"), process.platform === "win32" ? "win64" : "linux"], { cwd: root, maxBuffer: 8 * 1024 * 1024 });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
const e = error;
|
|
297
|
+
throw new Error("Extra\xE7\xE3o FCL-passrc falhou: " + (e.stderr ?? "") + (e.stdout ?? "") + e.message);
|
|
298
|
+
}
|
|
299
|
+
const raw = JSON.parse(await readFile5(path2.join(cache, "raw-model.json"), "utf8"));
|
|
300
|
+
const ir = buildModelIR(raw), sourceRevision = Number(options.revision);
|
|
301
|
+
if (!Number.isSafeInteger(sourceRevision) || sourceRevision < 1) throw new Error("Revis\xE3o SVN inv\xE1lida");
|
|
302
|
+
const sourceHash = options.sourceHash ?? await fingerprint(options.acbrRoot), contractHash = hash(stable({ sourceRevision, sourceHash, ir }));
|
|
303
|
+
let previous;
|
|
304
|
+
try {
|
|
305
|
+
previous = JSON.parse(await readFile5(path2.join(cache, "model.ir.json"), "utf8"));
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (error.code !== "ENOENT") throw error;
|
|
308
|
+
}
|
|
309
|
+
const report = compareContracts(previous, ir), reportPath = path2.join(cache, "changes.json"), manifestPath = path2.join(cache, "manifest.json");
|
|
310
|
+
const files = [path2.relative(root, path2.join(models, "models.ts")), path2.relative(root, path2.join(models, "index.ts")), path2.relative(root, path2.join(native, "AcbrModels.pas"))].map((f) => f.replaceAll("\\", "/"));
|
|
311
|
+
const content = [renderTypeScript(ir, sourceRevision, contractHash), renderClientIndex(), renderPascal(ir, sourceRevision, contractHash)];
|
|
312
|
+
const fileHashes = Object.fromEntries(files.map((file, index) => [file, hash(content[index])]));
|
|
313
|
+
await Promise.all([
|
|
314
|
+
...files.map((file, index) => writeFile4(path2.resolve(root, file), content[index], "utf8")),
|
|
315
|
+
writeFile4(path2.join(cache, "model.ir.json"), JSON.stringify(ir, null, 2) + "\n", "utf8"),
|
|
316
|
+
writeFile4(reportPath, JSON.stringify(report, null, 2) + "\n", "utf8"),
|
|
317
|
+
writeFile4(manifestPath, JSON.stringify({ schemaVersion: 1, sourceRevision, sourceHash, contractHash, root: ir.root, models: ir.types.length, files, fileHashes }, null, 2) + "\n", "utf8")
|
|
318
|
+
]);
|
|
319
|
+
return { contractHash, sourceHash, models: ir.types.length, reportPath, manifestPath };
|
|
320
|
+
}
|
|
321
|
+
var execute, bases, primitives, stable, hash, tsType;
|
|
322
|
+
var init_generator = __esm({
|
|
323
|
+
"src/generator.ts"() {
|
|
324
|
+
"use strict";
|
|
325
|
+
execute = promisify(execFile);
|
|
326
|
+
bases = /* @__PURE__ */ new Set(["tobject", "tpersistent", "tacbrobjectlist"]);
|
|
327
|
+
primitives = {
|
|
328
|
+
string: { kind: "string" },
|
|
329
|
+
ansistring: { kind: "string" },
|
|
330
|
+
unicodestring: { kind: "string" },
|
|
331
|
+
widestring: { kind: "string" },
|
|
332
|
+
boolean: { kind: "boolean" },
|
|
333
|
+
byte: { kind: "integer", min: 0, max: 255 },
|
|
334
|
+
word: { kind: "integer", min: 0, max: 65535 },
|
|
335
|
+
integer: { kind: "integer", min: -2147483648, max: 2147483647 },
|
|
336
|
+
longint: { kind: "integer", min: -2147483648, max: 2147483647 },
|
|
337
|
+
smallint: { kind: "integer", min: -32768, max: 32767 },
|
|
338
|
+
shortint: { kind: "integer", min: -128, max: 127 },
|
|
339
|
+
cardinal: { kind: "integer", min: 0, max: 4294967295 },
|
|
340
|
+
longword: { kind: "integer", min: 0, max: 4294967295 },
|
|
341
|
+
int64: { kind: "integer", min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER },
|
|
342
|
+
qword: { kind: "integer", min: 0, max: Number.MAX_SAFE_INTEGER },
|
|
343
|
+
double: { kind: "decimal" },
|
|
344
|
+
extended: { kind: "decimal" },
|
|
345
|
+
single: { kind: "decimal" },
|
|
346
|
+
currency: { kind: "decimal", scale: 4 },
|
|
347
|
+
tdatetime: { kind: "datetime" },
|
|
348
|
+
tdate: { kind: "datetime" },
|
|
349
|
+
ttime: { kind: "datetime" }
|
|
350
|
+
};
|
|
351
|
+
stable = (value) => JSON.stringify(value);
|
|
352
|
+
hash = (value) => createHash3("sha256").update(value).digest("hex");
|
|
353
|
+
tsType = (p) => p.kind === "object" ? p.ref + "Input" : p.kind === "list" ? p.ref + "Input" : p.kind === "enum" ? p.ref : p.kind === "integer" ? "number" : p.kind === "boolean" ? "boolean" : "string";
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// src/cli.ts
|
|
358
|
+
import { access as access4 } from "node:fs/promises";
|
|
359
|
+
import { resolve as resolve4 } from "node:path";
|
|
360
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
361
|
+
|
|
362
|
+
// src/config.ts
|
|
363
|
+
import { readFile, writeFile, mkdir, readdir, access } from "node:fs/promises";
|
|
364
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
365
|
+
var OFFICIAL_ACBR_URL = "https://svn.code.sf.net/p/acbr/code/trunk2";
|
|
366
|
+
var SOURCE_PARTS = ["Fontes", "Exemplos/ACBrDFe/Schemas/NFe", "Doctos/LICENSE.TXT"];
|
|
367
|
+
var DEFAULT = {
|
|
368
|
+
formatVersion: 1,
|
|
369
|
+
source: { url: OFFICIAL_ACBR_URL, revision: "HEAD", directory: ".acbr/source" },
|
|
370
|
+
generator: { outputDir: "." },
|
|
371
|
+
native: { project: "native/acbr-worker.lpr", outputDir: ".acbr/runtime", fpc: "fpc" }
|
|
372
|
+
};
|
|
373
|
+
function defaultConfig() {
|
|
374
|
+
return structuredClone(DEFAULT);
|
|
375
|
+
}
|
|
376
|
+
function record(value, name) {
|
|
377
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} deve ser um objeto.`);
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
function keys(value, allowed, name) {
|
|
381
|
+
const unexpected = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
382
|
+
if (unexpected.length) throw new Error(`Campo desconhecido em ${name}: ${unexpected.join(", ")}.`);
|
|
383
|
+
}
|
|
384
|
+
function string(value, name) {
|
|
385
|
+
if (typeof value !== "string" || !value.trim() || value.includes("\0")) throw new Error(`${name} deve ser um texto n\xE3o vazio.`);
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
function parseConfig(input, configPath) {
|
|
389
|
+
const root = record(input, "configura\xE7\xE3o");
|
|
390
|
+
keys(root, ["formatVersion", "source", "generator", "native"], "configura\xE7\xE3o");
|
|
391
|
+
if (root.formatVersion !== 1) throw new Error("formatVersion deve ser 1.");
|
|
392
|
+
const source = record(root.source, "source");
|
|
393
|
+
keys(source, ["url", "revision", "directory"], "source");
|
|
394
|
+
const generator = record(root.generator, "generator");
|
|
395
|
+
keys(generator, ["outputDir"], "generator");
|
|
396
|
+
const native = record(root.native, "native");
|
|
397
|
+
keys(native, ["project", "outputDir", "fpc", "lazarusRoot", "libraryFiles", "providerFiles"], "native");
|
|
398
|
+
const url = string(source.url, "source.url").replace(/\/+$/, "");
|
|
399
|
+
if (url !== OFFICIAL_ACBR_URL) throw new Error(`source.url deve apontar ao reposit\xF3rio oficial ${OFFICIAL_ACBR_URL}.`);
|
|
400
|
+
const revision2 = source.revision;
|
|
401
|
+
if (revision2 !== "HEAD" && (!Number.isSafeInteger(revision2) || Number(revision2) < 1)) throw new Error("source.revision deve ser HEAD ou uma revis\xE3o SVN positiva.");
|
|
402
|
+
const absoluteConfig = resolve(configPath);
|
|
403
|
+
const projectRoot = dirname(absoluteConfig);
|
|
404
|
+
const localPath = (value, label) => resolve(projectRoot, string(value, label));
|
|
405
|
+
const fpcInput = string(native.fpc, "native.fpc");
|
|
406
|
+
const fpc = isAbsolute(fpcInput) || /[\\/]/.test(fpcInput) ? resolve(projectRoot, fpcInput) : fpcInput;
|
|
407
|
+
let libraryFiles;
|
|
408
|
+
if (native.libraryFiles !== void 0) {
|
|
409
|
+
if (!Array.isArray(native.libraryFiles)) throw new Error("native.libraryFiles deve ser uma lista de caminhos.");
|
|
410
|
+
libraryFiles = native.libraryFiles.map((item, i) => localPath(item, `native.libraryFiles[${i}]`));
|
|
411
|
+
}
|
|
412
|
+
let providerFiles;
|
|
413
|
+
if (native.providerFiles !== void 0) {
|
|
414
|
+
if (!Array.isArray(native.providerFiles)) throw new Error("native.providerFiles deve ser uma lista de caminhos.");
|
|
415
|
+
providerFiles = native.providerFiles.map((item, i) => localPath(item, `native.providerFiles[${i}]`));
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
formatVersion: 1,
|
|
419
|
+
configPath: absoluteConfig,
|
|
420
|
+
projectRoot,
|
|
421
|
+
lockPath: resolve(projectRoot, "acbr.lock.json"),
|
|
422
|
+
source: { url, revision: revision2, directory: localPath(source.directory, "source.directory") },
|
|
423
|
+
generator: { outputDir: localPath(generator.outputDir, "generator.outputDir") },
|
|
424
|
+
native: {
|
|
425
|
+
project: localPath(native.project, "native.project"),
|
|
426
|
+
outputDir: localPath(native.outputDir, "native.outputDir"),
|
|
427
|
+
fpc,
|
|
428
|
+
...native.lazarusRoot !== void 0 ? { lazarusRoot: localPath(native.lazarusRoot, "native.lazarusRoot") } : {},
|
|
429
|
+
...libraryFiles !== void 0 ? { libraryFiles } : {},
|
|
430
|
+
...providerFiles !== void 0 ? { providerFiles } : {}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async function loadConfig(path3 = "acbr.config.json") {
|
|
435
|
+
const configPath = resolve(path3);
|
|
436
|
+
let contents;
|
|
437
|
+
try {
|
|
438
|
+
contents = await readFile(configPath, "utf8");
|
|
439
|
+
} catch (error) {
|
|
440
|
+
throw new Error(`N\xE3o foi poss\xEDvel ler ${configPath}. Execute acbr-node init.`, { cause: error });
|
|
441
|
+
}
|
|
442
|
+
let value;
|
|
443
|
+
try {
|
|
444
|
+
value = JSON.parse(contents);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
throw new Error(`JSON inv\xE1lido em ${configPath}.`, { cause: error });
|
|
447
|
+
}
|
|
448
|
+
return parseConfig(value, configPath);
|
|
449
|
+
}
|
|
450
|
+
async function initializeConfig(path3 = "acbr.config.json", revision2 = "HEAD", assetRoot) {
|
|
451
|
+
const configPath = resolve(path3);
|
|
452
|
+
const config = defaultConfig();
|
|
453
|
+
config.source.revision = revision2;
|
|
454
|
+
parseConfig(config, configPath);
|
|
455
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
456
|
+
try {
|
|
457
|
+
await access(configPath);
|
|
458
|
+
throw Object.assign(new Error(`EEXIST: configura\xE7\xE3o j\xE1 existe: ${configPath}`), { code: "EEXIST" });
|
|
459
|
+
} catch (error) {
|
|
460
|
+
if (error.code !== "ENOENT") throw error;
|
|
461
|
+
}
|
|
462
|
+
const resolved = parseConfig(config, configPath);
|
|
463
|
+
if (assetRoot) await copyInitAssets(assetRoot, resolved.projectRoot);
|
|
464
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
465
|
+
`, { flag: "wx" });
|
|
466
|
+
await mkdir(resolve(resolved.projectRoot, ".acbr/generated"), { recursive: true });
|
|
467
|
+
await mkdir(resolve(resolved.projectRoot, "generated/acbr"), { recursive: true });
|
|
468
|
+
return resolved;
|
|
469
|
+
}
|
|
470
|
+
async function copyInitAssets(assetRoot, targetRoot) {
|
|
471
|
+
const entries = [];
|
|
472
|
+
async function scan(current, target) {
|
|
473
|
+
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
474
|
+
if (entry.name === "generated" || entry.name.startsWith(".")) continue;
|
|
475
|
+
const source = resolve(current, entry.name);
|
|
476
|
+
const destination = resolve(target, entry.name);
|
|
477
|
+
if (entry.isDirectory()) await scan(source, destination);
|
|
478
|
+
else if (entry.isFile() && /\.(pas|lpr|inc|lpi)$/i.test(entry.name)) entries.push({ source, destination, content: await readFile(source) });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
await scan(resolve(assetRoot, "native"), resolve(targetRoot, "native"));
|
|
482
|
+
if (!entries.some((entry) => entry.source.endsWith("acbr-worker.lpr"))) throw new Error("Assets do pacote n\xE3o cont\xEAm native/acbr-worker.lpr.");
|
|
483
|
+
entries.push({ source: resolve(assetRoot, "tools/extract-model.lpr"), destination: resolve(targetRoot, "tools/extract-model.lpr"), content: await readFile(resolve(assetRoot, "tools/extract-model.lpr")) });
|
|
484
|
+
for (const entry of entries) {
|
|
485
|
+
try {
|
|
486
|
+
const existing = await readFile(entry.destination);
|
|
487
|
+
if (!existing.equals(entry.content)) throw new Error(`init preservou arquivo existente divergente: ${entry.destination}.`);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (error.code !== "ENOENT") throw error;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
for (const entry of entries) {
|
|
493
|
+
await mkdir(dirname(entry.destination), { recursive: true });
|
|
494
|
+
try {
|
|
495
|
+
await writeFile(entry.destination, entry.content, { flag: "wx" });
|
|
496
|
+
} catch (error) {
|
|
497
|
+
if (error.code !== "EEXIST") throw error;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/native-build.ts
|
|
503
|
+
import { copyFile as copyFile2, mkdir as mkdir3, readdir as readdir3, readFile as readFile3, rename as rename2, rm as rm2, stat as stat2, chmod, realpath, access as access3, writeFile as writeFile3 } from "node:fs/promises";
|
|
504
|
+
import { basename, dirname as dirname3, join as join2, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
505
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
506
|
+
import { fileURLToPath } from "node:url";
|
|
507
|
+
|
|
508
|
+
// src/source.ts
|
|
509
|
+
import { spawn } from "node:child_process";
|
|
510
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
511
|
+
import { createReadStream } from "node:fs";
|
|
512
|
+
import { access as access2, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, rename, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
513
|
+
import { dirname as dirname2, join, relative, resolve as resolve2, sep } from "node:path";
|
|
514
|
+
async function runCommand(command, args, options = {}) {
|
|
515
|
+
return new Promise((done, fail2) => {
|
|
516
|
+
const child = spawn(command, args, { cwd: options.cwd, env: options.env ?? process.env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, shell: false });
|
|
517
|
+
let stdout = "";
|
|
518
|
+
let stderr = "";
|
|
519
|
+
let overflow = false;
|
|
520
|
+
const maximum = options.maxOutputBytes ?? 16 * 1024 * 1024;
|
|
521
|
+
const append = (kind, data) => {
|
|
522
|
+
if (overflow) return;
|
|
523
|
+
if (kind === "stdout") stdout += data.toString("utf8");
|
|
524
|
+
else stderr += data.toString("utf8");
|
|
525
|
+
if (Buffer.byteLength(stdout) + Buffer.byteLength(stderr) > maximum) {
|
|
526
|
+
overflow = true;
|
|
527
|
+
child.kill();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
child.stdout.on("data", (data) => append("stdout", data));
|
|
531
|
+
child.stderr.on("data", (data) => append("stderr", data));
|
|
532
|
+
child.on("error", (error) => fail2(new Error(`N\xE3o foi poss\xEDvel executar ${command}: ${error.message}`, { cause: error })));
|
|
533
|
+
child.on("close", (code) => {
|
|
534
|
+
if (overflow) fail2(new Error(`Sa\xEDda de ${command} excedeu o limite de ${maximum} bytes.`));
|
|
535
|
+
else if (code !== 0) {
|
|
536
|
+
const diagnostic = (stderr || stdout).split("\n").slice(-80).join("\n").slice(-16e3);
|
|
537
|
+
fail2(Object.assign(new Error(`${command} terminou com c\xF3digo ${code}:
|
|
538
|
+
${diagnostic}`), { stdout, stderr }));
|
|
539
|
+
} else done({ stdout, stderr });
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async function fileHash(path3) {
|
|
544
|
+
const hash2 = createHash("sha256");
|
|
545
|
+
for await (const chunk of createReadStream(path3)) hash2.update(chunk);
|
|
546
|
+
return hash2.digest("hex");
|
|
547
|
+
}
|
|
548
|
+
async function snapshotSource(directory) {
|
|
549
|
+
const files = /* @__PURE__ */ Object.create(null);
|
|
550
|
+
async function scan(current) {
|
|
551
|
+
const entries = (await readdir2(current, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name, "en"));
|
|
552
|
+
for (const entry of entries) {
|
|
553
|
+
if (entry.name === ".svn") continue;
|
|
554
|
+
const path3 = join(current, entry.name);
|
|
555
|
+
if (entry.isSymbolicLink()) throw new Error(`Fonte ACBr cont\xE9m link simb\xF3lico: ${path3}.`);
|
|
556
|
+
if (entry.isDirectory()) await scan(path3);
|
|
557
|
+
else if (entry.isFile()) files[relative(directory, path3).split(sep).join("/")] = await fileHash(path3);
|
|
558
|
+
else throw new Error(`Fonte ACBr cont\xE9m arquivo especial: ${path3}.`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
await scan(resolve2(directory));
|
|
562
|
+
const sortedFiles = Object.fromEntries(Object.entries(files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
563
|
+
const treeHash = createHash("sha256").update(JSON.stringify(sortedFiles)).digest("hex");
|
|
564
|
+
return { treeHash, files: sortedFiles };
|
|
565
|
+
}
|
|
566
|
+
function validateLock(value) {
|
|
567
|
+
if (!value || typeof value !== "object") throw new Error("Lock ACBr inv\xE1lido.");
|
|
568
|
+
const lock = value;
|
|
569
|
+
if (lock.formatVersion !== 1 || !lock.source || !Number.isSafeInteger(lock.source.revision) || lock.source.revision < 1 || !Array.isArray(lock.source.parts) || !lock.source.files || typeof lock.source.files !== "object" || Array.isArray(lock.source.files) || !/^[a-f0-9]{64}$/.test(lock.source.treeHash)) throw new Error("Formato inv\xE1lido em acbr.lock.json.");
|
|
570
|
+
for (const [path3, hash2] of Object.entries(lock.source.files)) {
|
|
571
|
+
if (path3.startsWith("/") || path3.includes("\\") || path3.split("/").includes("..") || !/^[a-f0-9]{64}$/.test(hash2)) throw new Error("Caminho ou hash inv\xE1lido no lock ACBr.");
|
|
572
|
+
}
|
|
573
|
+
if (lock.source.parts.join("\n") !== SOURCE_PARTS.join("\n")) throw new Error("Partes ACBr divergentes no lock.");
|
|
574
|
+
return lock;
|
|
575
|
+
}
|
|
576
|
+
async function readSourceLock(config) {
|
|
577
|
+
let lock;
|
|
578
|
+
try {
|
|
579
|
+
lock = validateLock(JSON.parse(await readFile2(config.lockPath, "utf8")));
|
|
580
|
+
} catch (error) {
|
|
581
|
+
throw new Error(`N\xE3o foi poss\xEDvel ler ${config.lockPath}. Execute acbr-node source update.`, { cause: error });
|
|
582
|
+
}
|
|
583
|
+
if (lock.source.url !== config.source.url) throw new Error("URL ACBr do lock diverge da configura\xE7\xE3o.");
|
|
584
|
+
if (config.source.revision !== "HEAD" && lock.source.revision !== config.source.revision) throw new Error("Revis\xE3o ACBr do lock diverge da configura\xE7\xE3o. Execute source update explicitamente.");
|
|
585
|
+
return lock;
|
|
586
|
+
}
|
|
587
|
+
async function verifySource(config) {
|
|
588
|
+
try {
|
|
589
|
+
await access2(`${config.lockPath}.operation`);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (error.code === "ENOENT") return verifySourceContents(config);
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
throw new Error("Atualiza\xE7\xE3o de fontes ACBr em andamento. Aguarde o CLI concluir.");
|
|
595
|
+
}
|
|
596
|
+
async function verifySourceContents(config) {
|
|
597
|
+
const lock = await readSourceLock(config);
|
|
598
|
+
const snapshot = await snapshotSource(config.source.directory);
|
|
599
|
+
if (snapshot.treeHash !== lock.source.treeHash) {
|
|
600
|
+
const names = /* @__PURE__ */ new Set([...Object.keys(snapshot.files), ...Object.keys(lock.source.files)]);
|
|
601
|
+
const changed = [...names].filter((path3) => snapshot.files[path3] !== lock.source.files[path3]);
|
|
602
|
+
throw new Error(`Fontes ACBr divergem do lock (${changed.length} arquivo(s)): ${changed.slice(0, 10).join(", ")}. Preserve as altera\xE7\xF5es antes de atualizar.`);
|
|
603
|
+
}
|
|
604
|
+
return lock;
|
|
605
|
+
}
|
|
606
|
+
async function writeLock(config, snapshot, revision2) {
|
|
607
|
+
const lock = { formatVersion: 1, source: { url: config.source.url, revision: revision2, parts: [...SOURCE_PARTS], ...snapshot } };
|
|
608
|
+
await mkdir2(dirname2(config.lockPath), { recursive: true });
|
|
609
|
+
const temporary = `${config.lockPath}.${randomUUID()}.tmp`;
|
|
610
|
+
await writeFile2(temporary, `${JSON.stringify(lock, null, 2)}
|
|
611
|
+
`, { flag: "wx" });
|
|
612
|
+
try {
|
|
613
|
+
await rename(temporary, config.lockPath);
|
|
614
|
+
} finally {
|
|
615
|
+
await rm(temporary, { force: true });
|
|
616
|
+
}
|
|
617
|
+
return lock;
|
|
618
|
+
}
|
|
619
|
+
async function lockExistingSource(config, revision2) {
|
|
620
|
+
return withSourceOperation(config, () => performLockExistingSource(config, revision2));
|
|
621
|
+
}
|
|
622
|
+
async function performLockExistingSource(config, revision2) {
|
|
623
|
+
if (!Number.isSafeInteger(revision2) || revision2 < 1) throw new Error("Uma revis\xE3o SVN positiva \xE9 obrigat\xF3ria para registrar fontes existentes.");
|
|
624
|
+
try {
|
|
625
|
+
await access2(config.lockPath);
|
|
626
|
+
} catch {
|
|
627
|
+
return writeLock(config, await snapshotSource(config.source.directory), revision2);
|
|
628
|
+
}
|
|
629
|
+
throw new Error("O lock j\xE1 existe. Use source update para atualizar fontes e lock em conjunto.");
|
|
630
|
+
}
|
|
631
|
+
async function resolveSourceRevision(url, revision2, runner = runCommand) {
|
|
632
|
+
const { stdout } = await runner("svn", ["info", "--non-interactive", "--show-item", "revision", "-r", String(revision2), url]);
|
|
633
|
+
const resolved = Number(stdout.trim());
|
|
634
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1) throw new Error("SVN n\xE3o retornou uma revis\xE3o v\xE1lida.");
|
|
635
|
+
return resolved;
|
|
636
|
+
}
|
|
637
|
+
async function withSourceOperation(config, operation) {
|
|
638
|
+
const marker = `${config.lockPath}.operation`;
|
|
639
|
+
try {
|
|
640
|
+
await writeFile2(marker, `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
641
|
+
`, { flag: "wx" });
|
|
642
|
+
} catch (error) {
|
|
643
|
+
throw new Error("Outra opera\xE7\xE3o de fontes ACBr est\xE1 em andamento. Se o processo anterior terminou abruptamente, confira o PID do arquivo acbr.lock.json.operation antes de remov\xEA-lo.", { cause: error });
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
return await operation();
|
|
647
|
+
} finally {
|
|
648
|
+
await rm(marker, { force: true });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
async function updateSource(config, revision2 = "HEAD", runner = runCommand) {
|
|
652
|
+
return withSourceOperation(config, () => performUpdateSource(config, revision2, runner));
|
|
653
|
+
}
|
|
654
|
+
async function performUpdateSource(config, revision2, runner) {
|
|
655
|
+
const source = resolve2(config.source.directory);
|
|
656
|
+
if (source === config.projectRoot || source === dirname2(source)) throw new Error("O diret\xF3rio das fontes deve ser exclusivo e n\xE3o pode ser a raiz do projeto ou do sistema.");
|
|
657
|
+
let exists2 = false;
|
|
658
|
+
try {
|
|
659
|
+
const details = await stat(source);
|
|
660
|
+
exists2 = true;
|
|
661
|
+
if (!details.isDirectory()) throw new Error("O caminho das fontes n\xE3o \xE9 um diret\xF3rio.");
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (error.code !== "ENOENT") throw error;
|
|
664
|
+
}
|
|
665
|
+
if (exists2 && (await readdir2(source)).length) await verifySourceContents({ ...config, source: { ...config.source, revision: "HEAD" } });
|
|
666
|
+
const resolvedRevision = await resolveSourceRevision(config.source.url, revision2, runner);
|
|
667
|
+
const stage = join(dirname2(source), `.acbr-source-stage-${randomUUID()}`);
|
|
668
|
+
const backup = join(dirname2(source), `.acbr-source-backup-${randomUUID()}`);
|
|
669
|
+
await mkdir2(stage, { recursive: true });
|
|
670
|
+
let previousLock;
|
|
671
|
+
try {
|
|
672
|
+
previousLock = await readFile2(config.lockPath, "utf8");
|
|
673
|
+
} catch (error) {
|
|
674
|
+
if (error.code !== "ENOENT") throw error;
|
|
675
|
+
}
|
|
676
|
+
let backedUp = false;
|
|
677
|
+
let replaced = false;
|
|
678
|
+
let lockWritten = false;
|
|
679
|
+
try {
|
|
680
|
+
for (const part of SOURCE_PARTS) {
|
|
681
|
+
const destination = join(stage, ...part.split("/"));
|
|
682
|
+
await mkdir2(dirname2(destination), { recursive: true });
|
|
683
|
+
await runner("svn", ["export", "--non-interactive", "-q", "-r", String(resolvedRevision), `${config.source.url}/${part}`, destination], { maxOutputBytes: 4 * 1024 * 1024 });
|
|
684
|
+
}
|
|
685
|
+
const snapshot = await snapshotSource(stage);
|
|
686
|
+
if (exists2) {
|
|
687
|
+
await rename(source, backup);
|
|
688
|
+
backedUp = true;
|
|
689
|
+
}
|
|
690
|
+
await rename(stage, source);
|
|
691
|
+
replaced = true;
|
|
692
|
+
const lock = await writeLock(config, snapshot, resolvedRevision);
|
|
693
|
+
lockWritten = true;
|
|
694
|
+
await persistRevision(config, resolvedRevision);
|
|
695
|
+
config.source.revision = resolvedRevision;
|
|
696
|
+
if (backedUp) await rm(backup, { recursive: true, force: true }).catch(() => void 0);
|
|
697
|
+
return lock;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
if (lockWritten) {
|
|
700
|
+
if (previousLock === void 0) await rm(config.lockPath, { force: true });
|
|
701
|
+
else await writeFile2(config.lockPath, previousLock);
|
|
702
|
+
}
|
|
703
|
+
if (replaced) await rm(source, { recursive: true, force: true });
|
|
704
|
+
if (backedUp) await rename(backup, source);
|
|
705
|
+
throw error;
|
|
706
|
+
} finally {
|
|
707
|
+
await rm(stage, { recursive: true, force: true });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function persistRevision(config, revision2) {
|
|
711
|
+
const original = JSON.parse(await readFile2(config.configPath, "utf8"));
|
|
712
|
+
original.source.revision = revision2;
|
|
713
|
+
const temporary = `${config.configPath}.${randomUUID()}.tmp`;
|
|
714
|
+
await writeFile2(temporary, `${JSON.stringify(original, null, 2)}
|
|
715
|
+
`, { flag: "wx" });
|
|
716
|
+
try {
|
|
717
|
+
await rename(temporary, config.configPath);
|
|
718
|
+
} finally {
|
|
719
|
+
await rm(temporary, { force: true });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/native-build.ts
|
|
724
|
+
function runtimeDirectory(config) {
|
|
725
|
+
return join2(config.native.outputDir, `${process.platform}-${process.arch}`);
|
|
726
|
+
}
|
|
727
|
+
async function exists(path3) {
|
|
728
|
+
try {
|
|
729
|
+
await access3(path3);
|
|
730
|
+
return true;
|
|
731
|
+
} catch {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async function findLazarus(config) {
|
|
736
|
+
const candidates = config.native.lazarusRoot ? [config.native.lazarusRoot] : [process.env.LAZARUS_ROOT, "/usr/lib/lazarus/default", "/usr/lib/lazarus/3.0", "C:\\lazarus"].filter((path3) => Boolean(path3));
|
|
737
|
+
for (const candidate of candidates) if (await exists(join2(candidate, "components/lazutils"))) return realpath(candidate);
|
|
738
|
+
throw new Error("Lazarus n\xE3o encontrado. Defina native.lazarusRoot em acbr.config.json ou LAZARUS_ROOT.");
|
|
739
|
+
}
|
|
740
|
+
async function generatedManifest(config) {
|
|
741
|
+
const path3 = join2(config.generator.outputDir, ".acbr/generated/manifest.json");
|
|
742
|
+
let value;
|
|
743
|
+
try {
|
|
744
|
+
value = JSON.parse(await readFile3(path3, "utf8"));
|
|
745
|
+
} catch (error) {
|
|
746
|
+
throw new Error("Contrato gerado ausente ou inv\xE1lido. Execute acbr-node generate.", { cause: error });
|
|
747
|
+
}
|
|
748
|
+
if (!Number.isSafeInteger(value.sourceRevision) || !/^[a-f0-9]{64}$/.test(value.contractHash) || !/^[a-f0-9]{64}$/.test(value.sourceHash)) throw new Error("Manifesto gerado inv\xE1lido. Execute generate.");
|
|
749
|
+
if (!value.fileHashes || typeof value.fileHashes !== "object" || Array.isArray(value.fileHashes) || Object.keys(value.fileHashes).length !== 3 || ["generated/acbr/models.ts", "generated/acbr/index.ts", "native/generated/AcbrModels.pas"].some((name) => !Object.hasOwn(value.fileHashes, name))) throw new Error("Manifesto gerado n\xE3o cont\xE9m os hashes dos tr\xEAs arquivos. Execute generate.");
|
|
750
|
+
for (const [name, expected] of Object.entries(value.fileHashes)) {
|
|
751
|
+
const file = resolve3(config.generator.outputDir, name);
|
|
752
|
+
const location = relative2(config.generator.outputDir, file);
|
|
753
|
+
if (!name || name.startsWith("/") || name.includes("\\") || location === ".." || location.startsWith(`..${sep2}`) || !/^[a-f0-9]{64}$/.test(expected)) throw new Error("Caminho ou SHA256 inv\xE1lido no manifesto gerado.");
|
|
754
|
+
if (await fileHash(file) !== expected) throw new Error(`Arquivo gerado diverge do manifesto: ${name}. Execute generate para restaurar o contrato.`);
|
|
755
|
+
}
|
|
756
|
+
return value;
|
|
757
|
+
}
|
|
758
|
+
async function subdirectories(path3) {
|
|
759
|
+
const result = [path3];
|
|
760
|
+
for (const entry of (await readdir3(path3, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
761
|
+
if (entry.isDirectory()) result.push(...await subdirectories(join2(path3, entry.name)));
|
|
762
|
+
}
|
|
763
|
+
return result;
|
|
764
|
+
}
|
|
765
|
+
var REQUIRED_DYNAMIC = ["libssl.so.3", "libcrypto.so.3", "libxml2.so.2"];
|
|
766
|
+
var CORE_SYSTEM = /^(?:lib(?:c|m|pthread|dl|rt|resolv|anl|util)\.so(?:\.|$)|ld-linux|linux-vdso)/;
|
|
767
|
+
async function copyLinuxLibraries(stage) {
|
|
768
|
+
const { stdout } = await runCommand("ldconfig", ["-p"]);
|
|
769
|
+
const libraries = /* @__PURE__ */ new Map();
|
|
770
|
+
for (const name of REQUIRED_DYNAMIC) {
|
|
771
|
+
const line = stdout.split("\n").find((value) => value.trim().startsWith(`${name} `) && value.includes("x86-64"));
|
|
772
|
+
const path3 = line?.split("=>")[1]?.trim();
|
|
773
|
+
if (!path3) throw new Error(`Depend\xEAncia din\xE2mica ausente: ${name}. Instale OpenSSL 3 e libxml2 compat\xEDveis.`);
|
|
774
|
+
libraries.set(name, await realpath(path3));
|
|
775
|
+
}
|
|
776
|
+
const modules = "/usr/lib/x86_64-linux-gnu/ossl-modules";
|
|
777
|
+
if (!await exists(join2(modules, "legacy.so"))) throw new Error("Provider legacy OpenSSL 3 ausente. Instale o pacote libssl3/libssl3t64 completo.");
|
|
778
|
+
libraries.set("ossl-modules/legacy.so", await realpath(join2(modules, "legacy.so")));
|
|
779
|
+
const systemDependencies = /* @__PURE__ */ new Set();
|
|
780
|
+
const inspected = /* @__PURE__ */ new Set();
|
|
781
|
+
while (inspected.size < libraries.size) {
|
|
782
|
+
const [name, path3] = [...libraries].find(([name2]) => !inspected.has(name2));
|
|
783
|
+
inspected.add(name);
|
|
784
|
+
const dependencies = await runCommand("ldd", [path3]);
|
|
785
|
+
for (const line of dependencies.stdout.split("\n")) {
|
|
786
|
+
if (line.includes("not found")) throw new Error(`Depend\xEAncia transitiva ausente: ${line.trim()}.`);
|
|
787
|
+
const match = /^\s*(\S+)\s+=>\s+(\S+)/.exec(line);
|
|
788
|
+
if (match) {
|
|
789
|
+
const dependency = match[1];
|
|
790
|
+
const file = match[2];
|
|
791
|
+
if (CORE_SYSTEM.test(dependency)) systemDependencies.add(dependency);
|
|
792
|
+
else if (!libraries.has(dependency)) libraries.set(dependency, await realpath(file));
|
|
793
|
+
} else {
|
|
794
|
+
const direct = /^\s*(\/\S+)/.exec(line)?.[1];
|
|
795
|
+
if (direct) systemDependencies.add(basename(direct));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
const licenseDir = join2(stage, "licenses");
|
|
800
|
+
await mkdir3(licenseDir, { recursive: true });
|
|
801
|
+
const provenance = {};
|
|
802
|
+
for (const [name, path3] of libraries) {
|
|
803
|
+
const destination = name.startsWith("ossl-modules/") ? join2(stage, name) : join2(stage, "lib", name);
|
|
804
|
+
await mkdir3(dirname3(destination), { recursive: true });
|
|
805
|
+
await copyFile2(path3, destination);
|
|
806
|
+
const aliases = name === "libssl.so.3" ? ["libssl.so"] : name === "libcrypto.so.3" ? ["libcrypto.so"] : name === "libxml2.so.2" ? ["libxml2.so"] : [];
|
|
807
|
+
for (const alias of aliases) await copyFile2(path3, join2(stage, "lib", alias));
|
|
808
|
+
let owner;
|
|
809
|
+
try {
|
|
810
|
+
owner = (await runCommand("dpkg-query", ["-S", path3])).stdout.split("\n")[0].split(": ")[0];
|
|
811
|
+
} catch (error) {
|
|
812
|
+
throw new Error(`N\xE3o foi poss\xEDvel identificar pacote/licen\xE7a de ${path3}. Build requer Debian/Ubuntu com depend\xEAncias registradas pelo dpkg.`, { cause: error });
|
|
813
|
+
}
|
|
814
|
+
const pkg = owner.split(":")[0];
|
|
815
|
+
const copyright = `/usr/share/doc/${pkg}/copyright`;
|
|
816
|
+
if (!await exists(copyright)) throw new Error(`Licen\xE7a da depend\xEAncia ${name} n\xE3o encontrada: ${copyright}.`);
|
|
817
|
+
await copyFile2(copyright, join2(licenseDir, `${name.replaceAll("/", "-")}.txt`));
|
|
818
|
+
const source = (await runCommand("dpkg-query", ["-W", "-f=${source:Package}|${source:Version}|${Version}", owner])).stdout.trim().split("|");
|
|
819
|
+
provenance[name] = {
|
|
820
|
+
binaryPackage: owner,
|
|
821
|
+
binaryVersion: source[2],
|
|
822
|
+
sourcePackage: source[0],
|
|
823
|
+
sourceVersion: source[1],
|
|
824
|
+
sourceUrl: `https://launchpad.net/ubuntu/+source/${encodeURIComponent(source[0])}/${encodeURIComponent(source[1])}`,
|
|
825
|
+
copyright: `licenses/${name.replaceAll("/", "-")}.txt`
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
await mkdir3(join2(stage, "sources"), { recursive: true });
|
|
829
|
+
await writeFile3(join2(stage, "sources/native-dependencies.json"), `${JSON.stringify(provenance, null, 2)}
|
|
830
|
+
`);
|
|
831
|
+
await writeFile3(join2(stage, "openssl.cnf"), "openssl_conf = openssl_init\n[openssl_init]\nproviders = provider_sect\n[provider_sect]\ndefault = default_sect\nlegacy = legacy_sect\n[default_sect]\nactivate = 1\n[legacy_sect]\nactivate = 1\n");
|
|
832
|
+
return [...systemDependencies].sort();
|
|
833
|
+
}
|
|
834
|
+
async function copyExplicitLibraries(config, stage) {
|
|
835
|
+
if (!config.native.libraryFiles?.length) throw new Error("Windows exige native.libraryFiles com DLLs OpenSSL 3/libxml2 e arquivos .LICENSE adjacentes.");
|
|
836
|
+
await mkdir3(join2(stage, "lib"), { recursive: true });
|
|
837
|
+
await mkdir3(join2(stage, "licenses"), { recursive: true });
|
|
838
|
+
const sources = {};
|
|
839
|
+
const dependencyDirectories = new Set([...config.native.libraryFiles ?? [], ...config.native.providerFiles ?? []].map(dirname3));
|
|
840
|
+
for (const directory of dependencyDirectories) {
|
|
841
|
+
for (const entry of await readdir3(directory)) {
|
|
842
|
+
if (entry.endsWith(".LICENSE")) await copyFile2(join2(directory, entry), join2(stage, "licenses", entry));
|
|
843
|
+
else if (entry === "SOURCE.json") {
|
|
844
|
+
await mkdir3(join2(stage, "sources"), { recursive: true });
|
|
845
|
+
await copyFile2(join2(directory, entry), join2(stage, "sources", `${basename(directory)}-SOURCE.json`));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
for (const [directory, files] of [["lib", config.native.libraryFiles], ["ossl-modules", config.native.providerFiles ?? []]]) {
|
|
850
|
+
await mkdir3(join2(stage, directory), { recursive: true });
|
|
851
|
+
for (const source of files) {
|
|
852
|
+
await copyFile2(source, join2(stage, directory, basename(source)));
|
|
853
|
+
await copyFile2(`${source}.LICENSE`, join2(stage, "licenses", `${basename(source)}.txt`));
|
|
854
|
+
if (await exists(`${source}.SOURCE.json`)) sources[`${directory}/${basename(source)}`] = JSON.parse(await readFile3(`${source}.SOURCE.json`, "utf8"));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (!config.native.providerFiles?.some((path3) => basename(path3).toLowerCase() === "legacy.dll")) throw new Error("Windows requer native.providerFiles com o provider legacy.dll de OpenSSL 3 e licen\xE7a adjacente.");
|
|
858
|
+
await mkdir3(join2(stage, "sources"), { recursive: true });
|
|
859
|
+
await writeFile3(join2(stage, "sources/native-dependencies.json"), `${JSON.stringify(sources, null, 2)}
|
|
860
|
+
`);
|
|
861
|
+
await writeFile3(join2(stage, "openssl.cnf"), "openssl_conf = openssl_init\n[openssl_init]\nproviders = provider_sect\n[provider_sect]\ndefault = default_sect\nlegacy = legacy_sect\n[default_sect]\nactivate = 1\n[legacy_sect]\nactivate = 1\n");
|
|
862
|
+
}
|
|
863
|
+
async function buildNative(config, options = {}) {
|
|
864
|
+
if (!["linux", "win32"].includes(process.platform) || process.arch !== "x64") throw new Error("Build inicial suporta Linux x64 e Windows x64.");
|
|
865
|
+
const lock = await verifySource(config);
|
|
866
|
+
const contract = await generatedManifest(config);
|
|
867
|
+
if (contract.sourceRevision !== lock.source.revision || contract.sourceHash !== lock.source.treeHash) throw new Error("Contrato gerado n\xE3o corresponde \xE0s fontes travadas. Execute generate.");
|
|
868
|
+
const lazarus = await findLazarus(config);
|
|
869
|
+
const source = config.source.directory;
|
|
870
|
+
const target = options.testOnly ? join2(config.projectRoot, ".acbr/test-runtime", `${process.platform}-${process.arch}`) : runtimeDirectory(config);
|
|
871
|
+
const stage = join2(dirname3(target), `.runtime-stage-${randomUUID2()}`);
|
|
872
|
+
const units = join2(stage, ".units");
|
|
873
|
+
await mkdir3(units, { recursive: true });
|
|
874
|
+
const executable = process.platform === "win32" ? "acbr-worker.exe" : "acbr-worker";
|
|
875
|
+
try {
|
|
876
|
+
const cpu = (await runCommand(config.native.fpc, ["-iTP"])).stdout.trim();
|
|
877
|
+
if (cpu !== "x86_64") throw new Error(`FPC deve compilar x86_64, encontrado ${cpu}.`);
|
|
878
|
+
const targetOs = process.platform === "win32" ? "win64" : "linux";
|
|
879
|
+
const fontPaths = await subdirectories(join2(source, "Fontes"));
|
|
880
|
+
const lazPaths = [join2(lazarus, "components/lazutils/lib", `x86_64-${targetOs}`), join2(lazarus, "lcl/units", `x86_64-${targetOs}`), join2(lazarus, "lcl/units", `x86_64-${targetOs}`, "nogui"), join2(lazarus, "packager/units", `x86_64-${targetOs}`)];
|
|
881
|
+
const searchPaths = [dirname3(config.native.project), join2(config.generator.outputDir, "native/generated"), ...fontPaths, ...lazPaths];
|
|
882
|
+
const args = [
|
|
883
|
+
"-vew",
|
|
884
|
+
"-Mdelphi",
|
|
885
|
+
"-Scghi",
|
|
886
|
+
"-O2",
|
|
887
|
+
"-g-",
|
|
888
|
+
"-dNOGUI",
|
|
889
|
+
"-dNOREPORT",
|
|
890
|
+
...options.testOnly ? ["-dACBR_NODE_TEST"] : [],
|
|
891
|
+
`-FU${units}`,
|
|
892
|
+
`-FE${stage}`,
|
|
893
|
+
`-o${executable}`,
|
|
894
|
+
...searchPaths.map((path3) => `-Fu${path3}`),
|
|
895
|
+
...fontPaths.map((path3) => `-Fi${path3}`),
|
|
896
|
+
config.native.project
|
|
897
|
+
];
|
|
898
|
+
const responseFile = join2(stage, ".compiler.rsp");
|
|
899
|
+
if (args.some((arg) => /[\"\r\n]/.test(arg))) throw new Error("Caminhos do build n\xE3o podem conter aspas ou quebras de linha.");
|
|
900
|
+
await writeFile3(responseFile, args.slice(0, -1).join("\n") + "\n");
|
|
901
|
+
const logPath = join2(config.projectRoot, ".acbr/build.log");
|
|
902
|
+
try {
|
|
903
|
+
const compilation = await runCommand(config.native.fpc, [`@${responseFile}`, config.native.project], { cwd: config.projectRoot, maxOutputBytes: 32 * 1024 * 1024 });
|
|
904
|
+
await writeFile3(logPath, compilation.stdout + compilation.stderr);
|
|
905
|
+
} catch (error) {
|
|
906
|
+
const compilation = error;
|
|
907
|
+
await writeFile3(logPath, (compilation.stdout ?? "") + (compilation.stderr ?? compilation.message));
|
|
908
|
+
throw new Error(`${compilation.message}
|
|
909
|
+
Log completo: ${logPath}`, { cause: compilation });
|
|
910
|
+
}
|
|
911
|
+
const systemDependencies = process.platform === "linux" ? await copyLinuxLibraries(stage) : (await copyExplicitLibraries(config, stage), []);
|
|
912
|
+
const schemas = join2(source, "Exemplos/ACBrDFe/Schemas/NFe");
|
|
913
|
+
await copyTree(schemas, join2(stage, "schemas"));
|
|
914
|
+
const services = join2(source, "Fontes/ACBrDFe/ACBrNFe/ACBrNFeServicos.ini");
|
|
915
|
+
await copyFile2(services, join2(stage, "ACBrNFeServicos.ini"));
|
|
916
|
+
await mkdir3(join2(stage, "licenses"), { recursive: true });
|
|
917
|
+
await copyFile2(join2(source, "Doctos/LICENSE.TXT"), join2(stage, "licenses/ACBr-LICENSE.TXT"));
|
|
918
|
+
await mkdir3(join2(stage, "sources/native"), { recursive: true });
|
|
919
|
+
await copyNativeSources(dirname3(config.native.project), join2(stage, "sources/native"));
|
|
920
|
+
await copyFile2(join2(config.generator.outputDir, "native/generated/AcbrModels.pas"), join2(stage, "sources/native/AcbrModels.pas"));
|
|
921
|
+
for (const name of ["fp-compiler-3.2.2", "fp-units-rtl-3.2.2", "fpc"]) {
|
|
922
|
+
const license = `/usr/share/doc/${name}/copyright`;
|
|
923
|
+
if (await exists(license)) {
|
|
924
|
+
await copyFile2(license, join2(stage, "licenses/FPC-copyright.txt"));
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
await copyCompilerLicenses(config, stage, lazarus);
|
|
929
|
+
const archive = join2(stage, "sources/acbr-source.tar.gz");
|
|
930
|
+
const tarArgs = process.platform === "linux" ? ["--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", "-I", "gzip -n"] : [];
|
|
931
|
+
await runCommand("tar", [...tarArgs, process.platform === "linux" ? "-cf" : "-czf", archive, "-C", source, "Fontes", "Exemplos/ACBrDFe/Schemas/NFe", "Doctos/LICENSE.TXT"]);
|
|
932
|
+
await writeFile3(join2(stage, "sources/build.json"), `${JSON.stringify({
|
|
933
|
+
sourceRevision: lock.source.revision,
|
|
934
|
+
sourceUrl: lock.source.url,
|
|
935
|
+
sourceTreeHash: lock.source.treeHash,
|
|
936
|
+
compiler: (await runCommand(config.native.fpc, ["-iV"])).stdout.trim(),
|
|
937
|
+
lazarusRoot: lazarus,
|
|
938
|
+
lazarusVersion: basename(lazarus),
|
|
939
|
+
compilerSourceUrl: "https://gitlab.com/freepascal.org/fpc/source/-/tree/release_3_2_2",
|
|
940
|
+
testOnly: options.testOnly ?? false,
|
|
941
|
+
platform: process.platform,
|
|
942
|
+
arch: process.arch,
|
|
943
|
+
flags: ["-Mdelphi", "-Scghi", "-O2", "-dNOGUI", "-dNOREPORT"],
|
|
944
|
+
sourceParts: lock.source.parts
|
|
945
|
+
}, null, 2)}
|
|
946
|
+
`);
|
|
947
|
+
await rm2(units, { recursive: true, force: true });
|
|
948
|
+
await rm2(responseFile, { force: true });
|
|
949
|
+
if (process.platform !== "win32") await chmod(join2(stage, executable), 493);
|
|
950
|
+
const currentGenerated = await generatedManifest(config);
|
|
951
|
+
if (currentGenerated.contractHash !== contract.contractHash || currentGenerated.sourceHash !== contract.sourceHash || Object.entries(contract.fileHashes).some(([name, hash2]) => currentGenerated.fileHashes[name] !== hash2)) throw new Error("Arquivos gerados mudaram durante a compila\xE7\xE3o. Execute build novamente.");
|
|
952
|
+
const currentSource = await verifySource(config);
|
|
953
|
+
if (currentSource.source.revision !== lock.source.revision || currentSource.source.treeHash !== lock.source.treeHash) throw new Error("Fontes ACBr foram atualizadas durante a compila\xE7\xE3o. Execute generate e build novamente.");
|
|
954
|
+
const files = (await snapshotSource(stage)).files;
|
|
955
|
+
const manifest = {
|
|
956
|
+
formatVersion: 1,
|
|
957
|
+
sourceRevision: lock.source.revision,
|
|
958
|
+
contractHash: contract.contractHash,
|
|
959
|
+
platform: process.platform,
|
|
960
|
+
arch: process.arch,
|
|
961
|
+
executable,
|
|
962
|
+
files,
|
|
963
|
+
generatedFileHashes: contract.fileHashes,
|
|
964
|
+
systemDependencies,
|
|
965
|
+
...options.testOnly ? { testOnly: true } : {}
|
|
966
|
+
};
|
|
967
|
+
await writeFile3(join2(stage, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
968
|
+
`);
|
|
969
|
+
await replaceRuntime(stage, target);
|
|
970
|
+
return manifest;
|
|
971
|
+
} finally {
|
|
972
|
+
await rm2(stage, { recursive: true, force: true });
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
async function copyNativeSources(source, target) {
|
|
976
|
+
await mkdir3(target, { recursive: true });
|
|
977
|
+
for (const entry of await readdir3(source, { withFileTypes: true })) {
|
|
978
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) await copyNativeSources(join2(source, entry.name), join2(target, entry.name));
|
|
979
|
+
else if (entry.isFile() && /\.(pas|lpr|inc|lpi)$/i.test(entry.name)) await copyFile2(join2(source, entry.name), join2(target, entry.name));
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
async function copyTree(source, target) {
|
|
983
|
+
await mkdir3(target, { recursive: true });
|
|
984
|
+
for (const entry of await readdir3(source, { withFileTypes: true })) {
|
|
985
|
+
if (entry.isSymbolicLink()) throw new Error(`Arquivo de distribui\xE7\xE3o n\xE3o pode ser link: ${entry.name}.`);
|
|
986
|
+
if (entry.isDirectory()) await copyTree(join2(source, entry.name), join2(target, entry.name));
|
|
987
|
+
else if (entry.isFile()) await copyFile2(join2(source, entry.name), join2(target, entry.name));
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
async function replaceRuntime(stage, target) {
|
|
991
|
+
const backup = `${target}.${randomUUID2()}.backup`;
|
|
992
|
+
const hadTarget = await exists(target);
|
|
993
|
+
if (hadTarget) await rename2(target, backup);
|
|
994
|
+
try {
|
|
995
|
+
await rename2(stage, target);
|
|
996
|
+
} catch (error) {
|
|
997
|
+
if (hadTarget) await rename2(backup, target);
|
|
998
|
+
throw error;
|
|
999
|
+
}
|
|
1000
|
+
if (hadTarget) await rm2(backup, { recursive: true, force: true });
|
|
1001
|
+
}
|
|
1002
|
+
async function checkBuild(config) {
|
|
1003
|
+
const lock = await verifySource(config);
|
|
1004
|
+
const generated = await generatedManifest(config);
|
|
1005
|
+
const directory = runtimeDirectory(config);
|
|
1006
|
+
const manifest = JSON.parse(await readFile3(join2(directory, "manifest.json"), "utf8"));
|
|
1007
|
+
if (manifest.formatVersion !== 1 || manifest.platform !== process.platform || manifest.arch !== process.arch || manifest.sourceRevision !== lock.source.revision || generated.sourceHash !== lock.source.treeHash || manifest.contractHash !== generated.contractHash || !manifest.generatedFileHashes || Object.entries(generated.fileHashes).some(([name, hash2]) => manifest.generatedFileHashes[name] !== hash2) || typeof manifest.executable !== "string" || !manifest.files || !Object.hasOwn(manifest.files, manifest.executable)) throw new Error("Runtime diverge das fontes ou contrato. Execute build.");
|
|
1008
|
+
for (const [path3, expected] of Object.entries(manifest.files)) {
|
|
1009
|
+
if (path3.startsWith("/") || path3.includes("\\") || path3.split("/").includes("..") || !/^[a-f0-9]{64}$/.test(expected)) throw new Error(`Caminho/hash inv\xE1lido no runtime: ${path3}.`);
|
|
1010
|
+
if (await fileHash(join2(directory, path3)) !== expected) throw new Error(`Hash inv\xE1lido no runtime: ${path3}.`);
|
|
1011
|
+
}
|
|
1012
|
+
return manifest;
|
|
1013
|
+
}
|
|
1014
|
+
async function doctor(config) {
|
|
1015
|
+
const items = [{ name: "Node.js", ok: Number(process.versions.node.split(".")[0]) > 22 || Number(process.versions.node.split(".")[0]) === 22 && Number(process.versions.node.split(".")[1]) >= 12, detail: process.versions.node }];
|
|
1016
|
+
for (const [name, command, args] of [["SVN", "svn", ["--version", "--quiet"]], ["Free Pascal", config?.native.fpc ?? "fpc", ["-iV"]]]) {
|
|
1017
|
+
try {
|
|
1018
|
+
const result = await runCommand(command, [...args]);
|
|
1019
|
+
items.push({ name, ok: true, detail: result.stdout.trim() });
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
items.push({ name, ok: false, detail: error.message });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (config) {
|
|
1025
|
+
try {
|
|
1026
|
+
items.push({ name: "Lazarus", ok: true, detail: await findLazarus(config) });
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
items.push({ name: "Lazarus", ok: false, detail: error.message });
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
const lock = await verifySource(config);
|
|
1032
|
+
items.push({ name: "Fontes ACBr", ok: true, detail: `revis\xE3o ${lock.source.revision}, ${lock.source.treeHash}` });
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
items.push({ name: "Fontes ACBr", ok: false, detail: error.message });
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (process.platform === "linux") {
|
|
1038
|
+
try {
|
|
1039
|
+
const result = await runCommand("ldconfig", ["-p"]);
|
|
1040
|
+
const missing = REQUIRED_DYNAMIC.filter((name) => !result.stdout.includes(name));
|
|
1041
|
+
items.push({ name: "OpenSSL/libxml2 din\xE2micos", ok: missing.length === 0, detail: missing.length ? `Ausentes: ${missing.join(", ")}` : REQUIRED_DYNAMIC.join(", ") });
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
items.push({ name: "OpenSSL/libxml2 din\xE2micos", ok: false, detail: error.message });
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return items;
|
|
1047
|
+
}
|
|
1048
|
+
async function copyCompilerLicenses(config, stage, lazarus) {
|
|
1049
|
+
const packagedLazarus = fileURLToPath(new URL("../docs/licenses/lazarus/", import.meta.url));
|
|
1050
|
+
const packagedFpc = fileURLToPath(new URL("../docs/licenses/fpc/", import.meta.url));
|
|
1051
|
+
const notices = {};
|
|
1052
|
+
async function copyNotice(name, candidates2) {
|
|
1053
|
+
for (const source of candidates2) if (await exists(source)) {
|
|
1054
|
+
await copyFile2(source, join2(stage, "licenses", name));
|
|
1055
|
+
notices[name] = { source, sha256: await fileHash(source) };
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
throw new Error(`Notice obrigat\xF3rio ausente: ${name}.`);
|
|
1059
|
+
}
|
|
1060
|
+
for (const name of ["COPYING.txt", "COPYING.LGPL.txt", "COPYING.modifiedLGPL.txt"]) {
|
|
1061
|
+
await copyNotice(`Lazarus-${name}`, [join2(lazarus, name), join2(lazarus, "docs", name), join2(packagedLazarus, name)]);
|
|
1062
|
+
}
|
|
1063
|
+
if (await exists(join2(packagedLazarus, "ORIGIN.json"))) await copyFile2(join2(packagedLazarus, "ORIGIN.json"), join2(stage, "licenses/Lazarus-notice-origin.json"));
|
|
1064
|
+
const fpcRoot = resolve3(dirname3(config.native.fpc), "../..");
|
|
1065
|
+
const sourceRoots = [join2(fpcRoot, "source"), join2(lazarus, "fpc/3.2.2/source"), "/usr/share/fpcsrc/3.2.2"];
|
|
1066
|
+
for (const [name, sourceRelative] of [["COPYING.GPL.txt", "compiler/COPYING.txt"], ["COPYING.LGPL.txt", "rtl/COPYING.txt"], ["COPYING.FPC", "rtl/COPYING.FPC"]]) {
|
|
1067
|
+
await copyNotice(`FPC-${name}`, [...sourceRoots.map((directory) => join2(directory, sourceRelative)), join2(packagedFpc, name)]);
|
|
1068
|
+
}
|
|
1069
|
+
if (await exists(join2(packagedFpc, "ORIGIN.json"))) await copyFile2(join2(packagedFpc, "ORIGIN.json"), join2(stage, "licenses/FPC-notice-origin.json"));
|
|
1070
|
+
const candidates = [join2(fpcRoot, "doc"), fpcRoot, join2(lazarus, "fpc"), "/usr/share/doc/fp-compiler-3.2.2", "/usr/share/doc/fp-units-rtl-3.2.2"];
|
|
1071
|
+
for (const directory of candidates) {
|
|
1072
|
+
if (!await exists(directory)) continue;
|
|
1073
|
+
for (const name of await readdir3(directory)) {
|
|
1074
|
+
if (/^(?:copying|license|copyright)/i.test(name) && (await stat2(join2(directory, name))).isFile()) await copyFile2(join2(directory, name), join2(stage, "licenses", `FPC-${basename(directory)}-${name}`));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
await writeFile3(join2(stage, "licenses/toolchain-notices.json"), `${JSON.stringify(notices, null, 2)}
|
|
1078
|
+
`);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/runtime.ts
|
|
1082
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1083
|
+
import { readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
1084
|
+
import path from "node:path";
|
|
1085
|
+
import { createRequire } from "node:module";
|
|
1086
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
1087
|
+
import { TextDecoder } from "node:util";
|
|
1088
|
+
|
|
1089
|
+
// src/types.ts
|
|
1090
|
+
var ErroAcbrNode = class extends Error {
|
|
1091
|
+
constructor(codigo, mensagem, resultadoDesconhecido = false, xmlBase64) {
|
|
1092
|
+
super(mensagem);
|
|
1093
|
+
this.codigo = codigo;
|
|
1094
|
+
this.resultadoDesconhecido = resultadoDesconhecido;
|
|
1095
|
+
this.xmlBase64 = xmlBase64;
|
|
1096
|
+
}
|
|
1097
|
+
name = "ErroAcbrNode";
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
// src/runtime.ts
|
|
1101
|
+
var STATES = /* @__PURE__ */ new Set(["DENEGADO", "XML_GERADO", "VALIDADO", "ASSINADO", "AUTORIZADO", "REJEITADO", "PENDENTE", "INDETERMINADO", "CONSULTADO", "CANCELADO", "EVENTO_REGISTRADO", "INUTILIZADO", "ERRO"]);
|
|
1102
|
+
var UFS = new Set("AC AL AP AM BA CE DF ES GO MA MT MS MG PA PB PR PE PI RJ RN RS RO RR SC SP SE TO".split(" "));
|
|
1103
|
+
var MAX_BYTES = 8 * 1024 * 1024;
|
|
1104
|
+
var plain = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1105
|
+
var validContract = (v) => plain(v) && Number.isInteger(v.sourceRevision) && Number(v.sourceRevision) > 0 && typeof v.contractHash === "string" && /^[a-f0-9]{64}$/.test(v.contractHash);
|
|
1106
|
+
var fail = (code, message, req, unknown = false) => new ErroAcbrNode(code, message, unknown, req?.comando === "transmitir" ? req.xmlBase64 : void 0);
|
|
1107
|
+
function confined(root, relative3) {
|
|
1108
|
+
const file = path.resolve(root, relative3), part = path.relative(root, file);
|
|
1109
|
+
if (!relative3 || path.isAbsolute(relative3) || part === ".." || part.startsWith(".." + path.sep)) throw fail("RUNTIME_INVALIDO", "Caminho inv\xE1lido no runtime.");
|
|
1110
|
+
return file;
|
|
1111
|
+
}
|
|
1112
|
+
async function resolveRuntime(directory, contract) {
|
|
1113
|
+
if (!validContract(contract)) throw fail("CONTRATO_INVALIDO", "Contrato gerado inv\xE1lido.");
|
|
1114
|
+
let root = directory;
|
|
1115
|
+
if (!root) {
|
|
1116
|
+
if (!["linux", "win32"].includes(process.platform) || process.arch !== "x64") throw fail("PLATAFORMA_NAO_SUPORTADA", "Esta vers\xE3o exige Linux ou Windows x64.");
|
|
1117
|
+
try {
|
|
1118
|
+
const require2 = createRequire(typeof __filename !== "undefined" ? __filename : import.meta.url);
|
|
1119
|
+
root = path.join(path.dirname(require2.resolve("cosmemilton-acbr-node-" + process.platform + "-x64/package.json")), "runtime");
|
|
1120
|
+
} catch {
|
|
1121
|
+
throw fail("RUNTIME_AUSENTE", "Instale o pacote de runtime da plataforma ou informe runtimeDirectory.");
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
root = path.resolve(root);
|
|
1125
|
+
let m;
|
|
1126
|
+
try {
|
|
1127
|
+
m = JSON.parse(await readFile4(path.join(root, "manifest.json"), "utf8"));
|
|
1128
|
+
} catch {
|
|
1129
|
+
throw fail("RUNTIME_AUSENTE", "Manifesto do runtime n\xE3o encontrado.");
|
|
1130
|
+
}
|
|
1131
|
+
if (!validContract(m) || m.formatVersion !== 1 || !plain(m.files) || typeof m.executable !== "string" || m.platform !== process.platform || m.arch !== process.arch) throw fail("RUNTIME_INVALIDO", "Manifesto ou plataforma do runtime inv\xE1lidos.");
|
|
1132
|
+
if (m.sourceRevision !== contract.sourceRevision || m.contractHash !== contract.contractHash) throw fail("CONTRATO_DIVERGENTE", "Cliente e motor foram gerados de contratos diferentes; regenere e recompile.");
|
|
1133
|
+
for (const [name, hash2] of Object.entries(m.files)) {
|
|
1134
|
+
if (typeof hash2 !== "string" || !/^[a-f0-9]{64}$/.test(hash2)) throw fail("RUNTIME_INVALIDO", "Hash inv\xE1lido no runtime.");
|
|
1135
|
+
const file = confined(root, name);
|
|
1136
|
+
let data;
|
|
1137
|
+
try {
|
|
1138
|
+
if (!(await stat3(file)).isFile()) throw Error();
|
|
1139
|
+
data = await readFile4(file);
|
|
1140
|
+
} catch {
|
|
1141
|
+
throw fail("RUNTIME_INCOMPLETO", "Arquivo do runtime ausente.");
|
|
1142
|
+
}
|
|
1143
|
+
if (createHash2("sha256").update(data).digest("hex") !== hash2) throw fail("RUNTIME_ALTERADO", "O runtime n\xE3o corresponde ao manifesto distribu\xEDdo.");
|
|
1144
|
+
}
|
|
1145
|
+
if (!m.files[m.executable]) throw fail("RUNTIME_INVALIDO", "O execut\xE1vel n\xE3o consta no invent\xE1rio.");
|
|
1146
|
+
return { directory: root, executable: confined(root, m.executable) };
|
|
1147
|
+
}
|
|
1148
|
+
function criarEmissor(options) {
|
|
1149
|
+
if (!/^\d{14}$/.test(options.cnpj) || !UFS.has(options.uf) || ![55, 65].includes(options.modelo) || ![1, 2].includes(options.ambiente)) throw fail("CONFIGURACAO_INVALIDA", "Informe CNPJ, UF, modelo e ambiente v\xE1lidos.");
|
|
1150
|
+
if (!validContract(options.contrato)) throw fail("CONTRATO_INVALIDO", "Contrato gerado inv\xE1lido.");
|
|
1151
|
+
const cert = options.certificado;
|
|
1152
|
+
if (cert && (typeof cert.senha !== "string" || !!cert.pfx === !!cert.arquivo || cert.pfx && !(cert.pfx instanceof Uint8Array))) throw fail("CERTIFICADO_INVALIDO", "Informe A1 em pfx ou arquivo e sua senha.");
|
|
1153
|
+
if (options.csc && (!/^\d{1,6}$/.test(options.csc.id) || typeof options.csc.valor !== "string" || !options.csc.valor || options.csc.valor.length > 256)) throw fail("CSC_INVALIDO", "CSC inv\xE1lido.");
|
|
1154
|
+
const cfg = { cnpj: options.cnpj, uf: options.uf, modelo: options.modelo, ambiente: options.ambiente, csc: options.csc ? { ...options.csc } : void 0 };
|
|
1155
|
+
const certificate = cert ? { ...cert, pfx: cert.pfx ? Buffer.from(cert.pfx) : void 0 } : void 0;
|
|
1156
|
+
const contract = { ...options.contrato };
|
|
1157
|
+
const timeout = options.timeoutMs ?? 12e4, capacity = options.limiteFila ?? 32, bytesLimit = options.limiteBytes ?? MAX_BYTES;
|
|
1158
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 12e4 || !Number.isInteger(capacity) || capacity < 0 || capacity > 1e3 || !Number.isInteger(bytesLimit) || bytesLimit < 256 || bytesLimit > MAX_BYTES) throw fail("CONFIGURACAO_INVALIDA", "Limites de execu\xE7\xE3o inv\xE1lidos.");
|
|
1159
|
+
const launch = options.criaProcesso ?? ((exe, args, config) => spawn2(exe, args, { ...config, stdio: ["pipe", "pipe", "pipe"] }));
|
|
1160
|
+
let runtimePromise, closed = false, current;
|
|
1161
|
+
const queue = [];
|
|
1162
|
+
async function execute2(item) {
|
|
1163
|
+
if (closed || item.signal?.aborted) throw fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o.");
|
|
1164
|
+
const runtime = await (runtimePromise ??= resolveRuntime(options.runtimeDirectory, contract));
|
|
1165
|
+
if (closed || item.signal?.aborted) throw fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o.");
|
|
1166
|
+
await new Promise((done) => {
|
|
1167
|
+
let stopError;
|
|
1168
|
+
let child, finished = false, started = false, sending = false, total = 0, rest = Buffer.alloc(0), result;
|
|
1169
|
+
let finishCurrent;
|
|
1170
|
+
const doneCurrent = new Promise((r) => {
|
|
1171
|
+
finishCurrent = r;
|
|
1172
|
+
});
|
|
1173
|
+
const uncertain = () => ["transmitir", "cancelar", "cartaCorrecao", "inutilizar"].includes(item.req.comando) && started || sending;
|
|
1174
|
+
const finish = (error) => {
|
|
1175
|
+
if (finished) return;
|
|
1176
|
+
finished = true;
|
|
1177
|
+
clearTimeout(timer);
|
|
1178
|
+
item.signal?.removeEventListener("abort", abort);
|
|
1179
|
+
item.bytes.fill(0);
|
|
1180
|
+
if (current?.done === doneCurrent) current = void 0;
|
|
1181
|
+
finishCurrent();
|
|
1182
|
+
error ? item.reject(error) : item.resolve(result);
|
|
1183
|
+
done();
|
|
1184
|
+
};
|
|
1185
|
+
const stop = (code, message) => {
|
|
1186
|
+
if (finished || stopError) return;
|
|
1187
|
+
stopError = fail(code, message, item.req, uncertain());
|
|
1188
|
+
if (child?.pid) child.kill("SIGKILL");
|
|
1189
|
+
else finish(stopError);
|
|
1190
|
+
};
|
|
1191
|
+
const abort = () => stop("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada; confira a situa\xE7\xE3o fiscal quando o envio foi iniciado.");
|
|
1192
|
+
const timer = setTimeout(() => stop("TIMEOUT_MOTOR", "O motor excedeu o prazo; consulte a situa\xE7\xE3o antes de transmitir novamente."), timeout);
|
|
1193
|
+
current = { stop: () => stop("EMISSOR_FECHADO", "Emissor encerrado."), done: doneCurrent };
|
|
1194
|
+
const env = {};
|
|
1195
|
+
for (const key of ["PATH", "Path", "SystemRoot", "WINDIR", "TEMP", "TMP", "HOME", "LANG", "LC_ALL"]) if (process.env[key] !== void 0) env[key] = process.env[key];
|
|
1196
|
+
if (process.platform === "linux") env.LD_LIBRARY_PATH = path.join(runtime.directory, "lib");
|
|
1197
|
+
if (process.platform === "win32") {
|
|
1198
|
+
const inheritedPath = env.Path ?? env.PATH ?? "";
|
|
1199
|
+
delete env.Path;
|
|
1200
|
+
env.PATH = path.join(runtime.directory, "lib") + ";" + runtime.directory + ";" + inheritedPath;
|
|
1201
|
+
}
|
|
1202
|
+
const modules = path.join(runtime.directory, "ossl-modules");
|
|
1203
|
+
env.OPENSSL_MODULES = modules;
|
|
1204
|
+
env.OPENSSL_CONF = path.join(runtime.directory, "openssl.cnf");
|
|
1205
|
+
try {
|
|
1206
|
+
child = launch(runtime.executable, ["--parent-pid", String(process.pid)], { cwd: runtime.directory, env, windowsHide: true, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
1207
|
+
started = typeof child.pid === "number";
|
|
1208
|
+
} catch {
|
|
1209
|
+
finish(fail("FALHA_INICIAR_MOTOR", "N\xE3o foi poss\xEDvel iniciar o motor.", item.req));
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
item.signal?.addEventListener("abort", abort, { once: true });
|
|
1213
|
+
child.on("spawn", () => {
|
|
1214
|
+
started = true;
|
|
1215
|
+
});
|
|
1216
|
+
child.on("error", () => finish(fail("FALHA_INICIAR_MOTOR", "N\xE3o foi poss\xEDvel iniciar o motor.", item.req, uncertain())));
|
|
1217
|
+
child.stdin.on("error", () => stop("FALHA_PIPE", "N\xE3o foi poss\xEDvel comunicar com o motor."));
|
|
1218
|
+
child.stderr.on("data", () => {
|
|
1219
|
+
});
|
|
1220
|
+
child.stdout.on("data", (chunk) => {
|
|
1221
|
+
if (finished || stopError) return;
|
|
1222
|
+
total += chunk.length;
|
|
1223
|
+
if (total > bytesLimit) {
|
|
1224
|
+
stop("RESPOSTA_EXCESSIVA", "Resposta do motor excede o limite.");
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
rest = Buffer.concat([rest, chunk]);
|
|
1228
|
+
let end;
|
|
1229
|
+
while ((end = rest.indexOf(10)) >= 0) {
|
|
1230
|
+
const line = rest.subarray(0, end);
|
|
1231
|
+
rest = rest.subarray(end + 1);
|
|
1232
|
+
if (!line.length) continue;
|
|
1233
|
+
try {
|
|
1234
|
+
const frame = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(line));
|
|
1235
|
+
if (!plain(frame) || frame.id !== item.req.id) throw Error();
|
|
1236
|
+
if (frame.tipo === "envioIniciado") {
|
|
1237
|
+
sending = true;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (result || frame.versao !== 1 || frame.sourceRevision !== contract.sourceRevision || frame.contractHash !== contract.contractHash || typeof frame.sucesso !== "boolean" || !STATES.has(String(frame.estado))) throw Error();
|
|
1241
|
+
if (frame.xmlBase64 !== void 0 && (typeof frame.xmlBase64 !== "string" || !frame.xmlBase64.length || Buffer.from(frame.xmlBase64, "base64").toString("base64") !== frame.xmlBase64)) throw Error();
|
|
1242
|
+
if (frame.cStat !== void 0 && (!Number.isInteger(frame.cStat) || Number(frame.cStat) < 1 || Number(frame.cStat) > 9999)) throw Error();
|
|
1243
|
+
result = frame;
|
|
1244
|
+
} catch {
|
|
1245
|
+
stop("RESPOSTA_INVALIDA", "O motor retornou uma resposta inv\xE1lida.");
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
child.on("close", (code) => {
|
|
1251
|
+
if (finished) return;
|
|
1252
|
+
if (stopError) {
|
|
1253
|
+
finish(stopError);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (code !== 0 || rest.length || !result) {
|
|
1257
|
+
finish(fail("FALHA_MOTOR", "O motor encerrou sem resposta fiscal v\xE1lida.", item.req, uncertain()));
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
finish();
|
|
1261
|
+
});
|
|
1262
|
+
child.stdin.end(item.bytes);
|
|
1263
|
+
if (item.signal?.aborted) abort();
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
let busy = false;
|
|
1267
|
+
async function next() {
|
|
1268
|
+
if (busy) return;
|
|
1269
|
+
const item = queue.shift();
|
|
1270
|
+
if (!item) return;
|
|
1271
|
+
busy = true;
|
|
1272
|
+
item.signal?.removeEventListener("abort", item.abort);
|
|
1273
|
+
try {
|
|
1274
|
+
await execute2(item);
|
|
1275
|
+
} catch (e) {
|
|
1276
|
+
item.bytes.fill(0);
|
|
1277
|
+
item.reject(e);
|
|
1278
|
+
} finally {
|
|
1279
|
+
busy = false;
|
|
1280
|
+
void next();
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
async function request(command, data, operation = {}) {
|
|
1284
|
+
if (closed) throw fail("EMISSOR_FECHADO", "Emissor encerrado.");
|
|
1285
|
+
if (operation.signal?.aborted) throw fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o.");
|
|
1286
|
+
if (["assinar", "transmitir", "consultar", "cancelar", "cartaCorrecao", "inutilizar"].includes(command) && !certificate) throw fail("CERTIFICADO_AUSENTE", "A opera\xE7\xE3o exige um certificado A1.");
|
|
1287
|
+
if (command === "gerarXml") options.validaDocumento?.(data.documento);
|
|
1288
|
+
const config = { ...cfg };
|
|
1289
|
+
if (certificate) {
|
|
1290
|
+
let pfx;
|
|
1291
|
+
try {
|
|
1292
|
+
pfx = certificate.pfx ?? await readFile4(certificate.arquivo);
|
|
1293
|
+
} catch {
|
|
1294
|
+
throw fail("CERTIFICADO_AUSENTE", "N\xE3o foi poss\xEDvel ler o A1 configurado.");
|
|
1295
|
+
}
|
|
1296
|
+
if (pfx.length > 2 * 1024 * 1024) throw fail("CERTIFICADO_EXCESSIVO", "Certificado excede o limite.");
|
|
1297
|
+
config.certificado = { pfxBase64: pfx.toString("base64"), senha: certificate.senha };
|
|
1298
|
+
}
|
|
1299
|
+
let bytes, req;
|
|
1300
|
+
try {
|
|
1301
|
+
bytes = Buffer.from(JSON.stringify({ versao: 1, id: randomUUID3(), ...contract, comando: command, config, ...data }) + "\n", "utf8");
|
|
1302
|
+
if (bytes.length > bytesLimit) throw Error();
|
|
1303
|
+
req = JSON.parse(bytes.toString("utf8"));
|
|
1304
|
+
} catch {
|
|
1305
|
+
throw fail("REQUISICAO_INVALIDA", "Dados da opera\xE7\xE3o inv\xE1lidos ou acima do limite.");
|
|
1306
|
+
}
|
|
1307
|
+
return new Promise((resolve5, reject) => {
|
|
1308
|
+
if (closed) {
|
|
1309
|
+
bytes.fill(0);
|
|
1310
|
+
reject(fail("EMISSOR_FECHADO", "Emissor encerrado."));
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (busy && queue.length >= capacity) {
|
|
1314
|
+
bytes.fill(0);
|
|
1315
|
+
reject(fail("FILA_CHEIA", "A fila fiscal est\xE1 cheia."));
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
const item = { req, bytes, signal: operation.signal, resolve: resolve5, reject };
|
|
1319
|
+
item.abort = () => {
|
|
1320
|
+
const i = queue.indexOf(item);
|
|
1321
|
+
if (i >= 0) {
|
|
1322
|
+
queue.splice(i, 1);
|
|
1323
|
+
bytes.fill(0);
|
|
1324
|
+
operation.signal?.removeEventListener("abort", item.abort);
|
|
1325
|
+
reject(fail("OPERACAO_CANCELADA", "Opera\xE7\xE3o cancelada antes da execu\xE7\xE3o."));
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
operation.signal?.addEventListener("abort", item.abort, { once: true });
|
|
1329
|
+
queue.push(item);
|
|
1330
|
+
if (operation.signal?.aborted) item.abort();
|
|
1331
|
+
void next();
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
const xmlData = (xml) => {
|
|
1335
|
+
if (typeof xml !== "string" || !xml.trim() || Buffer.byteLength(xml, "utf8") > 2 * 1024 * 1024) throw fail("XML_INVALIDO", "XML vazio ou acima do limite.");
|
|
1336
|
+
return { xmlBase64: Buffer.from(xml, "utf8").toString("base64") };
|
|
1337
|
+
};
|
|
1338
|
+
const checkKey = (key) => {
|
|
1339
|
+
if (!/^\d{44}$/.test(key)) throw fail("CHAVE_INVALIDA", "Chave fiscal inv\xE1lida.");
|
|
1340
|
+
};
|
|
1341
|
+
const reason = (value) => {
|
|
1342
|
+
if (typeof value !== "string" || value.length < 15 || value.length > 255) throw fail("JUSTIFICATIVA_INVALIDA", "Justificativa deve ter de 15 a 255 caracteres.");
|
|
1343
|
+
};
|
|
1344
|
+
return {
|
|
1345
|
+
gerarXml: (documento, op) => request("gerarXml", { documento }, op),
|
|
1346
|
+
validar: (xml, op) => request("validar", xmlData(xml), op),
|
|
1347
|
+
assinar: (xml, op) => request("assinar", xmlData(xml), op),
|
|
1348
|
+
transmitir: (xml, op) => request("transmitir", xmlData(xml), op),
|
|
1349
|
+
consultar: (consulta, op) => {
|
|
1350
|
+
checkKey(consulta.chave);
|
|
1351
|
+
if (consulta.recibo && !/^\d{15}$/.test(consulta.recibo)) throw fail("RECIBO_INVALIDO", "Recibo inv\xE1lido.");
|
|
1352
|
+
return request("consultar", consulta, op);
|
|
1353
|
+
},
|
|
1354
|
+
cancelar: (data, op) => {
|
|
1355
|
+
checkKey(data.chave);
|
|
1356
|
+
reason(data.justificativa);
|
|
1357
|
+
if (!/^\d{15}$/.test(data.protocolo)) throw fail("PROTOCOLO_INVALIDO", "Protocolo inv\xE1lido.");
|
|
1358
|
+
return request("cancelar", data, op);
|
|
1359
|
+
},
|
|
1360
|
+
cartaCorrecao: (data, op) => {
|
|
1361
|
+
if (cfg.modelo !== 55) throw fail("OPERACAO_NAO_SUPORTADA", "Carta de corre\xE7\xE3o exige NF-e modelo 55.");
|
|
1362
|
+
checkKey(data.chave);
|
|
1363
|
+
if (typeof data.correcao !== "string" || data.correcao.length < 15 || data.correcao.length > 1e3 || !Number.isInteger(data.sequenciaEvento ?? 1) || (data.sequenciaEvento ?? 1) < 1 || (data.sequenciaEvento ?? 1) > 20) throw fail("CORRECAO_INVALIDA", "Dados da carta de corre\xE7\xE3o inv\xE1lidos.");
|
|
1364
|
+
return request("cartaCorrecao", { ...data, sequenciaEvento: data.sequenciaEvento ?? 1 }, op);
|
|
1365
|
+
},
|
|
1366
|
+
inutilizar: (data, op) => {
|
|
1367
|
+
reason(data.justificativa);
|
|
1368
|
+
if (!Number.isInteger(data.ano) || data.ano < 0 || data.ano > 99 || !Number.isInteger(data.serie) || data.serie < 0 || data.serie > 999 || !Number.isInteger(data.numeroInicial) || data.numeroInicial < 1 || !Number.isInteger(data.numeroFinal) || data.numeroFinal < data.numeroInicial || data.numeroFinal > 999999999) throw fail("INUTILIZACAO_INVALIDA", "Faixa de inutiliza\xE7\xE3o inv\xE1lida.");
|
|
1369
|
+
return request("inutilizar", { inutilizacao: { ano: data.ano, serie: data.serie, numeroInicial: data.numeroInicial, numeroFinal: data.numeroFinal }, justificativa: data.justificativa }, op);
|
|
1370
|
+
},
|
|
1371
|
+
async fechar() {
|
|
1372
|
+
if (closed) return;
|
|
1373
|
+
closed = true;
|
|
1374
|
+
for (const item of queue.splice(0)) {
|
|
1375
|
+
item.signal?.removeEventListener("abort", item.abort);
|
|
1376
|
+
item.bytes.fill(0);
|
|
1377
|
+
item.reject(fail("EMISSOR_FECHADO", "Emissor encerrado."));
|
|
1378
|
+
}
|
|
1379
|
+
const running = current;
|
|
1380
|
+
running?.stop();
|
|
1381
|
+
if (running) await running.done;
|
|
1382
|
+
certificate?.pfx?.fill(0);
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/offline-fixture.ts
|
|
1388
|
+
var OFFLINE_DOCUMENT = {
|
|
1389
|
+
"infNFe": {
|
|
1390
|
+
"Versao": "4.00"
|
|
1391
|
+
},
|
|
1392
|
+
"Ide": {
|
|
1393
|
+
"cUF": 23,
|
|
1394
|
+
"cNF": 12345678,
|
|
1395
|
+
"natOp": "VENDA TESTE OFFLINE",
|
|
1396
|
+
"modelo": 55,
|
|
1397
|
+
"serie": 1,
|
|
1398
|
+
"nNF": 1,
|
|
1399
|
+
"dEmi": "2026-10-08T12:00:00",
|
|
1400
|
+
"tpNF": "tnSaida",
|
|
1401
|
+
"idDest": "doInterna",
|
|
1402
|
+
"cMunFG": 2304400,
|
|
1403
|
+
"tpImp": "tiRetrato",
|
|
1404
|
+
"tpEmis": "teNormal",
|
|
1405
|
+
"tpAmb": "taHomologacao",
|
|
1406
|
+
"finNFe": "fnNormal",
|
|
1407
|
+
"indFinal": "cfConsumidorFinal",
|
|
1408
|
+
"indPres": "pcPresencial",
|
|
1409
|
+
"procEmi": "peAplicativoContribuinte",
|
|
1410
|
+
"verProc": "ACBR-NODE-OFFLINE"
|
|
1411
|
+
},
|
|
1412
|
+
"Emit": {
|
|
1413
|
+
"CNPJCPF": "11222333000181",
|
|
1414
|
+
"xNome": "Caf\xE9 S\xE3o Jos\xE9 TESTE OFFLINE SEM VALOR FISCAL",
|
|
1415
|
+
"IE": "123456789",
|
|
1416
|
+
"CRT": "crtSimplesNacional",
|
|
1417
|
+
"EnderEmit": {
|
|
1418
|
+
"xLgr": "RUA TESTE",
|
|
1419
|
+
"nro": "1",
|
|
1420
|
+
"xBairro": "TESTE",
|
|
1421
|
+
"cMun": 2304400,
|
|
1422
|
+
"xMun": "FORTALEZA",
|
|
1423
|
+
"UF": "CE",
|
|
1424
|
+
"CEP": 6e7,
|
|
1425
|
+
"cPais": 1058,
|
|
1426
|
+
"xPais": "BRASIL"
|
|
1427
|
+
}
|
|
1428
|
+
},
|
|
1429
|
+
"Dest": {
|
|
1430
|
+
"CNPJCPF": "12345678909",
|
|
1431
|
+
"xNome": "NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL",
|
|
1432
|
+
"indIEDest": "inNaoContribuinte",
|
|
1433
|
+
"EnderDest": {
|
|
1434
|
+
"xLgr": "RUA TESTE",
|
|
1435
|
+
"nro": "2",
|
|
1436
|
+
"xBairro": "TESTE",
|
|
1437
|
+
"cMun": 2304400,
|
|
1438
|
+
"xMun": "FORTALEZA",
|
|
1439
|
+
"UF": "CE",
|
|
1440
|
+
"CEP": 6e7,
|
|
1441
|
+
"cPais": 1058,
|
|
1442
|
+
"xPais": "BRASIL"
|
|
1443
|
+
}
|
|
1444
|
+
},
|
|
1445
|
+
"Det": [
|
|
1446
|
+
{
|
|
1447
|
+
"Prod": {
|
|
1448
|
+
"nItem": 1,
|
|
1449
|
+
"cProd": "TESTE",
|
|
1450
|
+
"cEAN": "SEM GTIN",
|
|
1451
|
+
"xProd": "Caf\xE9 S\xE3o Jos\xE9 PRODUTO SINT\xC9TICO OFFLINE",
|
|
1452
|
+
"NCM": "22021000",
|
|
1453
|
+
"CFOP": "5102",
|
|
1454
|
+
"uCom": "UN",
|
|
1455
|
+
"qCom": "1",
|
|
1456
|
+
"vUnCom": "10",
|
|
1457
|
+
"vProd": "10",
|
|
1458
|
+
"cEANTrib": "SEM GTIN",
|
|
1459
|
+
"uTrib": "UN",
|
|
1460
|
+
"qTrib": "1",
|
|
1461
|
+
"vUnTrib": "10",
|
|
1462
|
+
"IndTot": "itSomaTotalNFe"
|
|
1463
|
+
},
|
|
1464
|
+
"Imposto": {
|
|
1465
|
+
"ICMS": {
|
|
1466
|
+
"orig": "oeNacional",
|
|
1467
|
+
"CSOSN": "csosn102"
|
|
1468
|
+
},
|
|
1469
|
+
"PIS": {
|
|
1470
|
+
"CST": "pis07"
|
|
1471
|
+
},
|
|
1472
|
+
"COFINS": {
|
|
1473
|
+
"CST": "cof07"
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
],
|
|
1478
|
+
"Total": {
|
|
1479
|
+
"ICMSTot": {
|
|
1480
|
+
"vProd": "10",
|
|
1481
|
+
"vNF": "10"
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1484
|
+
"Transp": {
|
|
1485
|
+
"modFrete": "mfSemFrete"
|
|
1486
|
+
},
|
|
1487
|
+
"pag": [
|
|
1488
|
+
{
|
|
1489
|
+
"tPag": "fpDinheiro",
|
|
1490
|
+
"indPag": "ipVista",
|
|
1491
|
+
"vPag": "10"
|
|
1492
|
+
}
|
|
1493
|
+
]
|
|
1494
|
+
};
|
|
1495
|
+
var OFFLINE_SIGNED_XML = '<?xml version="1.0" encoding="UTF-8"?><NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe Id="NFe23261011222333000181550010000000011123456780" versao="4.00"><ide><cUF>23</cUF><cNF>12345678</cNF><natOp>VENDA TESTE OFFLINE</natOp><mod>55</mod><serie>1</serie><nNF>1</nNF><dhEmi>2026-10-08T12:00:00-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>2304400</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>0</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>ACBR-NODE-OFFLINE</verProc></ide><emit><CNPJ>11222333000181</CNPJ><xNome>Caf\xE9 S\xE3o Jos\xE9 TESTE OFFLINE SEM VALOR FISCAL</xNome><enderEmit><xLgr>RUA TESTE</xLgr><nro>1</nro><xBairro>TESTE</xBairro><cMun>2304400</cMun><xMun>FORTALEZA</xMun><UF>CE</UF><CEP>60000000</CEP><cPais>1058</cPais><xPais>BRASIL</xPais></enderEmit><IE>123456789</IE><CRT>1</CRT></emit><dest><CPF>12345678909</CPF><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL</xNome><enderDest><xLgr>RUA TESTE</xLgr><nro>2</nro><xBairro>TESTE</xBairro><cMun>2304400</cMun><xMun>FORTALEZA</xMun><UF>CE</UF><CEP>60000000</CEP><cPais>1058</cPais><xPais>BRASIL</xPais></enderDest><indIEDest>9</indIEDest></dest><det nItem="1"><prod><cProd>TESTE</cProd><cEAN>SEM GTIN</cEAN><xProd>Caf\xE9 S\xE3o Jos\xE9 PRODUTO SINT\xC9TICO OFFLINE</xProd><NCM>22021000</NCM><CFOP>5102</CFOP><uCom>UN</uCom><qCom>1.0000</qCom><vUnCom>10.0000000000</vUnCom><vProd>10.00</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>UN</uTrib><qTrib>1.0000</qTrib><vUnTrib>10.0000000000</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMSSN102><orig>0</orig><CSOSN>102</CSOSN></ICMSSN102></ICMS><PIS><PISNT><CST>07</CST></PISNT></PIS><COFINS><COFINSNT><CST>07</CST></COFINSNT></COFINS></imposto></det><total><ICMSTot><vBC>0.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>10.00</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>10.00</vNF></ICMSTot></total><transp><modFrete>9</modFrete></transp><pag><detPag><indPag>0</indPag><tPag>01</tPag><vPag>10.00</vPag></detPag></pag></infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod><Reference URI="#NFe23261011222333000181550010000000011123456780"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>Ya5kXhcEibOVfcyOI0BzqcYzyMM=</DigestValue></Reference></SignedInfo><SignatureValue>mirdDvdV+VId/zsstW/6lVFsSVxdyWkdkY1NPd2ynLHpjtolFoIhaBGlfxxs7IhU/AzcNQmR6ivr9YkH1HXaMAypvvx9M6GR/T4du/Zqab4gx3YInVybjJTeQ7BKEDnLhde2TUKoXrxmfrrmBUw6K4nTNufVjUjFJBfGcwhJ57T72x7ssdoYuPvZCphb0IR8WxHRKhvKAQhjW61qfh33csTabelgVZSLuTC2I2k9+a+ARd88CLTlfhYNCAPItOdUnAjgrhAhtiDi2FjXKjywgRqx0zay8DxF9Y2iyTJd3IAoWOERGdI9whgsuzWiZUr2I0I5QGGBELVabfjXm/Bneg==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIDhjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBbMSswKQYDVQQDEyJBQ0JSIE5PREUgU1lOVEhFVElDOjExMjIyMzMzMDAwMTgxMQswCQYDVQQGEwJCUjEfMB0GA1UEChMWU1lOVEhFVElDIE9GRkxJTkUgVEVTVDAeFw0yNjEwMDYyMjIzMzNaFw0yNzEwMDgyMjIzMzNaMFsxKzApBgNVBAMTIkFDQlIgTk9ERSBTWU5USEVUSUM6MTEyMjIzMzMwMDAxODExCzAJBgNVBAYTAkJSMR8wHQYDVQQKExZTWU5USEVUSUMgT0ZGTElORSBURVNUMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsOwRzuW4de7jrv1IkcNgT311292NR5NNIHt6Fncarm2iJX6mYsIjPWriFQmrU88p7bfRI3EsXJrlK0oGCuMafbNf1nBZ6Tu8aV1ixbAIgu9ZIbWoDqDNbYJBucQRuKsI1ytFbY7eI0juhcdAlBHw3TNdgSbuv/8kEGgTTJ803wBcC+emGZHdpb4Bwx6Db0Fio0lWLlBaep9+MFNZZiEYmDA3dwgZF14MAr5MpzcNDUVYh/g95Ks8x+mhErOPdtmL1A6cDg+xtjiApeMJZqjMrdsXP4yQ34h5VRy03ZUJkXVAquOfWhXpQL/qjoSzXfHvij2wB0pVn89FzVZh7GzStQIDAQABo1UwUzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDAjAkBgNVHREEHTAboBkGBWBMAQMDoBAMDjExMjIyMzMzMDAwMTgxMA0GCSqGSIb3DQEBCwUAA4IBAQBDhbpxphiuAUlQWXkcVt22Nc+g2UM6GCZyC3v9o21llLSCyK/nhkrRCQzM75/86kbnYZ1zdulC9N4XQFgATwm16Sf/nDZw8s5EsXqZ8mwbauDs/aQvWrbQnyawB7c+aXvlUDkcfNq+3hLQ5ep8/bmGh4eueiNoPD0fjf/jUVmrS6KyHb6/FyIZGP6fEYAO6XrqJPMSPxpJHnbPXZXKW2vcCh3qaG1vTSeN8PLfN2Q/mp1XUH8VqHgl3jzssx/zwb1NKuKEz9WL+6ilLc1ZfIqUakJyQNMqMz2KIjqGKGp7TtpNHZ7mz+kBX4Xw7D5fFVrXYb7DN6KylyKLmXoEzs1B</X509Certificate></X509Data></KeyInfo></Signature></NFe>';
|
|
1496
|
+
|
|
1497
|
+
// src/offline-check.ts
|
|
1498
|
+
async function checkOffline(config, manifest) {
|
|
1499
|
+
const emitter = criarEmissor({
|
|
1500
|
+
cnpj: "11222333000181",
|
|
1501
|
+
uf: "CE",
|
|
1502
|
+
modelo: 55,
|
|
1503
|
+
ambiente: 2,
|
|
1504
|
+
contrato: { sourceRevision: manifest.sourceRevision, contractHash: manifest.contractHash },
|
|
1505
|
+
runtimeDirectory: runtimeDirectory(config),
|
|
1506
|
+
timeoutMs: 3e4
|
|
1507
|
+
});
|
|
1508
|
+
const report = [];
|
|
1509
|
+
function expect(name, result, estado, codigo) {
|
|
1510
|
+
if (result.estado !== estado || result.sucesso !== (estado !== "ERRO") || result.envioIniciado !== false || result.sourceRevision !== manifest.sourceRevision || codigo !== void 0 && result.codigo !== codigo) {
|
|
1511
|
+
throw new Error(`Smoke offline ${name} falhou: ${result.estado}/${result.codigo ?? "sem c\xF3digo"}.`);
|
|
1512
|
+
}
|
|
1513
|
+
report.push({ name, estado: result.estado, codigo: result.codigo, envioIniciado: false });
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
const generated = await emitter.gerarXml(OFFLINE_DOCUMENT);
|
|
1517
|
+
expect("gerarXml sint\xE9tico", generated, "XML_GERADO");
|
|
1518
|
+
const xml = generated.xmlBase64 ? Buffer.from(generated.xmlBase64, "base64").toString("utf8") : "";
|
|
1519
|
+
const generatedKey = /Id="NFe(\d{44})"/.exec(xml)?.[1];
|
|
1520
|
+
if (!generatedKey || !xml.includes("Caf\xE9 S\xE3o Jos\xE9 TESTE OFFLINE") || !xml.includes("<CNPJ>11222333000181</CNPJ>")) throw new Error("Smoke offline gerarXml perdeu chave, identidade ou UTF-8.");
|
|
1521
|
+
const validated = await emitter.validar(OFFLINE_SIGNED_XML);
|
|
1522
|
+
expect("validar XML assinado p\xFAblico", validated, "VALIDADO");
|
|
1523
|
+
const signedKey = /Id="NFe(\d{44})"/.exec(OFFLINE_SIGNED_XML)?.[1];
|
|
1524
|
+
if (validated.chave !== signedKey || generatedKey !== signedKey) throw new Error("Smoke offline validar retornou outra chave fiscal.");
|
|
1525
|
+
expect("recusar XML malformado", await emitter.validar("<NFe><infNFe></NFe>"), "ERRO", "OPERACAO_FISCAL_FALHOU");
|
|
1526
|
+
expect("recusar DTD/entidade externa", await emitter.validar('<!DOCTYPE NFe [<!ENTITY probe SYSTEM "file:///acbr-node-offline-check-does-not-exist">]><NFe>&probe;</NFe>'), "ERRO", "XML_INVALIDO");
|
|
1527
|
+
return report;
|
|
1528
|
+
} finally {
|
|
1529
|
+
await emitter.fechar();
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// src/cli.ts
|
|
1534
|
+
var HELP = `acbr-node \u2014 compila\xE7\xE3o e contrato Node/ACBr NF-e
|
|
1535
|
+
|
|
1536
|
+
Uso:
|
|
1537
|
+
acbr-node init [--config caminho] [--revision HEAD|n\xFAmero]
|
|
1538
|
+
acbr-node source update [--config caminho] [--revision HEAD|n\xFAmero]
|
|
1539
|
+
acbr-node source lock --revision n\xFAmero [--config caminho]
|
|
1540
|
+
acbr-node generate [--config caminho]
|
|
1541
|
+
acbr-node build [--config caminho]
|
|
1542
|
+
acbr-node check [--config caminho]
|
|
1543
|
+
acbr-node doctor [--config caminho]
|
|
1544
|
+
|
|
1545
|
+
source update consulta o SVN oficial e substitui apenas fontes verificadas pelo lock.
|
|
1546
|
+
source lock registra um export existente cuja origem/revis\xE3o o operador j\xE1 conferiu.
|
|
1547
|
+
generate/build/check nunca atualizam fontes. Nenhum comando transmite NF-e.
|
|
1548
|
+
`;
|
|
1549
|
+
function revision(value) {
|
|
1550
|
+
if (value === void 0) return void 0;
|
|
1551
|
+
if (value === "HEAD") return value;
|
|
1552
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) throw new Error("--revision deve ser HEAD ou um n\xFAmero SVN positivo.");
|
|
1553
|
+
return Number(value);
|
|
1554
|
+
}
|
|
1555
|
+
function parseArguments(args) {
|
|
1556
|
+
const result = { command: [], config: "acbr.config.json", json: false, help: false };
|
|
1557
|
+
const used = /* @__PURE__ */ new Set();
|
|
1558
|
+
for (let i = 0; i < args.length; i++) {
|
|
1559
|
+
const arg = args[i];
|
|
1560
|
+
if (!arg.startsWith("-")) {
|
|
1561
|
+
result.command.push(arg);
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (used.has(arg)) throw new Error(`Op\xE7\xE3o repetida: ${arg}.`);
|
|
1565
|
+
used.add(arg);
|
|
1566
|
+
if (arg === "--help" || arg === "-h") result.help = true;
|
|
1567
|
+
else if (arg === "--json") result.json = true;
|
|
1568
|
+
else if (arg === "--config" || arg === "--revision") {
|
|
1569
|
+
const value = args[++i];
|
|
1570
|
+
if (value === void 0 || value.startsWith("--")) throw new Error(`Valor obrigat\xF3rio para ${arg}.`);
|
|
1571
|
+
if (arg === "--config") result.config = value;
|
|
1572
|
+
else result.revision = revision(value);
|
|
1573
|
+
} else throw new Error(`Op\xE7\xE3o desconhecida: ${arg}.`);
|
|
1574
|
+
}
|
|
1575
|
+
return result;
|
|
1576
|
+
}
|
|
1577
|
+
async function main(args = process.argv.slice(2), io = process) {
|
|
1578
|
+
try {
|
|
1579
|
+
const options = parseArguments(args);
|
|
1580
|
+
const command = options.command.join(" ");
|
|
1581
|
+
if (options.help || !command) {
|
|
1582
|
+
io.stdout.write(HELP);
|
|
1583
|
+
return 0;
|
|
1584
|
+
}
|
|
1585
|
+
const output = (value, text) => io.stdout.write(options.json ? `${JSON.stringify(value, null, 2)}
|
|
1586
|
+
` : `${text}
|
|
1587
|
+
`);
|
|
1588
|
+
if (command === "init") {
|
|
1589
|
+
const config2 = await initializeConfig(options.config, options.revision, fileURLToPath3(new URL("../", import.meta.url)));
|
|
1590
|
+
output({ configPath: config2.configPath }, `Configura\xE7\xE3o criada: ${config2.configPath}
|
|
1591
|
+
Pr\xF3ximo passo: acbr-node source update.`);
|
|
1592
|
+
return 0;
|
|
1593
|
+
}
|
|
1594
|
+
if (!["source update", "source lock", "generate", "build", "check", "doctor"].includes(command)) throw new Error(`Comando desconhecido: ${command}. Use --help.`);
|
|
1595
|
+
if (options.revision !== void 0 && !command.startsWith("source ")) throw new Error("--revision \xE9 aceito somente em init e source.");
|
|
1596
|
+
if (command === "doctor") {
|
|
1597
|
+
let config2;
|
|
1598
|
+
try {
|
|
1599
|
+
await access4(resolve4(options.config));
|
|
1600
|
+
config2 = await loadConfig(options.config);
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
if (error.code !== "ENOENT") throw error;
|
|
1603
|
+
}
|
|
1604
|
+
const report = await doctor(config2);
|
|
1605
|
+
output(report, report.map((item) => `${item.ok ? "OK" : "FALHA"} ${item.name}: ${item.detail}`).join("\n"));
|
|
1606
|
+
return report.every((item) => item.ok) ? 0 : 1;
|
|
1607
|
+
}
|
|
1608
|
+
const config = await loadConfig(options.config);
|
|
1609
|
+
if (command === "source update") {
|
|
1610
|
+
const lock = await updateSource(config, options.revision);
|
|
1611
|
+
output(lock, `Fontes oficiais registradas na revis\xE3o ${lock.source.revision}.
|
|
1612
|
+
SHA256 da \xE1rvore: ${lock.source.treeHash}
|
|
1613
|
+
Execute generate para atualizar o contrato.`);
|
|
1614
|
+
} else if (command === "source lock") {
|
|
1615
|
+
if (typeof options.revision !== "number") throw new Error("source lock exige --revision com o n\xFAmero verificado do export SVN.");
|
|
1616
|
+
if (config.source.revision !== "HEAD" && config.source.revision !== options.revision) throw new Error("Revis\xE3o do export diverge da configura\xE7\xE3o.");
|
|
1617
|
+
const lock = await lockExistingSource(config, options.revision);
|
|
1618
|
+
output(lock, `Export existente registrado na revis\xE3o ${lock.source.revision}; ${Object.keys(lock.source.files).length} arquivos protegidos por SHA256.`);
|
|
1619
|
+
} else if (command === "generate") {
|
|
1620
|
+
const lock = await verifySource(config);
|
|
1621
|
+
const { generateModel: generateModel2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
|
|
1622
|
+
const result = await generateModel2({ acbrRoot: config.source.directory, outputDir: config.generator.outputDir, revision: String(lock.source.revision), sourceHash: lock.source.treeHash, fpcPath: config.native.fpc });
|
|
1623
|
+
output(result, `Contrato ${result.contractHash}; ${result.models} modelos gerados.
|
|
1624
|
+
Revise o relat\xF3rio: ${result.reportPath}`);
|
|
1625
|
+
} else if (command === "build") {
|
|
1626
|
+
const result = await buildNative(config);
|
|
1627
|
+
output(result, `Runtime compilado: ${runtimeDirectory(config)}
|
|
1628
|
+
ACBr revis\xE3o ${result.sourceRevision}; contrato ${result.contractHash}.`);
|
|
1629
|
+
} else {
|
|
1630
|
+
const result = await checkBuild(config);
|
|
1631
|
+
const offlineChecks = await checkOffline(config, result);
|
|
1632
|
+
output({ ...result, offlineChecks }, `Fontes, contrato e ${Object.keys(result.files).length} arquivos do runtime conferidos por SHA256.
|
|
1633
|
+
${offlineChecks.length} verifica\xE7\xF5es offline do motor aprovadas; nenhuma transmiss\xE3o.`);
|
|
1634
|
+
}
|
|
1635
|
+
return 0;
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
io.stderr.write(`Erro: ${error.message}
|
|
1638
|
+
`);
|
|
1639
|
+
return 1;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
if (process.argv[1] && resolve4(process.argv[1]) === fileURLToPath3(import.meta.url)) {
|
|
1643
|
+
main().then((code) => {
|
|
1644
|
+
process.exitCode = code;
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
export {
|
|
1648
|
+
HELP,
|
|
1649
|
+
main,
|
|
1650
|
+
parseArguments
|
|
1651
|
+
};
|
|
1652
|
+
//# sourceMappingURL=cli.js.map
|