@neat.is/core 0.3.5 → 0.3.6
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/dist/{chunk-KCEZSFU2.js → chunk-YHQYHFI3.js} +2 -2
- package/dist/{chunk-BUB3ASD5.js → chunk-ZU2RQRCN.js} +3 -3
- package/dist/chunk-ZU2RQRCN.js.map +1 -0
- package/dist/cli.cjs +341 -61
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +339 -60
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +83 -2
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +82 -2
- package/dist/neatd.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BUB3ASD5.js.map +0 -1
- /package/dist/{chunk-KCEZSFU2.js.map → chunk-YHQYHFI3.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -1663,7 +1663,7 @@ function buildErrorEventForReceiver(span) {
|
|
|
1663
1663
|
service: span.service,
|
|
1664
1664
|
traceId: span.traceId,
|
|
1665
1665
|
spanId: span.spanId,
|
|
1666
|
-
errorMessage: span.exception?.message ??
|
|
1666
|
+
errorMessage: span.exception?.message ?? "unknown error",
|
|
1667
1667
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1668
1668
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1669
1669
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
@@ -1755,7 +1755,7 @@ async function handleSpan(ctx, span) {
|
|
|
1755
1755
|
service: span.service,
|
|
1756
1756
|
traceId: span.traceId,
|
|
1757
1757
|
spanId: span.spanId,
|
|
1758
|
-
errorMessage: span.exception?.message ??
|
|
1758
|
+
errorMessage: span.exception?.message ?? "unknown error",
|
|
1759
1759
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1760
1760
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1761
1761
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
@@ -5911,15 +5911,69 @@ init_cjs_shims();
|
|
|
5911
5911
|
init_cjs_shims();
|
|
5912
5912
|
var import_node_fs23 = require("fs");
|
|
5913
5913
|
var import_node_path38 = __toESM(require("path"), 1);
|
|
5914
|
+
|
|
5915
|
+
// src/installers/templates.ts
|
|
5916
|
+
init_cjs_shims();
|
|
5917
|
+
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
5918
|
+
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
5919
|
+
// Loads .env.neat (OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT) before
|
|
5920
|
+
// the auto-instrumentation hook attaches. Configure via the env file.
|
|
5921
|
+
const path = require('node:path')
|
|
5922
|
+
try {
|
|
5923
|
+
require('dotenv').config({ path: path.join(__dirname, '.env.neat') })
|
|
5924
|
+
} catch (err) {
|
|
5925
|
+
// dotenv unavailable \u2014 fall through to process.env.
|
|
5926
|
+
}
|
|
5927
|
+
require('@opentelemetry/auto-instrumentations-node/register')
|
|
5928
|
+
`;
|
|
5929
|
+
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
5930
|
+
// Loads .env.neat (OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT) before
|
|
5931
|
+
// the auto-instrumentation hook attaches. Configure via the env file.
|
|
5932
|
+
import { fileURLToPath } from 'node:url'
|
|
5933
|
+
import path from 'node:path'
|
|
5934
|
+
import dotenv from 'dotenv'
|
|
5935
|
+
|
|
5936
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
5937
|
+
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
5938
|
+
|
|
5939
|
+
await import('@opentelemetry/auto-instrumentations-node/register')
|
|
5940
|
+
`;
|
|
5941
|
+
var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
|
|
5942
|
+
// Loads .env.neat (OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT) before
|
|
5943
|
+
// the auto-instrumentation hook attaches. Configure via the env file.
|
|
5944
|
+
import { fileURLToPath } from 'node:url'
|
|
5945
|
+
import path from 'node:path'
|
|
5946
|
+
import dotenv from 'dotenv'
|
|
5947
|
+
|
|
5948
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
5949
|
+
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
5950
|
+
|
|
5951
|
+
await import('@opentelemetry/auto-instrumentations-node/register')
|
|
5952
|
+
`;
|
|
5953
|
+
function renderEnvNeat(serviceName) {
|
|
5954
|
+
return [
|
|
5955
|
+
"# Generated by `neat init --apply` (ADR-069).",
|
|
5956
|
+
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
5957
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318",
|
|
5958
|
+
""
|
|
5959
|
+
].join("\n");
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
// src/installers/javascript.ts
|
|
5914
5963
|
var SDK_PACKAGES = [
|
|
5915
5964
|
{ name: "@opentelemetry/api", version: "^1.9.0" },
|
|
5916
5965
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
5917
|
-
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
5966
|
+
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" },
|
|
5967
|
+
// ADR-069 §5 — dotenv is the fourth dep. The generated otel-init loads
|
|
5968
|
+
// .env.neat through it so OTEL_SERVICE_NAME and the endpoint are in scope
|
|
5969
|
+
// before the auto-instrumentation hook attaches.
|
|
5970
|
+
{ name: "dotenv", version: "^16.4.5" }
|
|
5918
5971
|
];
|
|
5919
|
-
var AUTO_INSTRUMENT_REQUIRE = "--require @opentelemetry/auto-instrumentations-node/register";
|
|
5920
5972
|
var OTEL_ENV = {
|
|
5921
|
-
//
|
|
5922
|
-
//
|
|
5973
|
+
// ADR-069 §4 — endpoint moves into the per-package .env.neat (written
|
|
5974
|
+
// by the apply phase). The envEdits surface stays for the dry-run
|
|
5975
|
+
// patch render: it documents the key/value the user can inspect in the
|
|
5976
|
+
// generated .env.neat.
|
|
5923
5977
|
file: null,
|
|
5924
5978
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
5925
5979
|
value: "http://localhost:4318"
|
|
@@ -5932,16 +5986,75 @@ async function readPackageJson(serviceDir) {
|
|
|
5932
5986
|
return null;
|
|
5933
5987
|
}
|
|
5934
5988
|
}
|
|
5989
|
+
async function exists2(p) {
|
|
5990
|
+
try {
|
|
5991
|
+
await import_node_fs23.promises.stat(p);
|
|
5992
|
+
return true;
|
|
5993
|
+
} catch {
|
|
5994
|
+
return false;
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5935
5997
|
async function detect(serviceDir) {
|
|
5936
5998
|
const pkg = await readPackageJson(serviceDir);
|
|
5937
5999
|
return pkg !== null && typeof pkg.name === "string";
|
|
5938
6000
|
}
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
if (
|
|
5942
|
-
|
|
6001
|
+
var INDEX_CANDIDATES = ["index.ts", "index.tsx", "index.js", "index.mjs", "index.cjs"];
|
|
6002
|
+
async function resolveEntry(serviceDir, pkg) {
|
|
6003
|
+
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
6004
|
+
const candidate = import_node_path38.default.resolve(serviceDir, pkg.main);
|
|
6005
|
+
if (await exists2(candidate)) return candidate;
|
|
6006
|
+
}
|
|
6007
|
+
if (pkg.bin) {
|
|
6008
|
+
let binEntry;
|
|
6009
|
+
if (typeof pkg.bin === "string") {
|
|
6010
|
+
binEntry = pkg.bin;
|
|
6011
|
+
} else if (pkg.name && typeof pkg.bin[pkg.name] === "string") {
|
|
6012
|
+
binEntry = pkg.bin[pkg.name];
|
|
6013
|
+
} else {
|
|
6014
|
+
const first = Object.values(pkg.bin)[0];
|
|
6015
|
+
if (typeof first === "string") binEntry = first;
|
|
6016
|
+
}
|
|
6017
|
+
if (binEntry) {
|
|
6018
|
+
const candidate = import_node_path38.default.resolve(serviceDir, binEntry);
|
|
6019
|
+
if (await exists2(candidate)) return candidate;
|
|
6020
|
+
}
|
|
5943
6021
|
}
|
|
5944
|
-
|
|
6022
|
+
for (const name of INDEX_CANDIDATES) {
|
|
6023
|
+
const candidate = import_node_path38.default.join(serviceDir, name);
|
|
6024
|
+
if (await exists2(candidate)) return candidate;
|
|
6025
|
+
}
|
|
6026
|
+
return null;
|
|
6027
|
+
}
|
|
6028
|
+
function dispatchEntry(entryFile, pkg) {
|
|
6029
|
+
const ext = import_node_path38.default.extname(entryFile).toLowerCase();
|
|
6030
|
+
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
6031
|
+
if (ext === ".mjs") return "esm";
|
|
6032
|
+
if (ext === ".cjs") return "cjs";
|
|
6033
|
+
return pkg.type === "module" ? "esm" : "cjs";
|
|
6034
|
+
}
|
|
6035
|
+
function otelInitFilename(flavor) {
|
|
6036
|
+
if (flavor === "ts") return "otel-init.ts";
|
|
6037
|
+
if (flavor === "esm") return "otel-init.mjs";
|
|
6038
|
+
return "otel-init.cjs";
|
|
6039
|
+
}
|
|
6040
|
+
function otelInitContents(flavor) {
|
|
6041
|
+
if (flavor === "ts") return OTEL_INIT_TS;
|
|
6042
|
+
if (flavor === "esm") return OTEL_INIT_ESM;
|
|
6043
|
+
return OTEL_INIT_CJS;
|
|
6044
|
+
}
|
|
6045
|
+
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
6046
|
+
let rel = import_node_path38.default.relative(import_node_path38.default.dirname(entryFile), otelInitFile);
|
|
6047
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
6048
|
+
rel = rel.split(import_node_path38.default.sep).join("/");
|
|
6049
|
+
if (flavor === "cjs") return `require('${rel}')`;
|
|
6050
|
+
if (flavor === "esm") return `import '${rel}'`;
|
|
6051
|
+
const tsRel = rel.replace(/\.ts$/, "");
|
|
6052
|
+
return `import '${tsRel}'`;
|
|
6053
|
+
}
|
|
6054
|
+
function lineIsOtelInjection(line) {
|
|
6055
|
+
const trimmed = line.trim();
|
|
6056
|
+
if (trimmed.length === 0) return false;
|
|
6057
|
+
return /(?:require\(|import\s+)['"]\.\/otel-init[^'"]*['"]/.test(trimmed);
|
|
5945
6058
|
}
|
|
5946
6059
|
async function plan(serviceDir) {
|
|
5947
6060
|
const pkg = await readPackageJson(serviceDir);
|
|
@@ -5951,9 +6064,17 @@ async function plan(serviceDir) {
|
|
|
5951
6064
|
serviceDir,
|
|
5952
6065
|
dependencyEdits: [],
|
|
5953
6066
|
entrypointEdits: [],
|
|
5954
|
-
envEdits: []
|
|
6067
|
+
envEdits: [],
|
|
6068
|
+
generatedFiles: []
|
|
5955
6069
|
};
|
|
5956
6070
|
if (!pkg) return empty;
|
|
6071
|
+
const entryFile = await resolveEntry(serviceDir, pkg);
|
|
6072
|
+
if (!entryFile) {
|
|
6073
|
+
return { ...empty, libOnly: true };
|
|
6074
|
+
}
|
|
6075
|
+
const flavor = dispatchEntry(entryFile, pkg);
|
|
6076
|
+
const otelInitFile = import_node_path38.default.join(import_node_path38.default.dirname(entryFile), otelInitFilename(flavor));
|
|
6077
|
+
const envNeatFile = import_node_path38.default.join(serviceDir, ".env.neat");
|
|
5957
6078
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5958
6079
|
const dependencyEdits = [];
|
|
5959
6080
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -5966,14 +6087,37 @@ async function plan(serviceDir) {
|
|
|
5966
6087
|
});
|
|
5967
6088
|
}
|
|
5968
6089
|
const entrypointEdits = [];
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
const
|
|
5972
|
-
|
|
5973
|
-
|
|
6090
|
+
try {
|
|
6091
|
+
const raw = await import_node_fs23.promises.readFile(entryFile, "utf8");
|
|
6092
|
+
const lines = raw.split(/\r?\n/);
|
|
6093
|
+
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
6094
|
+
if (!lineIsOtelInjection(firstReal)) {
|
|
6095
|
+
const inject = injectionLine(flavor, entryFile, otelInitFile);
|
|
6096
|
+
entrypointEdits.push({
|
|
6097
|
+
file: entryFile,
|
|
6098
|
+
before: firstReal,
|
|
6099
|
+
after: inject
|
|
6100
|
+
});
|
|
5974
6101
|
}
|
|
6102
|
+
} catch {
|
|
6103
|
+
return { ...empty, libOnly: true };
|
|
6104
|
+
}
|
|
6105
|
+
const generatedFiles = [];
|
|
6106
|
+
if (!await exists2(otelInitFile)) {
|
|
6107
|
+
generatedFiles.push({
|
|
6108
|
+
file: otelInitFile,
|
|
6109
|
+
contents: otelInitContents(flavor),
|
|
6110
|
+
skipIfExists: true
|
|
6111
|
+
});
|
|
5975
6112
|
}
|
|
5976
|
-
if (
|
|
6113
|
+
if (!await exists2(envNeatFile)) {
|
|
6114
|
+
generatedFiles.push({
|
|
6115
|
+
file: envNeatFile,
|
|
6116
|
+
contents: renderEnvNeat(pkg.name ?? import_node_path38.default.basename(serviceDir)),
|
|
6117
|
+
skipIfExists: true
|
|
6118
|
+
});
|
|
6119
|
+
}
|
|
6120
|
+
if (dependencyEdits.length === 0 && entrypointEdits.length === 0 && generatedFiles.length === 0) {
|
|
5977
6121
|
return empty;
|
|
5978
6122
|
}
|
|
5979
6123
|
return {
|
|
@@ -5981,53 +6125,129 @@ async function plan(serviceDir) {
|
|
|
5981
6125
|
serviceDir,
|
|
5982
6126
|
dependencyEdits,
|
|
5983
6127
|
entrypointEdits,
|
|
5984
|
-
envEdits: [OTEL_ENV]
|
|
6128
|
+
envEdits: [OTEL_ENV],
|
|
6129
|
+
generatedFiles,
|
|
6130
|
+
entryFile,
|
|
6131
|
+
libOnly: false
|
|
5985
6132
|
};
|
|
5986
6133
|
}
|
|
6134
|
+
function isAllowedWritePath(serviceDir, target) {
|
|
6135
|
+
const rel = import_node_path38.default.relative(serviceDir, target);
|
|
6136
|
+
if (rel.startsWith("..")) return false;
|
|
6137
|
+
const base = import_node_path38.default.basename(target);
|
|
6138
|
+
if (base === "package.json") return true;
|
|
6139
|
+
if (base === ".env.neat") return true;
|
|
6140
|
+
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
6141
|
+
return false;
|
|
6142
|
+
}
|
|
6143
|
+
async function writeAtomic(file, contents) {
|
|
6144
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
6145
|
+
await import_node_fs23.promises.writeFile(tmp, contents, "utf8");
|
|
6146
|
+
await import_node_fs23.promises.rename(tmp, file);
|
|
6147
|
+
}
|
|
5987
6148
|
async function apply(installPlan) {
|
|
5988
|
-
const
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6149
|
+
const { serviceDir } = installPlan;
|
|
6150
|
+
if (installPlan.libOnly) {
|
|
6151
|
+
return {
|
|
6152
|
+
serviceDir,
|
|
6153
|
+
outcome: "lib-only",
|
|
6154
|
+
reason: "no resolvable entry point",
|
|
6155
|
+
writtenFiles: []
|
|
6156
|
+
};
|
|
6157
|
+
}
|
|
6158
|
+
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0) {
|
|
6159
|
+
return {
|
|
6160
|
+
serviceDir,
|
|
6161
|
+
outcome: "already-instrumented",
|
|
6162
|
+
writtenFiles: []
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
6165
|
+
const allTargets = /* @__PURE__ */ new Set();
|
|
6166
|
+
for (const d of installPlan.dependencyEdits) allTargets.add(d.file);
|
|
6167
|
+
for (const e of installPlan.entrypointEdits) allTargets.add(e.file);
|
|
6168
|
+
for (const g of installPlan.generatedFiles ?? []) allTargets.add(g.file);
|
|
6169
|
+
for (const target of allTargets) {
|
|
6170
|
+
const isEntryEdit = installPlan.entrypointEdits.some((e) => e.file === target);
|
|
6171
|
+
if (isEntryEdit) continue;
|
|
6172
|
+
if (!isAllowedWritePath(serviceDir, target)) {
|
|
6173
|
+
throw new Error(
|
|
6174
|
+
`javascript installer: refusing to write outside the allowed path set (ADR-069 \xA77): ${target}`
|
|
6175
|
+
);
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
5992
6178
|
const originals = /* @__PURE__ */ new Map();
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
6179
|
+
const createdFiles = [];
|
|
6180
|
+
for (const target of allTargets) {
|
|
6181
|
+
if (await exists2(target)) {
|
|
6182
|
+
try {
|
|
6183
|
+
originals.set(target, await import_node_fs23.promises.readFile(target, "utf8"));
|
|
6184
|
+
} catch {
|
|
6185
|
+
}
|
|
5997
6186
|
}
|
|
5998
6187
|
}
|
|
6188
|
+
const writtenFiles = [];
|
|
5999
6189
|
try {
|
|
6000
|
-
|
|
6001
|
-
|
|
6190
|
+
const manifestTargets = installPlan.dependencyEdits.reduce((acc, e) => {
|
|
6191
|
+
acc.add(e.file);
|
|
6192
|
+
return acc;
|
|
6193
|
+
}, /* @__PURE__ */ new Set());
|
|
6194
|
+
for (const file of manifestTargets) {
|
|
6195
|
+
const raw = originals.get(file);
|
|
6196
|
+
if (raw === void 0) {
|
|
6197
|
+
throw new Error(`javascript installer: cannot read ${file} during apply`);
|
|
6198
|
+
}
|
|
6002
6199
|
const pkg = JSON.parse(raw);
|
|
6003
6200
|
pkg.dependencies = pkg.dependencies ?? {};
|
|
6004
6201
|
for (const dep of installPlan.dependencyEdits) {
|
|
6005
6202
|
if (dep.file !== file) continue;
|
|
6006
6203
|
if (dep.kind === "add") {
|
|
6007
|
-
|
|
6204
|
+
if (!(dep.name in (pkg.dependencies ?? {}))) {
|
|
6205
|
+
pkg.dependencies[dep.name] = dep.version;
|
|
6206
|
+
}
|
|
6008
6207
|
} else {
|
|
6009
6208
|
delete pkg.dependencies[dep.name];
|
|
6010
6209
|
}
|
|
6011
6210
|
}
|
|
6012
|
-
for (const ep of installPlan.entrypointEdits) {
|
|
6013
|
-
if (ep.file !== file) continue;
|
|
6014
|
-
pkg.scripts = pkg.scripts ?? {};
|
|
6015
|
-
if (pkg.scripts.start === ep.before) {
|
|
6016
|
-
pkg.scripts.start = ep.after;
|
|
6017
|
-
}
|
|
6018
|
-
}
|
|
6019
6211
|
const newRaw = JSON.stringify(pkg, null, 2) + "\n";
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6212
|
+
await writeAtomic(file, newRaw);
|
|
6213
|
+
writtenFiles.push(file);
|
|
6214
|
+
}
|
|
6215
|
+
for (const gen of installPlan.generatedFiles ?? []) {
|
|
6216
|
+
if (gen.skipIfExists && await exists2(gen.file)) {
|
|
6217
|
+
continue;
|
|
6218
|
+
}
|
|
6219
|
+
await writeAtomic(gen.file, gen.contents);
|
|
6220
|
+
if (!originals.has(gen.file)) createdFiles.push(gen.file);
|
|
6221
|
+
writtenFiles.push(gen.file);
|
|
6222
|
+
}
|
|
6223
|
+
for (const ep of installPlan.entrypointEdits) {
|
|
6224
|
+
const raw = originals.get(ep.file);
|
|
6225
|
+
if (raw === void 0) {
|
|
6226
|
+
throw new Error(`javascript installer: cannot read entry ${ep.file} during apply`);
|
|
6227
|
+
}
|
|
6228
|
+
const lines = raw.split(/\r?\n/);
|
|
6229
|
+
const hasShebang = lines[0]?.startsWith("#!") ?? false;
|
|
6230
|
+
const insertAt = hasShebang ? 1 : 0;
|
|
6231
|
+
const firstReal = lines[insertAt] ?? "";
|
|
6232
|
+
if (lineIsOtelInjection(firstReal)) continue;
|
|
6233
|
+
lines.splice(insertAt, 0, ep.after);
|
|
6234
|
+
const newRaw = lines.join("\n");
|
|
6235
|
+
await writeAtomic(ep.file, newRaw);
|
|
6236
|
+
writtenFiles.push(ep.file);
|
|
6023
6237
|
}
|
|
6024
6238
|
} catch (err) {
|
|
6025
|
-
await rollback(installPlan, originals);
|
|
6239
|
+
await rollback(installPlan, originals, createdFiles);
|
|
6026
6240
|
throw err;
|
|
6027
6241
|
}
|
|
6242
|
+
return {
|
|
6243
|
+
serviceDir,
|
|
6244
|
+
outcome: "instrumented",
|
|
6245
|
+
writtenFiles
|
|
6246
|
+
};
|
|
6028
6247
|
}
|
|
6029
|
-
async function rollback(installPlan, originals) {
|
|
6248
|
+
async function rollback(installPlan, originals, createdFiles) {
|
|
6030
6249
|
const restored = [];
|
|
6250
|
+
const removed = [];
|
|
6031
6251
|
for (const [file, raw] of originals.entries()) {
|
|
6032
6252
|
try {
|
|
6033
6253
|
await import_node_fs23.promises.writeFile(file, raw, "utf8");
|
|
@@ -6035,6 +6255,13 @@ async function rollback(installPlan, originals) {
|
|
|
6035
6255
|
} catch {
|
|
6036
6256
|
}
|
|
6037
6257
|
}
|
|
6258
|
+
for (const file of createdFiles) {
|
|
6259
|
+
try {
|
|
6260
|
+
await import_node_fs23.promises.unlink(file);
|
|
6261
|
+
removed.push(file);
|
|
6262
|
+
} catch {
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6038
6265
|
const lines = [
|
|
6039
6266
|
"# neat-rollback.patch",
|
|
6040
6267
|
"",
|
|
@@ -6042,6 +6269,7 @@ async function rollback(installPlan, originals) {
|
|
|
6042
6269
|
"# Files listed below were restored to their pre-apply contents.",
|
|
6043
6270
|
"",
|
|
6044
6271
|
...restored.map((f) => `restored: ${f}`),
|
|
6272
|
+
...removed.map((f) => `removed: ${f}`),
|
|
6045
6273
|
""
|
|
6046
6274
|
];
|
|
6047
6275
|
const rollbackPath = import_node_path38.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
@@ -6067,7 +6295,7 @@ var OTEL_ENV2 = {
|
|
|
6067
6295
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
6068
6296
|
value: "http://localhost:4318"
|
|
6069
6297
|
};
|
|
6070
|
-
async function
|
|
6298
|
+
async function exists3(p) {
|
|
6071
6299
|
try {
|
|
6072
6300
|
await import_node_fs24.promises.stat(p);
|
|
6073
6301
|
return true;
|
|
@@ -6078,7 +6306,7 @@ async function exists2(p) {
|
|
|
6078
6306
|
async function detect2(serviceDir) {
|
|
6079
6307
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
6080
6308
|
for (const m of markers) {
|
|
6081
|
-
if (await
|
|
6309
|
+
if (await exists3(import_node_path39.default.join(serviceDir, m))) return true;
|
|
6082
6310
|
}
|
|
6083
6311
|
return false;
|
|
6084
6312
|
}
|
|
@@ -6089,7 +6317,7 @@ function reqPackageName(line) {
|
|
|
6089
6317
|
}
|
|
6090
6318
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
6091
6319
|
const file = import_node_path39.default.join(serviceDir, "requirements.txt");
|
|
6092
|
-
if (!await
|
|
6320
|
+
if (!await exists3(file)) return null;
|
|
6093
6321
|
const raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
6094
6322
|
const presentNames = new Set(
|
|
6095
6323
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
@@ -6099,7 +6327,7 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
6099
6327
|
}
|
|
6100
6328
|
async function planProcfileEdits(serviceDir) {
|
|
6101
6329
|
const procfile = import_node_path39.default.join(serviceDir, "Procfile");
|
|
6102
|
-
if (!await
|
|
6330
|
+
if (!await exists3(procfile)) return [];
|
|
6103
6331
|
const raw = await import_node_fs24.promises.readFile(procfile, "utf8");
|
|
6104
6332
|
const edits = [];
|
|
6105
6333
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -6166,10 +6394,13 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
6166
6394
|
await import_node_fs24.promises.rename(tmp, procfile);
|
|
6167
6395
|
}
|
|
6168
6396
|
async function apply2(installPlan) {
|
|
6397
|
+
const { serviceDir } = installPlan;
|
|
6169
6398
|
const touched = /* @__PURE__ */ new Set();
|
|
6170
6399
|
for (const e of installPlan.dependencyEdits) touched.add(e.file);
|
|
6171
6400
|
for (const e of installPlan.entrypointEdits) touched.add(e.file);
|
|
6172
|
-
if (touched.size === 0)
|
|
6401
|
+
if (touched.size === 0) {
|
|
6402
|
+
return { serviceDir, outcome: "already-instrumented", writtenFiles: [] };
|
|
6403
|
+
}
|
|
6173
6404
|
const originals = /* @__PURE__ */ new Map();
|
|
6174
6405
|
for (const file of touched) {
|
|
6175
6406
|
try {
|
|
@@ -6177,6 +6408,7 @@ async function apply2(installPlan) {
|
|
|
6177
6408
|
} catch {
|
|
6178
6409
|
}
|
|
6179
6410
|
}
|
|
6411
|
+
const writtenFiles = [];
|
|
6180
6412
|
try {
|
|
6181
6413
|
for (const file of touched) {
|
|
6182
6414
|
const raw = originals.get(file);
|
|
@@ -6186,16 +6418,23 @@ async function apply2(installPlan) {
|
|
|
6186
6418
|
const base = import_node_path39.default.basename(file);
|
|
6187
6419
|
if (base === "requirements.txt") {
|
|
6188
6420
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
6189
|
-
if (edits.length > 0)
|
|
6421
|
+
if (edits.length > 0) {
|
|
6422
|
+
await applyRequirementsTxt(file, edits, raw);
|
|
6423
|
+
writtenFiles.push(file);
|
|
6424
|
+
}
|
|
6190
6425
|
} else if (base === "Procfile") {
|
|
6191
6426
|
const edits = installPlan.entrypointEdits.filter((e) => e.file === file);
|
|
6192
|
-
if (edits.length > 0)
|
|
6427
|
+
if (edits.length > 0) {
|
|
6428
|
+
await applyProcfile(file, edits, raw);
|
|
6429
|
+
writtenFiles.push(file);
|
|
6430
|
+
}
|
|
6193
6431
|
}
|
|
6194
6432
|
}
|
|
6195
6433
|
} catch (err) {
|
|
6196
6434
|
await rollback2(installPlan, originals);
|
|
6197
6435
|
throw err;
|
|
6198
6436
|
}
|
|
6437
|
+
return { serviceDir, outcome: "instrumented", writtenFiles };
|
|
6199
6438
|
}
|
|
6200
6439
|
async function rollback2(installPlan, originals) {
|
|
6201
6440
|
const restored = [];
|
|
@@ -6228,7 +6467,7 @@ var pythonInstaller = {
|
|
|
6228
6467
|
// src/installers/shared.ts
|
|
6229
6468
|
init_cjs_shims();
|
|
6230
6469
|
function isEmptyPlan(plan3) {
|
|
6231
|
-
return plan3.dependencyEdits.length === 0 && plan3.entrypointEdits.length === 0 && plan3.envEdits.length === 0;
|
|
6470
|
+
return plan3.dependencyEdits.length === 0 && plan3.entrypointEdits.length === 0 && plan3.envEdits.length === 0 && (plan3.generatedFiles?.length ?? 0) === 0;
|
|
6232
6471
|
}
|
|
6233
6472
|
|
|
6234
6473
|
// src/installers/index.ts
|
|
@@ -6270,8 +6509,18 @@ function renderPatch(sections) {
|
|
|
6270
6509
|
const { installer, plan: plan3 } = section;
|
|
6271
6510
|
lines.push(`## ${installer} (${plan3.language}) \u2014 ${plan3.serviceDir}`);
|
|
6272
6511
|
lines.push("");
|
|
6512
|
+
if (plan3.libOnly) {
|
|
6513
|
+
lines.push("### skipped \u2014 no resolvable entry point (lib-only)");
|
|
6514
|
+
lines.push("");
|
|
6515
|
+
continue;
|
|
6516
|
+
}
|
|
6517
|
+
if (plan3.entryFile) {
|
|
6518
|
+
lines.push(`entry: ${plan3.entryFile}`);
|
|
6519
|
+
lines.push("");
|
|
6520
|
+
}
|
|
6273
6521
|
if (plan3.dependencyEdits.length > 0) {
|
|
6274
6522
|
lines.push("### dependencies");
|
|
6523
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
6275
6524
|
for (const dep of plan3.dependencyEdits) {
|
|
6276
6525
|
const base = dep.file.split(/[\\/]/).pop() ?? dep.file;
|
|
6277
6526
|
if (FORBIDDEN_LOCKFILES.has(base)) {
|
|
@@ -6279,24 +6528,41 @@ function renderPatch(sections) {
|
|
|
6279
6528
|
`installer "${installer}" produced a dependency edit against a lockfile (${dep.file}); lockfiles must never be touched (ADR-047).`
|
|
6280
6529
|
);
|
|
6281
6530
|
}
|
|
6282
|
-
|
|
6531
|
+
const existing = byFile.get(dep.file) ?? [];
|
|
6532
|
+
existing.push(dep);
|
|
6533
|
+
byFile.set(dep.file, existing);
|
|
6534
|
+
}
|
|
6535
|
+
for (const [file, deps] of byFile) {
|
|
6536
|
+
lines.push(`--- ${file}`);
|
|
6537
|
+
for (const dep of deps) {
|
|
6538
|
+
lines.push(`+ "${dep.name}": "${dep.version}"`);
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
lines.push("");
|
|
6542
|
+
}
|
|
6543
|
+
if (plan3.generatedFiles && plan3.generatedFiles.length > 0) {
|
|
6544
|
+
lines.push("### generated files");
|
|
6545
|
+
for (const gen of plan3.generatedFiles) {
|
|
6546
|
+
lines.push(`--- (new file) ${gen.file}`);
|
|
6547
|
+
for (const ln of gen.contents.split(/\r?\n/)) {
|
|
6548
|
+
lines.push(`+ ${ln}`);
|
|
6549
|
+
}
|
|
6283
6550
|
}
|
|
6284
6551
|
lines.push("");
|
|
6285
6552
|
}
|
|
6286
6553
|
if (plan3.entrypointEdits.length > 0) {
|
|
6287
|
-
lines.push("###
|
|
6554
|
+
lines.push("### entry-point injection");
|
|
6288
6555
|
for (const e of plan3.entrypointEdits) {
|
|
6289
|
-
lines.push(
|
|
6290
|
-
lines.push(
|
|
6291
|
-
lines.push(`
|
|
6556
|
+
lines.push(`--- ${e.file}`);
|
|
6557
|
+
lines.push(`+ ${e.after}`);
|
|
6558
|
+
lines.push(` ${e.before}`);
|
|
6292
6559
|
}
|
|
6293
6560
|
lines.push("");
|
|
6294
6561
|
}
|
|
6295
6562
|
if (plan3.envEdits.length > 0) {
|
|
6296
|
-
lines.push("### env");
|
|
6563
|
+
lines.push("### env (written to <package-dir>/.env.neat)");
|
|
6297
6564
|
for (const env of plan3.envEdits) {
|
|
6298
|
-
|
|
6299
|
-
lines.push(`- ${env.key}=${env.value} \u2192 ${target}`);
|
|
6565
|
+
lines.push(`- ${env.key}=${env.value}`);
|
|
6300
6566
|
}
|
|
6301
6567
|
lines.push("");
|
|
6302
6568
|
}
|
|
@@ -7045,7 +7311,7 @@ async function buildPatchSections(services) {
|
|
|
7045
7311
|
const installer = await pickInstaller(svc.dir);
|
|
7046
7312
|
if (!installer) continue;
|
|
7047
7313
|
const plan3 = await installer.plan(svc.dir);
|
|
7048
|
-
if (isEmptyPlan(plan3)) continue;
|
|
7314
|
+
if (isEmptyPlan(plan3) && !plan3.libOnly) continue;
|
|
7049
7315
|
sections.push({ installer: installer.name, plan: plan3 });
|
|
7050
7316
|
}
|
|
7051
7317
|
return sections;
|
|
@@ -7098,14 +7364,28 @@ async function runInit(opts) {
|
|
|
7098
7364
|
}
|
|
7099
7365
|
if (!opts.noInstall) {
|
|
7100
7366
|
if (opts.apply) {
|
|
7367
|
+
let instrumented = 0;
|
|
7368
|
+
let alreadyInstrumented = 0;
|
|
7369
|
+
let libOnly = 0;
|
|
7101
7370
|
for (const section of sections) {
|
|
7102
7371
|
const installer = INSTALLERS.find((i) => i.name === section.installer);
|
|
7103
7372
|
if (!installer) continue;
|
|
7104
|
-
await installer.apply(section.plan);
|
|
7373
|
+
const outcome = await installer.apply(section.plan);
|
|
7374
|
+
if (outcome.outcome === "instrumented") {
|
|
7375
|
+
instrumented++;
|
|
7376
|
+
for (const f of outcome.writtenFiles) written.push(f);
|
|
7377
|
+
} else if (outcome.outcome === "already-instrumented") {
|
|
7378
|
+
alreadyInstrumented++;
|
|
7379
|
+
} else if (outcome.outcome === "lib-only") {
|
|
7380
|
+
libOnly++;
|
|
7381
|
+
}
|
|
7105
7382
|
}
|
|
7106
7383
|
if (sections.length > 0) {
|
|
7107
7384
|
console.log("");
|
|
7108
|
-
console.log(
|
|
7385
|
+
console.log(
|
|
7386
|
+
`apply: instrumented ${instrumented}, already-instrumented ${alreadyInstrumented}, lib-only ${libOnly}`
|
|
7387
|
+
);
|
|
7388
|
+
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
7109
7389
|
}
|
|
7110
7390
|
} else {
|
|
7111
7391
|
await import_node_fs25.promises.writeFile(patchPath, patch, "utf8");
|