@cairnvibe/indexer 0.2.4 → 0.2.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/cli.js +38 -0
- package/dist/manifest.js +18 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,43 @@ const diff_1 = require("./diff");
|
|
|
18
18
|
const docs_1 = require("./docs");
|
|
19
19
|
const init_1 = require("./init");
|
|
20
20
|
const setup_1 = require("./setup");
|
|
21
|
+
/**
|
|
22
|
+
* A local `cairn build`/`npx cairn build` run (as opposed to a hosting
|
|
23
|
+
* platform's own build step, which injects its configured env vars
|
|
24
|
+
* directly into process.env) only ever sees the plain shell environment —
|
|
25
|
+
* it does NOT get a bundler's or framework's automatic .env/.env.local
|
|
26
|
+
* loading, since that's scoped to that tool's own process, not whatever
|
|
27
|
+
* invokes this CLI. A real key sitting in .env was invisible here even
|
|
28
|
+
* though `cairn setup` had written it there — the exact same class of gap
|
|
29
|
+
* already fixed for cairn-realtime's own CLI (realtime-cli.ts), for the
|
|
30
|
+
* exact same reason. No new dependency: .env is a plain KEY=VALUE format.
|
|
31
|
+
* .env.local loads first so it wins (matches Next.js's own precedence);
|
|
32
|
+
* nothing here overrides a real, already-set process.env value (a shell
|
|
33
|
+
* export, or a platform's own injected env vars) — file contents only
|
|
34
|
+
* ever fill a gap, never override something actually set.
|
|
35
|
+
*/
|
|
36
|
+
function loadDotEnv(dir) {
|
|
37
|
+
for (const filename of [".env.local", ".env"]) {
|
|
38
|
+
const filePath = node_path_1.default.join(dir, filename);
|
|
39
|
+
if (!node_fs_1.default.existsSync(filePath))
|
|
40
|
+
continue;
|
|
41
|
+
for (const line of node_fs_1.default.readFileSync(filePath, "utf8").split("\n")) {
|
|
42
|
+
const trimmed = line.trim();
|
|
43
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
44
|
+
continue;
|
|
45
|
+
const eq = trimmed.indexOf("=");
|
|
46
|
+
if (eq === -1)
|
|
47
|
+
continue;
|
|
48
|
+
const key = trimmed.slice(0, eq).trim();
|
|
49
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
50
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
51
|
+
value = value.slice(1, -1);
|
|
52
|
+
}
|
|
53
|
+
if (process.env[key] === undefined)
|
|
54
|
+
process.env[key] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
21
58
|
function parseArgs(rest) {
|
|
22
59
|
const positional = [];
|
|
23
60
|
const flags = {};
|
|
@@ -37,6 +74,7 @@ async function main() {
|
|
|
37
74
|
const [command, ...rest] = process.argv.slice(2);
|
|
38
75
|
const { positional, flags } = parseArgs(rest);
|
|
39
76
|
const dir = positional[0] ?? ".";
|
|
77
|
+
loadDotEnv(dir);
|
|
40
78
|
if (command === "scan") {
|
|
41
79
|
const facts = (0, l1_scan_1.scanL1)(dir);
|
|
42
80
|
process.stdout.write(JSON.stringify(facts, null, 2) + "\n");
|
package/dist/manifest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.assembleManifest = assembleManifest;
|
|
4
|
+
exports.parseApiCall = parseApiCall;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
5
6
|
function assembleManifest(rootDir, facts, l2, l3) {
|
|
6
7
|
const globalElements = facts.frameworkElements.map((el) => toManifestElement(el, l3.globalElements.find((e) => e.id === el.id), "present in the root layout"));
|
|
@@ -29,13 +30,23 @@ function assembleManifest(rootDir, facts, l2, l3) {
|
|
|
29
30
|
}
|
|
30
31
|
const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
31
32
|
/**
|
|
32
|
-
* Turns l1-scan's traced `"POST /api/items
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* Turns l1-scan's traced `"POST /api/items"`-shaped string into structured,
|
|
34
|
+
* executable data — this is what lets a `do` action actually run
|
|
35
|
+
* (verb-executor.ts) instead of only ever describing itself. Only real
|
|
36
|
+
* mutating methods count as an action; `"navigate ..."` (a Link) and a bare
|
|
37
|
+
* GET aren't "do" material — see ApiCallSchema's doc comment in
|
|
37
38
|
* @cairnvibe/core for the safety reasoning (bounded to calls a human
|
|
38
39
|
* developer already wrote and shipped, nothing invented at runtime).
|
|
40
|
+
*
|
|
41
|
+
* Only accepts a clean, static, same-origin relative path. l1-scan.ts's URL
|
|
42
|
+
* capture falls back to a call's raw source text when the first argument
|
|
43
|
+
* isn't a plain string literal — for a template literal (a per-row action
|
|
44
|
+
* built as `` `/api/items/${id}/archive` ``) that's literal backticks and a
|
|
45
|
+
* "${...}" hole, not a real fetchable URL; for a bare identifier or some
|
|
46
|
+
* other expression it isn't a URL at all. Rejecting both instead of
|
|
47
|
+
* guessing at resolving them is what keeps this bounded to calls that are
|
|
48
|
+
* actually safe to fire as-is — see ApiCallSchema's doc comment for the
|
|
49
|
+
* real gap this leaves (per-row actions aren't auto-executable yet).
|
|
39
50
|
*/
|
|
40
51
|
function parseApiCall(handlerCall) {
|
|
41
52
|
if (!handlerCall)
|
|
@@ -47,6 +58,8 @@ function parseApiCall(handlerCall) {
|
|
|
47
58
|
const url = handlerCall.slice(spaceIndex + 1);
|
|
48
59
|
if (!MUTATING_METHODS.has(method) || !url)
|
|
49
60
|
return null;
|
|
61
|
+
if (!/^\/[a-zA-Z0-9/_.-]*$/.test(url))
|
|
62
|
+
return null;
|
|
50
63
|
return { method: method, url };
|
|
51
64
|
}
|
|
52
65
|
function toManifestElement(el, elDesc, baseEvidence) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnvibe/indexer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Cairn's analyzer and installer (the `cairn` CLI) — scans Next.js source or crawls any running app, and scaffolds the backend either way.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": { "access": "public" },
|