nexarch 0.12.14 → 0.12.15

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.
@@ -88,8 +88,28 @@ function loadState(statePath, dir) {
88
88
  return { document: parseTerraformStateBuffer(stdout), source: "terraform show -json" };
89
89
  }
90
90
  catch (error) {
91
- const detail = error instanceof Error ? error.message : String(error);
92
- throw new Error(`Could not read Terraform state. Run inside the infrastructure repo with terraform available, or pass --state <file>.\n ${detail.split("\n")[0]}`);
91
+ // `error.message` is only ever "Command failed: terraform show -json",
92
+ // which names the command that failed and not one thing about why. The
93
+ // reason — no state, a backend that needs credentials, a lock, a directory
94
+ // owned by another user — is on stderr, and reporting the wrapper instead
95
+ // of the cause leaves the caller with nothing to act on. That matters more
96
+ // here than usual: the caller is normally an agent, and an agent given
97
+ // "Command failed" can only report failure, while an agent given
98
+ // "Error: Backend initialization required" can run terraform init.
99
+ const failure = error;
100
+ const stderr = (typeof failure.stderr === "string" ? failure.stderr : failure.stderr?.toString("utf8") ?? "").trim();
101
+ const reason = stderr || failure.message || String(error);
102
+ // Terraform's own errors are already formatted for a reader; keep enough
103
+ // lines to carry the explanation without pasting an entire trace.
104
+ const detail = reason
105
+ .split("\n")
106
+ .filter((line) => line.trim().length > 0)
107
+ .slice(0, 8)
108
+ .map((line) => ` ${line.trim()}`)
109
+ .join("\n");
110
+ throw new Error(`Could not read Terraform state in ${dir}.\n` +
111
+ ` Run inside the infrastructure repo with terraform available, or pass --state <file>.\n` +
112
+ `\n terraform said:\n${detail}`);
93
113
  }
94
114
  }
95
115
  function resolveEnvironment(projection, override, dir, rootModuleDir) {
@@ -183,7 +183,14 @@ export function discoverRootModules(repoDir) {
183
183
  if (/^\s*provider\s+"[a-z0-9_]+"\s*\{/m.test(content))
184
184
  hasProvider = true;
185
185
  }
186
- const initialised = existsSync(join(dir, ".terraform"));
186
+ // A `.terraform` directory only proves provider plugins were downloaded.
187
+ // The backend — the thing `terraform show` needs — is initialised when
188
+ // `.terraform/terraform.tfstate` records its configuration. Treating the
189
+ // directory as proof reports a root as ready, the caller runs ingest, and
190
+ // Terraform answers "Backend initialization required" from somewhere the
191
+ // caller was told not to expect it.
192
+ const hasPluginDir = existsSync(join(dir, ".terraform"));
193
+ const initialised = existsSync(join(dir, ".terraform", "terraform.tfstate"));
187
194
  const hasTfvars = entries.some((entry) => entry.endsWith(".tfvars") || entry.endsWith(".tfvars.json"));
188
195
  const environmentHint = environmentFromPath(relative);
189
196
  const underStacksDir = /(^|[\\/])(environments|envs|stacks|live)([\\/]|$)/i.test(relative);
@@ -193,11 +200,13 @@ export function discoverRootModules(repoDir) {
193
200
  signals.push("provider configuration");
194
201
  if (initialised)
195
202
  signals.push("initialised");
203
+ else if (hasPluginDir)
204
+ signals.push("providers downloaded, backend not initialised");
196
205
  if (hasTfvars)
197
206
  signals.push("tfvars");
198
207
  if (underStacksDir)
199
208
  signals.push("stacks directory");
200
- if (hasBackend || hasProvider || initialised || (underStacksDir && hasTfvars) || (underStacksDir && environmentHint)) {
209
+ if (hasBackend || hasProvider || initialised || hasPluginDir || (underStacksDir && hasTfvars) || (underStacksDir && environmentHint)) {
201
210
  found.push({ dir, relative: relative || ".", signals, environmentHint, initialised, usesLocalBackend: hasLocalBackend });
202
211
  // A root module's subdirectories are its own modules, not further roots.
203
212
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",