@ankhorage/infra 3.1.1 → 3.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/infra
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f4ffc4b: Expose canonical project inspection, filtered generated runtime-environment access, and trusted project database URL resolution through `@ankhorage/infra/project`.
8
+
3
9
  ## 3.1.1
4
10
 
5
11
  ### Patch Changes
@@ -1,4 +1,6 @@
1
1
  export { type ResolvedInfraProject, resolveInfraProject as resolveInfraWorkspaceProject, } from '../project.js';
2
- export { type InfraSyncResult, resolveProjectInfrastructureTarget, syncProjectInfrastructure, } from '../projectInfrastructure.js';
2
+ export { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';
3
+ export { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';
4
+ export { type InfraProjectInspection, type InfraSyncResult, inspectProjectInfrastructure, resolveProjectInfrastructureTarget, syncProjectInfrastructure, } from '../projectInfrastructure.js';
3
5
  export { type InfraLifecycleScript, type InfraPortForwardInfo, InfraScriptExecutionError, type InfraScriptOutput, resolveProjectInfrastructurePortForward, runProjectInfrastructureLifecycle, } from '../runtime.js';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,eAAe,EACpB,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uCAAuC,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,oCAAoC,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,4BAA4B,EAC5B,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export { resolveInfraProject as resolveInfraWorkspaceProject, } from '../project.js';
2
- export { resolveProjectInfrastructureTarget, syncProjectInfrastructure, } from '../projectInfrastructure.js';
2
+ export { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';
3
+ export { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';
4
+ export { inspectProjectInfrastructure, resolveProjectInfrastructureTarget, syncProjectInfrastructure, } from '../projectInfrastructure.js';
3
5
  export { InfraScriptExecutionError, resolveProjectInfrastructurePortForward, runProjectInfrastructureLifecycle, } from '../runtime.js';
4
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGL,yBAAyB,EAEzB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC","sourcesContent":["export {\n type ResolvedInfraProject,\n resolveInfraProject as resolveInfraWorkspaceProject,\n} from '../project.js';\nexport {\n type InfraSyncResult,\n resolveProjectInfrastructureTarget,\n syncProjectInfrastructure,\n} from '../projectInfrastructure.js';\nexport {\n type InfraLifecycleScript,\n type InfraPortForwardInfo,\n InfraScriptExecutionError,\n type InfraScriptOutput,\n resolveProjectInfrastructurePortForward,\n runProjectInfrastructureLifecycle,\n} from '../runtime.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uCAAuC,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,oCAAoC,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EAGL,4BAA4B,EAC5B,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGL,yBAAyB,EAEzB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC","sourcesContent":["export {\n type ResolvedInfraProject,\n resolveInfraProject as resolveInfraWorkspaceProject,\n} from '../project.js';\nexport { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';\nexport { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';\nexport {\n type InfraProjectInspection,\n type InfraSyncResult,\n inspectProjectInfrastructure,\n resolveProjectInfrastructureTarget,\n syncProjectInfrastructure,\n} from '../projectInfrastructure.js';\nexport {\n type InfraLifecycleScript,\n type InfraPortForwardInfo,\n InfraScriptExecutionError,\n type InfraScriptOutput,\n resolveProjectInfrastructurePortForward,\n runProjectInfrastructureLifecycle,\n} from '../runtime.js';\n"]}
@@ -0,0 +1,5 @@
1
+ export declare function resolveProjectInfrastructureDatabaseUrl(args: {
2
+ readonly projectPath: string;
3
+ readonly target: string;
4
+ }): Promise<string | null>;
5
+ //# sourceMappingURL=projectDatabase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectDatabase.d.ts","sourceRoot":"","sources":["../src/projectDatabase.ts"],"names":[],"mappings":"AAUA,wBAAsB,uCAAuC,CAAC,IAAI,EAAE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAazB"}
@@ -0,0 +1,22 @@
1
+ import { readProjectInfrastructureEnvironment } from './projectEnvironment.js';
2
+ const DATABASE_URL_KEYS = [
3
+ 'ANKH_SECRET_STORE_DATABASE_URL',
4
+ 'SUPABASE_DB_URL',
5
+ 'POSTGRES_URL',
6
+ 'DB_URL',
7
+ 'DATABASE_URL',
8
+ ];
9
+ export async function resolveProjectInfrastructureDatabaseUrl(args) {
10
+ const environment = await readProjectInfrastructureEnvironment({
11
+ keys: DATABASE_URL_KEYS,
12
+ projectPath: args.projectPath,
13
+ target: args.target,
14
+ });
15
+ for (const key of DATABASE_URL_KEYS) {
16
+ const value = environment[key]?.trim();
17
+ if (value)
18
+ return value;
19
+ }
20
+ return null;
21
+ }
22
+ //# sourceMappingURL=projectDatabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectDatabase.js","sourceRoot":"","sources":["../src/projectDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oCAAoC,EAAE,MAAM,yBAAyB,CAAC;AAE/E,MAAM,iBAAiB,GAAG;IACxB,gCAAgC;IAChC,iBAAiB;IACjB,cAAc;IACd,QAAQ;IACR,cAAc;CACN,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,uCAAuC,CAAC,IAG7D;IACC,MAAM,WAAW,GAAG,MAAM,oCAAoC,CAAC;QAC7D,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;IAEH,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { readProjectInfrastructureEnvironment } from './projectEnvironment.js';\n\nconst DATABASE_URL_KEYS = [\n 'ANKH_SECRET_STORE_DATABASE_URL',\n 'SUPABASE_DB_URL',\n 'POSTGRES_URL',\n 'DB_URL',\n 'DATABASE_URL',\n] as const;\n\nexport async function resolveProjectInfrastructureDatabaseUrl(args: {\n readonly projectPath: string;\n readonly target: string;\n}): Promise<string | null> {\n const environment = await readProjectInfrastructureEnvironment({\n keys: DATABASE_URL_KEYS,\n projectPath: args.projectPath,\n target: args.target,\n });\n\n for (const key of DATABASE_URL_KEYS) {\n const value = environment[key]?.trim();\n if (value) return value;\n }\n\n return null;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export declare function readProjectInfrastructureEnvironment(args: {
2
+ readonly keys: readonly string[];
3
+ readonly projectPath: string;
4
+ readonly target: string;
5
+ }): Promise<Readonly<Record<string, string>>>;
6
+ //# sourceMappingURL=projectEnvironment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectEnvironment.d.ts","sourceRoot":"","sources":["../src/projectEnvironment.ts"],"names":[],"mappings":"AAGA,wBAAsB,oCAAoC,CAAC,IAAI,EAAE;IAC/D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAc5C"}
@@ -0,0 +1,45 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ export async function readProjectInfrastructureEnvironment(args) {
4
+ const infraRoot = path.join(args.projectPath, 'infra', args.target);
5
+ const envPath = path.join(infraRoot, '.env');
6
+ const fallbackPath = path.join(infraRoot, '.env.example');
7
+ const sourcePath = (await pathExists(envPath)) ? envPath : fallbackPath;
8
+ const environment = await readEnvironmentFile(sourcePath);
9
+ const selected = {};
10
+ for (const key of new Set(args.keys)) {
11
+ const value = environment.get(key);
12
+ if (value !== undefined)
13
+ selected[key] = value;
14
+ }
15
+ return selected;
16
+ }
17
+ async function readEnvironmentFile(filePath) {
18
+ const out = new Map();
19
+ const content = await fs.readFile(filePath, 'utf8');
20
+ for (const line of content.split(/\r?\n/u)) {
21
+ const trimmed = line.trim();
22
+ if (!trimmed || trimmed.startsWith('#'))
23
+ continue;
24
+ const separator = trimmed.indexOf('=');
25
+ if (separator <= 0)
26
+ continue;
27
+ const key = trimmed.slice(0, separator).trim();
28
+ const value = trimmed
29
+ .slice(separator + 1)
30
+ .trim()
31
+ .replace(/^['"]|['"]$/g, '');
32
+ out.set(key, value);
33
+ }
34
+ return out;
35
+ }
36
+ async function pathExists(filePath) {
37
+ try {
38
+ await fs.access(filePath);
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ //# sourceMappingURL=projectEnvironment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectEnvironment.js","sourceRoot":"","sources":["../src/projectEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,KAAK,UAAU,oCAAoC,CAAC,IAI1D;IACC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;IACxE,MAAM,WAAW,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAE5C,KAAK,MAAM,GAAG,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,KAAK,KAAK,SAAS;YAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACjD,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACjD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEpD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,SAAS,IAAI,CAAC;YAAE,SAAS;QAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,OAAO;aAClB,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;aACpB,IAAI,EAAE;aACN,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { promises as fs } from 'fs';\nimport path from 'path';\n\nexport async function readProjectInfrastructureEnvironment(args: {\n readonly keys: readonly string[];\n readonly projectPath: string;\n readonly target: string;\n}): Promise<Readonly<Record<string, string>>> {\n const infraRoot = path.join(args.projectPath, 'infra', args.target);\n const envPath = path.join(infraRoot, '.env');\n const fallbackPath = path.join(infraRoot, '.env.example');\n const sourcePath = (await pathExists(envPath)) ? envPath : fallbackPath;\n const environment = await readEnvironmentFile(sourcePath);\n const selected: Record<string, string> = {};\n\n for (const key of new Set(args.keys)) {\n const value = environment.get(key);\n if (value !== undefined) selected[key] = value;\n }\n\n return selected;\n}\n\nasync function readEnvironmentFile(filePath: string): Promise<Map<string, string>> {\n const out = new Map<string, string>();\n const content = await fs.readFile(filePath, 'utf8');\n\n for (const line of content.split(/\\r?\\n/u)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const separator = trimmed.indexOf('=');\n if (separator <= 0) continue;\n const key = trimmed.slice(0, separator).trim();\n const value = trimmed\n .slice(separator + 1)\n .trim()\n .replace(/^['\"]|['\"]$/g, '');\n out.set(key, value);\n }\n\n return out;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
@@ -8,12 +8,28 @@ export interface InfraSyncResult {
8
8
  readonly reason: string;
9
9
  };
10
10
  }
11
+ export interface InfraProjectInspection {
12
+ readonly generated: boolean;
13
+ readonly generatedAt: string | null;
14
+ readonly hasDeployment: boolean;
15
+ readonly target: string | null;
16
+ readonly trackedFiles: number;
17
+ readonly warnings: readonly string[];
18
+ readonly skipped?: {
19
+ readonly reason: string;
20
+ };
21
+ }
11
22
  export declare function syncProjectInfrastructure(args: {
12
23
  readonly generateInfrastructureImpl?: typeof generateInfrastructure;
13
24
  readonly manifest: AppManifest;
14
25
  readonly projectId: string;
15
26
  readonly projectPath: string;
16
27
  }): Promise<InfraSyncResult>;
28
+ export declare function inspectProjectInfrastructure(args: {
29
+ readonly manifest: AppManifest;
30
+ readonly projectId: string;
31
+ readonly projectPath: string;
32
+ }): Promise<InfraProjectInspection>;
17
33
  export declare function resolveProjectInfrastructureTarget(args: {
18
34
  readonly manifest: AppManifest;
19
35
  readonly projectPath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"projectInfrastructure.d.ts","sourceRoot":"","sources":["../src/projectInfrastructure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAcpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,QAAQ,CAAC,0BAA0B,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,OAAO,CAAC,eAAe,CAAC,CAiE3B;AAED,wBAAsB,kCAAkC,CAAC,IAAI,EAAE;IAC7D,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQzB;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUhF"}
1
+ {"version":3,"file":"projectInfrastructure.d.ts","sourceRoot":"","sources":["../src/projectInfrastructure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAcpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,QAAQ,CAAC,0BAA0B,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,OAAO,CAAC,eAAe,CAAC,CAiE3B;AAED,wBAAsB,4BAA4B,CAAC,IAAI,EAAE;IACvD,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA0BlC;AAED,wBAAsB,kCAAkC,CAAC,IAAI,EAAE;IAC7D,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQzB;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUhF"}
@@ -57,6 +57,31 @@ export async function syncProjectInfrastructure(args) {
57
57
  warnings: combinedWarnings,
58
58
  };
59
59
  }
60
+ export async function inspectProjectInfrastructure(args) {
61
+ const supportWarnings = validateInfraSupport(args.manifest.infra);
62
+ if (args.projectId === STUDIO_PROJECT_ID) {
63
+ return {
64
+ generated: false,
65
+ generatedAt: null,
66
+ hasDeployment: false,
67
+ target: null,
68
+ trackedFiles: 0,
69
+ warnings: supportWarnings,
70
+ skipped: {
71
+ reason: 'apps/studio is the dashboard and is not a generated app target.',
72
+ },
73
+ };
74
+ }
75
+ const ledger = await readInfraLedger(args.projectPath);
76
+ return {
77
+ generated: ledger !== null,
78
+ generatedAt: ledger?.generatedAt ?? null,
79
+ hasDeployment: args.manifest.infra.deployment !== undefined,
80
+ target: args.manifest.infra.deployment?.target ?? ledger?.target ?? null,
81
+ trackedFiles: ledger?.files.length ?? 0,
82
+ warnings: uniqueStrings([...supportWarnings, ...(ledger?.warnings ?? [])]),
83
+ };
84
+ }
60
85
  export async function resolveProjectInfrastructureTarget(args) {
61
86
  const manifestTarget = args.manifest.infra.deployment?.target;
62
87
  if (manifestTarget !== undefined) {
@@ -1 +1 @@
1
- {"version":3,"file":"projectInfrastructure.js","sourceRoot":"","sources":["../src/projectInfrastructure.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AppManifest } from '@ankhorage/contracts';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nimport { generateInfrastructure } from './index.js';\nimport { validateInfraSupport } from './infraValidation.js';\n\nconst INFRA_LEDGER_RELATIVE_PATH = '.ankh/infra-ledger.json';\nconst STUDIO_PROJECT_ID = 'studio';\n\ninterface InfraLedger {\n readonly schemaVersion: 1;\n readonly generatedAt: string;\n readonly target: string;\n readonly files: readonly string[];\n readonly warnings: readonly string[];\n}\n\nexport interface InfraSyncResult {\n readonly generated: number;\n readonly removed: number;\n readonly warnings: readonly string[];\n readonly skipped?: {\n readonly reason: string;\n };\n}\n\nexport async function syncProjectInfrastructure(args: {\n readonly generateInfrastructureImpl?: typeof generateInfrastructure;\n readonly manifest: AppManifest;\n readonly projectId: string;\n readonly projectPath: string;\n}): Promise<InfraSyncResult> {\n const supportWarnings = validateInfraSupport(args.manifest.infra);\n\n if (args.projectId === STUDIO_PROJECT_ID) {\n return {\n generated: 0,\n removed: 0,\n warnings: supportWarnings,\n skipped: {\n reason: 'apps/studio is the dashboard and is not a generated app target.',\n },\n };\n }\n\n const previousLedger = await readInfraLedger(args.projectPath);\n const previousFiles = new Set(previousLedger?.files ?? []);\n\n if (args.manifest.infra.deployment === undefined) {\n const removed = await removeFiles(args.projectPath, previousFiles);\n await removeInfraLedger(args.projectPath);\n return {\n generated: 0,\n removed,\n warnings: supportWarnings,\n };\n }\n\n const generated = (args.generateInfrastructureImpl ?? generateInfrastructure)(\n args.manifest.infra,\n {\n namespaceHint: args.projectId,\n appManifest: args.manifest,\n },\n );\n const combinedWarnings = uniqueStrings([...supportWarnings, ...generated.warnings]);\n const nextFiles = new Set<string>();\n\n for (const file of generated.files) {\n const outputPath = resolveProjectFile(args.projectPath, file.path);\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, file.content, 'utf8');\n\n if (file.executable === true) {\n await fs.chmod(outputPath, 0o755);\n }\n\n nextFiles.add(file.path);\n }\n\n const staleFiles = new Set([...previousFiles].filter((filePath) => !nextFiles.has(filePath)));\n const removed = await removeFiles(args.projectPath, staleFiles);\n\n await writeInfraLedger(args.projectPath, {\n schemaVersion: 1,\n generatedAt: new Date().toISOString(),\n target: generated.meta.target,\n files: [...nextFiles].sort(),\n warnings: combinedWarnings,\n });\n\n return {\n generated: generated.files.length,\n removed,\n warnings: combinedWarnings,\n };\n}\n\nexport async function resolveProjectInfrastructureTarget(args: {\n readonly manifest: AppManifest;\n readonly projectPath: string;\n}): Promise<string | null> {\n const manifestTarget = args.manifest.infra.deployment?.target;\n if (manifestTarget !== undefined) {\n return manifestTarget;\n }\n\n const ledger = await readInfraLedger(args.projectPath);\n return ledger?.target ?? null;\n}\n\nexport function resolveProjectFile(projectPath: string, filePath: string): string {\n const rootPath = path.resolve(projectPath);\n const resolvedPath = path.resolve(rootPath, filePath);\n const relativePath = path.relative(rootPath, resolvedPath);\n\n if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {\n throw new Error(`Invalid infra output path outside project root: ${filePath}`);\n }\n\n return resolvedPath;\n}\n\nasync function readInfraLedger(projectPath: string): Promise<InfraLedger | null> {\n const ledgerPath = path.join(projectPath, INFRA_LEDGER_RELATIVE_PATH);\n\n try {\n const rawLedger = await fs.readFile(ledgerPath, 'utf8');\n const parsedLedger: unknown = JSON.parse(rawLedger);\n return isInfraLedger(parsedLedger) ? parsedLedger : null;\n } catch {\n return null;\n }\n}\n\nasync function writeInfraLedger(projectPath: string, ledger: InfraLedger): Promise<void> {\n const ledgerPath = path.join(projectPath, INFRA_LEDGER_RELATIVE_PATH);\n await fs.mkdir(path.dirname(ledgerPath), { recursive: true });\n await fs.writeFile(ledgerPath, `${JSON.stringify(ledger, null, 2)}\\n`, 'utf8');\n}\n\nasync function removeInfraLedger(projectPath: string): Promise<void> {\n await fs.rm(path.join(projectPath, INFRA_LEDGER_RELATIVE_PATH), { force: true });\n}\n\nasync function removeFiles(projectPath: string, files: ReadonlySet<string>): Promise<number> {\n let removedFiles = 0;\n\n for (const filePath of files) {\n await fs.rm(resolveProjectFile(projectPath, filePath), { force: true });\n removedFiles += 1;\n }\n\n return removedFiles;\n}\n\nfunction isInfraLedger(value: unknown): value is InfraLedger {\n return (\n isRecord(value) &&\n value.schemaVersion === 1 &&\n typeof value.generatedAt === 'string' &&\n typeof value.target === 'string' &&\n Array.isArray(value.files) &&\n value.files.every((filePath) => typeof filePath === 'string') &&\n Array.isArray(value.warnings) &&\n value.warnings.every((warning) => typeof warning === 'string')\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction uniqueStrings(values: readonly string[]): readonly string[] {\n return [...new Set(values)];\n}\n"]}
1
+ {"version":3,"file":"projectInfrastructure.js","sourceRoot":"","sources":["../src/projectInfrastructure.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,0BAA0B,GAAG,yBAAyB,CAAC;AAC7D,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AA+BnC,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,IAK/C;IACC,MAAM,eAAe,GAAG,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElE,IAAI,IAAI,CAAC,SAAS,KAAK,iBAAiB,EAAE,CAAC;QACzC,OAAO;YACL,SAAS,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;YACV,QAAQ,EAAE,eAAe;YACzB,OAAO,EAAE;gBACP,MAAM,EAAE,iEAAiE;aAC1E;SACF,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAE3D,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;QACnE,MAAM,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC1C,OAAO;YACL,SAAS,EAAE,CAAC;YACZ,OAAO;YACP,QAAQ,EAAE,eAAe;SAC1B,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,0BAA0B,IAAI,sBAAsB,CAAC,CAC3E,IAAI,CAAC,QAAQ,CAAC,KAAK,EACnB;QACE,aAAa,EAAE,IAAI,CAAC,SAAS;QAC7B,WAAW,EAAE,IAAI,CAAC,QAAQ;KAC3B,CACF,CAAC;IACF,MAAM,gBAAgB,GAAG,aAAa,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAErD,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC7B,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9F,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAEhE,MAAM,gBAAgB,CAAC,IAAI,CAAC,WAAW,EAAE;QACvC,aAAa,EAAE,CAAC;QAChB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM;QAC7B,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE;QAC5B,QAAQ,EAAE,gBAAgB;KAC3B,CAAC,CAAC;IAEH,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM;QACjC,OAAO;QACP,QAAQ,EAAE,gBAAgB;KAC3B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,IAIlD;IACC,MAAM,eAAe,GAAG,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElE,IAAI,IAAI,CAAC,SAAS,KAAK,iBAAiB,EAAE,CAAC;QACzC,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,IAAI;YACjB,aAAa,EAAE,KAAK;YACpB,MAAM,EAAE,IAAI;YACZ,YAAY,EAAE,CAAC;YACf,QAAQ,EAAE,eAAe;YACzB,OAAO,EAAE;gBACP,MAAM,EAAE,iEAAiE;aAC1E;SACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACvD,OAAO;QACL,SAAS,EAAE,MAAM,KAAK,IAAI;QAC1B,WAAW,EAAE,MAAM,EAAE,WAAW,IAAI,IAAI;QACxC,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS;QAC3D,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,IAAI,IAAI;QACxE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;QACvC,QAAQ,EAAE,aAAa,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;KAC3E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kCAAkC,CAAC,IAGxD;IACC,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC;IAC9D,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACvD,OAAO,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,QAAgB;IACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAE3D,IAAI,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,mDAAmD,QAAQ,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,WAAmB;IAChD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,0BAA0B,CAAC,CAAC;IAEtE,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,YAAY,GAAY,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACpD,OAAO,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,WAAmB,EAAE,MAAmB;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,0BAA0B,CAAC,CAAC;IACtE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACjF,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IAClD,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,0BAA0B,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,WAAmB,EAAE,KAA0B;IACxE,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,EAAE,CAAC,EAAE,CAAC,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,YAAY,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,CACL,QAAQ,CAAC,KAAK,CAAC;QACf,KAAK,CAAC,aAAa,KAAK,CAAC;QACzB,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;QACrC,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAChC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAC1B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,QAAQ,KAAK,QAAQ,CAAC;QAC7D,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC7B,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAC/D,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED,SAAS,aAAa,CAAC,MAAyB;IAC9C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import type { AppManifest } from '@ankhorage/contracts';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nimport { generateInfrastructure } from './index.js';\nimport { validateInfraSupport } from './infraValidation.js';\n\nconst INFRA_LEDGER_RELATIVE_PATH = '.ankh/infra-ledger.json';\nconst STUDIO_PROJECT_ID = 'studio';\n\ninterface InfraLedger {\n readonly schemaVersion: 1;\n readonly generatedAt: string;\n readonly target: string;\n readonly files: readonly string[];\n readonly warnings: readonly string[];\n}\n\nexport interface InfraSyncResult {\n readonly generated: number;\n readonly removed: number;\n readonly warnings: readonly string[];\n readonly skipped?: {\n readonly reason: string;\n };\n}\n\nexport interface InfraProjectInspection {\n readonly generated: boolean;\n readonly generatedAt: string | null;\n readonly hasDeployment: boolean;\n readonly target: string | null;\n readonly trackedFiles: number;\n readonly warnings: readonly string[];\n readonly skipped?: {\n readonly reason: string;\n };\n}\n\nexport async function syncProjectInfrastructure(args: {\n readonly generateInfrastructureImpl?: typeof generateInfrastructure;\n readonly manifest: AppManifest;\n readonly projectId: string;\n readonly projectPath: string;\n}): Promise<InfraSyncResult> {\n const supportWarnings = validateInfraSupport(args.manifest.infra);\n\n if (args.projectId === STUDIO_PROJECT_ID) {\n return {\n generated: 0,\n removed: 0,\n warnings: supportWarnings,\n skipped: {\n reason: 'apps/studio is the dashboard and is not a generated app target.',\n },\n };\n }\n\n const previousLedger = await readInfraLedger(args.projectPath);\n const previousFiles = new Set(previousLedger?.files ?? []);\n\n if (args.manifest.infra.deployment === undefined) {\n const removed = await removeFiles(args.projectPath, previousFiles);\n await removeInfraLedger(args.projectPath);\n return {\n generated: 0,\n removed,\n warnings: supportWarnings,\n };\n }\n\n const generated = (args.generateInfrastructureImpl ?? generateInfrastructure)(\n args.manifest.infra,\n {\n namespaceHint: args.projectId,\n appManifest: args.manifest,\n },\n );\n const combinedWarnings = uniqueStrings([...supportWarnings, ...generated.warnings]);\n const nextFiles = new Set<string>();\n\n for (const file of generated.files) {\n const outputPath = resolveProjectFile(args.projectPath, file.path);\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, file.content, 'utf8');\n\n if (file.executable === true) {\n await fs.chmod(outputPath, 0o755);\n }\n\n nextFiles.add(file.path);\n }\n\n const staleFiles = new Set([...previousFiles].filter((filePath) => !nextFiles.has(filePath)));\n const removed = await removeFiles(args.projectPath, staleFiles);\n\n await writeInfraLedger(args.projectPath, {\n schemaVersion: 1,\n generatedAt: new Date().toISOString(),\n target: generated.meta.target,\n files: [...nextFiles].sort(),\n warnings: combinedWarnings,\n });\n\n return {\n generated: generated.files.length,\n removed,\n warnings: combinedWarnings,\n };\n}\n\nexport async function inspectProjectInfrastructure(args: {\n readonly manifest: AppManifest;\n readonly projectId: string;\n readonly projectPath: string;\n}): Promise<InfraProjectInspection> {\n const supportWarnings = validateInfraSupport(args.manifest.infra);\n\n if (args.projectId === STUDIO_PROJECT_ID) {\n return {\n generated: false,\n generatedAt: null,\n hasDeployment: false,\n target: null,\n trackedFiles: 0,\n warnings: supportWarnings,\n skipped: {\n reason: 'apps/studio is the dashboard and is not a generated app target.',\n },\n };\n }\n\n const ledger = await readInfraLedger(args.projectPath);\n return {\n generated: ledger !== null,\n generatedAt: ledger?.generatedAt ?? null,\n hasDeployment: args.manifest.infra.deployment !== undefined,\n target: args.manifest.infra.deployment?.target ?? ledger?.target ?? null,\n trackedFiles: ledger?.files.length ?? 0,\n warnings: uniqueStrings([...supportWarnings, ...(ledger?.warnings ?? [])]),\n };\n}\n\nexport async function resolveProjectInfrastructureTarget(args: {\n readonly manifest: AppManifest;\n readonly projectPath: string;\n}): Promise<string | null> {\n const manifestTarget = args.manifest.infra.deployment?.target;\n if (manifestTarget !== undefined) {\n return manifestTarget;\n }\n\n const ledger = await readInfraLedger(args.projectPath);\n return ledger?.target ?? null;\n}\n\nexport function resolveProjectFile(projectPath: string, filePath: string): string {\n const rootPath = path.resolve(projectPath);\n const resolvedPath = path.resolve(rootPath, filePath);\n const relativePath = path.relative(rootPath, resolvedPath);\n\n if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {\n throw new Error(`Invalid infra output path outside project root: ${filePath}`);\n }\n\n return resolvedPath;\n}\n\nasync function readInfraLedger(projectPath: string): Promise<InfraLedger | null> {\n const ledgerPath = path.join(projectPath, INFRA_LEDGER_RELATIVE_PATH);\n\n try {\n const rawLedger = await fs.readFile(ledgerPath, 'utf8');\n const parsedLedger: unknown = JSON.parse(rawLedger);\n return isInfraLedger(parsedLedger) ? parsedLedger : null;\n } catch {\n return null;\n }\n}\n\nasync function writeInfraLedger(projectPath: string, ledger: InfraLedger): Promise<void> {\n const ledgerPath = path.join(projectPath, INFRA_LEDGER_RELATIVE_PATH);\n await fs.mkdir(path.dirname(ledgerPath), { recursive: true });\n await fs.writeFile(ledgerPath, `${JSON.stringify(ledger, null, 2)}\\n`, 'utf8');\n}\n\nasync function removeInfraLedger(projectPath: string): Promise<void> {\n await fs.rm(path.join(projectPath, INFRA_LEDGER_RELATIVE_PATH), { force: true });\n}\n\nasync function removeFiles(projectPath: string, files: ReadonlySet<string>): Promise<number> {\n let removedFiles = 0;\n\n for (const filePath of files) {\n await fs.rm(resolveProjectFile(projectPath, filePath), { force: true });\n removedFiles += 1;\n }\n\n return removedFiles;\n}\n\nfunction isInfraLedger(value: unknown): value is InfraLedger {\n return (\n isRecord(value) &&\n value.schemaVersion === 1 &&\n typeof value.generatedAt === 'string' &&\n typeof value.target === 'string' &&\n Array.isArray(value.files) &&\n value.files.every((filePath) => typeof filePath === 'string') &&\n Array.isArray(value.warnings) &&\n value.warnings.every((warning) => typeof warning === 'string')\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction uniqueStrings(values: readonly string[]): readonly string[] {\n return [...new Set(values)];\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEnG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB;CAOF;AAED,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAe7B;AAED,wBAAsB,uCAAuC,CAAC,IAAI,EAAE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkBhC;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAET"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEnG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB;CAOF;AAED,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAe7B;AAED,wBAAsB,uCAAuC,CAAC,IAAI,EAAE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkBhC;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAET"}
package/dist/runtime.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { promises as fs } from 'fs';
3
3
  import path from 'path';
4
+ import { readProjectInfrastructureEnvironment } from './projectEnvironment.js';
4
5
  export class InfraScriptExecutionError extends Error {
5
6
  exitCode;
6
7
  stderr;
@@ -27,14 +28,14 @@ export async function runProjectInfrastructureLifecycle(args) {
27
28
  });
28
29
  }
29
30
  export async function resolveProjectInfrastructurePortForward(args) {
30
- const infraRoot = path.join(args.projectPath, 'infra', args.target);
31
- const envPath = path.join(infraRoot, '.env');
32
- const fallbackEnvPath = path.join(infraRoot, '.env.example');
33
- const sourcePath = (await pathExists(envPath)) ? envPath : fallbackEnvPath;
34
- const env = await readSimpleEnvMap(sourcePath);
35
- const localPort = parsePositivePort(env.get('APP_PORT_FORWARD_LOCAL_PORT'));
31
+ const environment = await readProjectInfrastructureEnvironment({
32
+ keys: ['APP_PORT_FORWARD_LOCAL_PORT'],
33
+ projectPath: args.projectPath,
34
+ target: args.target,
35
+ });
36
+ const localPort = parsePositivePort(environment.APP_PORT_FORWARD_LOCAL_PORT);
36
37
  if (localPort === null) {
37
- throw new Error(`Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT in ${sourcePath}. Regenerate infrastructure first.`);
38
+ throw new Error('Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT. Regenerate infrastructure first.');
38
39
  }
39
40
  return {
40
41
  localPort,
@@ -108,25 +109,6 @@ function createExitError(scriptPath, exitCode, stdout, stderr) {
108
109
  stdout,
109
110
  });
110
111
  }
111
- async function readSimpleEnvMap(filePath) {
112
- const out = new Map();
113
- const content = await fs.readFile(filePath, 'utf8');
114
- for (const line of content.split(/\r?\n/u)) {
115
- const trimmed = line.trim();
116
- if (!trimmed || trimmed.startsWith('#'))
117
- continue;
118
- const separator = trimmed.indexOf('=');
119
- if (separator <= 0)
120
- continue;
121
- const key = trimmed.slice(0, separator).trim();
122
- const value = trimmed
123
- .slice(separator + 1)
124
- .trim()
125
- .replace(/^['"]|['"]$/g, '');
126
- out.set(key, value);
127
- }
128
- return out;
129
- }
130
112
  function parsePositivePort(value) {
131
113
  if (value === undefined)
132
114
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'child_process';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nimport type { InfraCommandContext } from './commandContext.js';\n\nexport type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';\n\nexport interface InfraScriptOutput {\n readonly stderr: string;\n readonly stdout: string;\n}\n\nexport interface InfraPortForwardInfo {\n readonly localPort: number;\n readonly url: string;\n}\n\nexport class InfraScriptExecutionError extends Error {\n readonly exitCode: number | null;\n readonly stderr: string;\n readonly stdout: string;\n\n constructor(args: {\n readonly exitCode: number | null;\n readonly message: string;\n readonly stderr: string;\n readonly stdout: string;\n }) {\n super(args.message);\n this.name = 'InfraScriptExecutionError';\n this.exitCode = args.exitCode;\n this.stderr = args.stderr;\n this.stdout = args.stdout;\n }\n}\n\nexport async function runProjectInfrastructureLifecycle(args: {\n readonly args?: readonly string[];\n readonly env?: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<InfraScriptOutput> {\n const scriptPath = resolveProjectInfraScriptPath(args);\n if (!(await pathExists(scriptPath))) {\n throw new Error(\n `Infra script not found: ${scriptPath}. Run infra generate for project '${args.projectId}' first.`,\n );\n }\n\n return runShellScript({\n args: args.args ?? [],\n env: args.env ?? process.env,\n onStderr: args.onStderr,\n onStdout: args.onStdout,\n scriptPath,\n });\n}\n\nexport async function resolveProjectInfrastructurePortForward(args: {\n readonly projectPath: string;\n readonly target: string;\n}): Promise<InfraPortForwardInfo> {\n const infraRoot = path.join(args.projectPath, 'infra', args.target);\n const envPath = path.join(infraRoot, '.env');\n const fallbackEnvPath = path.join(infraRoot, '.env.example');\n const sourcePath = (await pathExists(envPath)) ? envPath : fallbackEnvPath;\n const env = await readSimpleEnvMap(sourcePath);\n const localPort = parsePositivePort(env.get('APP_PORT_FORWARD_LOCAL_PORT'));\n\n if (localPort === null) {\n throw new Error(\n `Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT in ${sourcePath}. Regenerate infrastructure first.`,\n );\n }\n\n return {\n localPort,\n url: `http://127.0.0.1:${localPort}`,\n };\n}\n\nexport async function runProjectInfraScript(args: {\n readonly context: InfraCommandContext;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<void> {\n await runProjectInfrastructureLifecycle({\n env: args.context.env,\n onStderr: (chunk) => args.context.writeStderr(chunk),\n onStdout: (chunk) => args.context.writeStdout(chunk),\n projectId: args.projectId,\n projectPath: args.projectPath,\n script: args.script,\n target: args.target,\n });\n}\n\nexport function resolveProjectInfraScriptPath(args: {\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): string {\n return path.join(args.projectPath, getInfraScriptsDirectory(args.target), `${args.script}.sh`);\n}\n\nfunction getInfraScriptsDirectory(target: string): string {\n switch (target) {\n case 'minikube':\n return 'infra/minikube/scripts';\n default:\n throw new Error(`Unsupported deployment target for infra scripts: ${target}`);\n }\n}\n\nasync function runShellScript(args: {\n readonly args: readonly string[];\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly scriptPath: string;\n}): Promise<InfraScriptOutput> {\n return new Promise<InfraScriptOutput>((resolve, reject) => {\n const child = spawn('bash', [args.scriptPath, ...args.args], {\n cwd: path.dirname(args.scriptPath),\n env: { ...args.env },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n args.onStdout?.(chunk);\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n args.onStderr?.(chunk);\n });\n child.once('error', (error) =>\n reject(createStartError(args.scriptPath, error, stdout, stderr)),\n );\n child.once('close', (exitCode) => {\n if (exitCode === 0) {\n resolve({ stderr, stdout });\n return;\n }\n reject(createExitError(args.scriptPath, exitCode, stdout, stderr));\n });\n });\n}\n\nfunction createStartError(\n scriptPath: string,\n error: Error,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode: null,\n message: `Failed to start infra script '${scriptPath}': ${error.message}`,\n stderr,\n stdout,\n });\n}\n\nfunction createExitError(\n scriptPath: string,\n exitCode: number | null,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode,\n message: `Infra script '${scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,\n stderr,\n stdout,\n });\n}\n\nasync function readSimpleEnvMap(filePath: string): Promise<Map<string, string>> {\n const out = new Map<string, string>();\n const content = await fs.readFile(filePath, 'utf8');\n for (const line of content.split(/\\r?\\n/u)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const separator = trimmed.indexOf('=');\n if (separator <= 0) continue;\n const key = trimmed.slice(0, separator).trim();\n const value = trimmed\n .slice(separator + 1)\n .trim()\n .replace(/^['\"]|['\"]$/g, '');\n out.set(key, value);\n }\n return out;\n}\n\nfunction parsePositivePort(value: string | undefined): number | null {\n if (value === undefined) return null;\n const parsed = Number.parseInt(value.trim(), 10);\n return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,OAAO,EAAE,oCAAoC,EAAE,MAAM,yBAAyB,CAAC;AAc/E,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IACzC,QAAQ,CAAgB;IACxB,MAAM,CAAS;IACf,MAAM,CAAS;IAExB,YAAY,IAKX;QACC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,iCAAiC,CAAC,IASvD;IACC,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,2BAA2B,UAAU,qCAAqC,IAAI,CAAC,SAAS,UAAU,CACnG,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAAC;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;QACrB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;QAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU;KACX,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uCAAuC,CAAC,IAG7D;IACC,MAAM,WAAW,GAAG,MAAM,oCAAoC,CAAC;QAC7D,IAAI,EAAE,CAAC,6BAA6B,CAAC;QACrC,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAAC;IAE7E,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,uGAAuG,CACxG,CAAC;IACJ,CAAC;IAED,OAAO;QACL,SAAS;QACT,GAAG,EAAE,oBAAoB,SAAS,EAAE;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAM3C;IACC,MAAM,iCAAiC,CAAC;QACtC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;QACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC;QACpD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC;QACpD,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,IAI7C;IACC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,wBAAwB,CAAC,MAAc;IAC9C,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU;YACb,OAAO,wBAAwB,CAAC;QAClC;YACE,MAAM,IAAI,KAAK,CAAC,oDAAoD,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAM7B;IACC,OAAO,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE;YAC3D,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;YAClC,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;YACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,MAAM,IAAI,KAAK,CAAC;YAChB,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,MAAM,IAAI,KAAK,CAAC;YAChB,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAC5B,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CACjE,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC/B,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC5B,OAAO;YACT,CAAC;YACD,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CACvB,UAAkB,EAClB,KAAY,EACZ,MAAc,EACd,MAAc;IAEd,OAAO,IAAI,yBAAyB,CAAC;QACnC,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,iCAAiC,UAAU,MAAM,KAAK,CAAC,OAAO,EAAE;QACzE,MAAM;QACN,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CACtB,UAAkB,EAClB,QAAuB,EACvB,MAAc,EACd,MAAc;IAEd,OAAO,IAAI,yBAAyB,CAAC;QACnC,QAAQ;QACR,OAAO,EAAE,iBAAiB,UAAU,sBAAsB,QAAQ,IAAI,SAAS,GAAG;QAClF,MAAM;QACN,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAyB;IAClD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACrF,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { spawn } from 'child_process';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nimport type { InfraCommandContext } from './commandContext.js';\nimport { readProjectInfrastructureEnvironment } from './projectEnvironment.js';\n\nexport type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';\n\nexport interface InfraScriptOutput {\n readonly stderr: string;\n readonly stdout: string;\n}\n\nexport interface InfraPortForwardInfo {\n readonly localPort: number;\n readonly url: string;\n}\n\nexport class InfraScriptExecutionError extends Error {\n readonly exitCode: number | null;\n readonly stderr: string;\n readonly stdout: string;\n\n constructor(args: {\n readonly exitCode: number | null;\n readonly message: string;\n readonly stderr: string;\n readonly stdout: string;\n }) {\n super(args.message);\n this.name = 'InfraScriptExecutionError';\n this.exitCode = args.exitCode;\n this.stderr = args.stderr;\n this.stdout = args.stdout;\n }\n}\n\nexport async function runProjectInfrastructureLifecycle(args: {\n readonly args?: readonly string[];\n readonly env?: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<InfraScriptOutput> {\n const scriptPath = resolveProjectInfraScriptPath(args);\n if (!(await pathExists(scriptPath))) {\n throw new Error(\n `Infra script not found: ${scriptPath}. Run infra generate for project '${args.projectId}' first.`,\n );\n }\n\n return runShellScript({\n args: args.args ?? [],\n env: args.env ?? process.env,\n onStderr: args.onStderr,\n onStdout: args.onStdout,\n scriptPath,\n });\n}\n\nexport async function resolveProjectInfrastructurePortForward(args: {\n readonly projectPath: string;\n readonly target: string;\n}): Promise<InfraPortForwardInfo> {\n const environment = await readProjectInfrastructureEnvironment({\n keys: ['APP_PORT_FORWARD_LOCAL_PORT'],\n projectPath: args.projectPath,\n target: args.target,\n });\n const localPort = parsePositivePort(environment.APP_PORT_FORWARD_LOCAL_PORT);\n\n if (localPort === null) {\n throw new Error(\n 'Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT. Regenerate infrastructure first.',\n );\n }\n\n return {\n localPort,\n url: `http://127.0.0.1:${localPort}`,\n };\n}\n\nexport async function runProjectInfraScript(args: {\n readonly context: InfraCommandContext;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<void> {\n await runProjectInfrastructureLifecycle({\n env: args.context.env,\n onStderr: (chunk) => args.context.writeStderr(chunk),\n onStdout: (chunk) => args.context.writeStdout(chunk),\n projectId: args.projectId,\n projectPath: args.projectPath,\n script: args.script,\n target: args.target,\n });\n}\n\nexport function resolveProjectInfraScriptPath(args: {\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): string {\n return path.join(args.projectPath, getInfraScriptsDirectory(args.target), `${args.script}.sh`);\n}\n\nfunction getInfraScriptsDirectory(target: string): string {\n switch (target) {\n case 'minikube':\n return 'infra/minikube/scripts';\n default:\n throw new Error(`Unsupported deployment target for infra scripts: ${target}`);\n }\n}\n\nasync function runShellScript(args: {\n readonly args: readonly string[];\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly scriptPath: string;\n}): Promise<InfraScriptOutput> {\n return new Promise<InfraScriptOutput>((resolve, reject) => {\n const child = spawn('bash', [args.scriptPath, ...args.args], {\n cwd: path.dirname(args.scriptPath),\n env: { ...args.env },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n args.onStdout?.(chunk);\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n args.onStderr?.(chunk);\n });\n child.once('error', (error) =>\n reject(createStartError(args.scriptPath, error, stdout, stderr)),\n );\n child.once('close', (exitCode) => {\n if (exitCode === 0) {\n resolve({ stderr, stdout });\n return;\n }\n reject(createExitError(args.scriptPath, exitCode, stdout, stderr));\n });\n });\n}\n\nfunction createStartError(\n scriptPath: string,\n error: Error,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode: null,\n message: `Failed to start infra script '${scriptPath}': ${error.message}`,\n stderr,\n stdout,\n });\n}\n\nfunction createExitError(\n scriptPath: string,\n exitCode: number | null,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode,\n message: `Infra script '${scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,\n stderr,\n stdout,\n });\n}\n\nfunction parsePositivePort(value: string | undefined): number | null {\n if (value === undefined) return null;\n const parsed = Number.parseInt(value.trim(), 10);\n return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/infra",
3
3
  "type": "module",
4
- "version": "3.1.1",
4
+ "version": "3.2.0",
5
5
  "description": "Project-aware infrastructure generation library and executable Ankh provider for Ankhorage.",
6
6
  "homepage": "https://github.com/ankhorage/infra#readme",
7
7
  "bugs": {
@@ -3,7 +3,11 @@ import { promises as fs } from 'fs';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
5
 
6
+ import { createAppManifest } from '../testSupport.js';
6
7
  import {
8
+ inspectProjectInfrastructure,
9
+ readProjectInfrastructureEnvironment,
10
+ resolveProjectInfrastructureDatabaseUrl,
7
11
  resolveProjectInfrastructurePortForward,
8
12
  runProjectInfrastructureLifecycle,
9
13
  } from './index.js';
@@ -29,7 +33,13 @@ async function createProjectFixture(): Promise<string> {
29
33
  );
30
34
  await fs.writeFile(
31
35
  path.join(infraRoot, '.env.example'),
32
- 'APP_PORT_FORWARD_LOCAL_PORT=48123\n',
36
+ [
37
+ 'APP_PORT_FORWARD_LOCAL_PORT=48123',
38
+ 'SITE_URL=http://example.local',
39
+ 'DATABASE_URL=postgres://example',
40
+ 'PRIVATE_VALUE=not-selected',
41
+ '',
42
+ ].join('\n'),
33
43
  'utf8',
34
44
  );
35
45
  return projectPath;
@@ -67,4 +77,73 @@ describe('@ankhorage/infra/project', () => {
67
77
  url: 'http://127.0.0.1:48123',
68
78
  });
69
79
  });
80
+
81
+ test('reads only requested runtime environment values from the generated fallback', async () => {
82
+ const projectPath = await createProjectFixture();
83
+ const result = await readProjectInfrastructureEnvironment({
84
+ keys: ['SITE_URL', 'MISSING_VALUE'],
85
+ projectPath,
86
+ target: 'minikube',
87
+ });
88
+
89
+ expect(result).toEqual({ SITE_URL: 'http://example.local' });
90
+ });
91
+
92
+ test('prefers generated runtime environment and resolves the trusted database URL', async () => {
93
+ const projectPath = await createProjectFixture();
94
+ const envPath = path.join(projectPath, 'infra', 'minikube', '.env');
95
+ await fs.writeFile(
96
+ envPath,
97
+ 'SITE_URL=http://runtime.local\nDATABASE_URL=postgres://runtime\n',
98
+ 'utf8',
99
+ );
100
+
101
+ const environment = await readProjectInfrastructureEnvironment({
102
+ keys: ['SITE_URL'],
103
+ projectPath,
104
+ target: 'minikube',
105
+ });
106
+ const databaseUrl = await resolveProjectInfrastructureDatabaseUrl({
107
+ projectPath,
108
+ target: 'minikube',
109
+ });
110
+
111
+ expect(environment).toEqual({ SITE_URL: 'http://runtime.local' });
112
+ expect(databaseUrl).toBe('postgres://runtime');
113
+ });
114
+
115
+ test('inspects deployment and generated state through the owner ledger', async () => {
116
+ const projectPath = await createProjectFixture();
117
+ await fs.mkdir(path.join(projectPath, '.ankh'), { recursive: true });
118
+ await fs.writeFile(
119
+ path.join(projectPath, '.ankh', 'infra-ledger.json'),
120
+ JSON.stringify({
121
+ schemaVersion: 1,
122
+ generatedAt: '2026-08-12T04:00:00.000Z',
123
+ target: 'minikube',
124
+ files: ['infra/minikube/.env.example', 'infra/minikube/scripts/status.sh'],
125
+ warnings: ['generated warning'],
126
+ }),
127
+ 'utf8',
128
+ );
129
+ const manifest = createAppManifest('example', {
130
+ deployment: { monitoring: false, target: 'minikube' },
131
+ modules: [],
132
+ });
133
+
134
+ const result = await inspectProjectInfrastructure({
135
+ manifest,
136
+ projectId: 'example',
137
+ projectPath,
138
+ });
139
+
140
+ expect(result).toEqual({
141
+ generated: true,
142
+ generatedAt: '2026-08-12T04:00:00.000Z',
143
+ hasDeployment: true,
144
+ target: 'minikube',
145
+ trackedFiles: 2,
146
+ warnings: ['generated warning'],
147
+ });
148
+ });
70
149
  });
@@ -2,8 +2,12 @@ export {
2
2
  type ResolvedInfraProject,
3
3
  resolveInfraProject as resolveInfraWorkspaceProject,
4
4
  } from '../project.js';
5
+ export { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';
6
+ export { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';
5
7
  export {
8
+ type InfraProjectInspection,
6
9
  type InfraSyncResult,
10
+ inspectProjectInfrastructure,
7
11
  resolveProjectInfrastructureTarget,
8
12
  syncProjectInfrastructure,
9
13
  } from '../projectInfrastructure.js';
@@ -0,0 +1,27 @@
1
+ import { readProjectInfrastructureEnvironment } from './projectEnvironment.js';
2
+
3
+ const DATABASE_URL_KEYS = [
4
+ 'ANKH_SECRET_STORE_DATABASE_URL',
5
+ 'SUPABASE_DB_URL',
6
+ 'POSTGRES_URL',
7
+ 'DB_URL',
8
+ 'DATABASE_URL',
9
+ ] as const;
10
+
11
+ export async function resolveProjectInfrastructureDatabaseUrl(args: {
12
+ readonly projectPath: string;
13
+ readonly target: string;
14
+ }): Promise<string | null> {
15
+ const environment = await readProjectInfrastructureEnvironment({
16
+ keys: DATABASE_URL_KEYS,
17
+ projectPath: args.projectPath,
18
+ target: args.target,
19
+ });
20
+
21
+ for (const key of DATABASE_URL_KEYS) {
22
+ const value = environment[key]?.trim();
23
+ if (value) return value;
24
+ }
25
+
26
+ return null;
27
+ }
@@ -0,0 +1,51 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+
4
+ export async function readProjectInfrastructureEnvironment(args: {
5
+ readonly keys: readonly string[];
6
+ readonly projectPath: string;
7
+ readonly target: string;
8
+ }): Promise<Readonly<Record<string, string>>> {
9
+ const infraRoot = path.join(args.projectPath, 'infra', args.target);
10
+ const envPath = path.join(infraRoot, '.env');
11
+ const fallbackPath = path.join(infraRoot, '.env.example');
12
+ const sourcePath = (await pathExists(envPath)) ? envPath : fallbackPath;
13
+ const environment = await readEnvironmentFile(sourcePath);
14
+ const selected: Record<string, string> = {};
15
+
16
+ for (const key of new Set(args.keys)) {
17
+ const value = environment.get(key);
18
+ if (value !== undefined) selected[key] = value;
19
+ }
20
+
21
+ return selected;
22
+ }
23
+
24
+ async function readEnvironmentFile(filePath: string): Promise<Map<string, string>> {
25
+ const out = new Map<string, string>();
26
+ const content = await fs.readFile(filePath, 'utf8');
27
+
28
+ for (const line of content.split(/\r?\n/u)) {
29
+ const trimmed = line.trim();
30
+ if (!trimmed || trimmed.startsWith('#')) continue;
31
+ const separator = trimmed.indexOf('=');
32
+ if (separator <= 0) continue;
33
+ const key = trimmed.slice(0, separator).trim();
34
+ const value = trimmed
35
+ .slice(separator + 1)
36
+ .trim()
37
+ .replace(/^['"]|['"]$/g, '');
38
+ out.set(key, value);
39
+ }
40
+
41
+ return out;
42
+ }
43
+
44
+ async function pathExists(filePath: string): Promise<boolean> {
45
+ try {
46
+ await fs.access(filePath);
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
@@ -25,6 +25,18 @@ export interface InfraSyncResult {
25
25
  };
26
26
  }
27
27
 
28
+ export interface InfraProjectInspection {
29
+ readonly generated: boolean;
30
+ readonly generatedAt: string | null;
31
+ readonly hasDeployment: boolean;
32
+ readonly target: string | null;
33
+ readonly trackedFiles: number;
34
+ readonly warnings: readonly string[];
35
+ readonly skipped?: {
36
+ readonly reason: string;
37
+ };
38
+ }
39
+
28
40
  export async function syncProjectInfrastructure(args: {
29
41
  readonly generateInfrastructureImpl?: typeof generateInfrastructure;
30
42
  readonly manifest: AppManifest;
@@ -97,6 +109,38 @@ export async function syncProjectInfrastructure(args: {
97
109
  };
98
110
  }
99
111
 
112
+ export async function inspectProjectInfrastructure(args: {
113
+ readonly manifest: AppManifest;
114
+ readonly projectId: string;
115
+ readonly projectPath: string;
116
+ }): Promise<InfraProjectInspection> {
117
+ const supportWarnings = validateInfraSupport(args.manifest.infra);
118
+
119
+ if (args.projectId === STUDIO_PROJECT_ID) {
120
+ return {
121
+ generated: false,
122
+ generatedAt: null,
123
+ hasDeployment: false,
124
+ target: null,
125
+ trackedFiles: 0,
126
+ warnings: supportWarnings,
127
+ skipped: {
128
+ reason: 'apps/studio is the dashboard and is not a generated app target.',
129
+ },
130
+ };
131
+ }
132
+
133
+ const ledger = await readInfraLedger(args.projectPath);
134
+ return {
135
+ generated: ledger !== null,
136
+ generatedAt: ledger?.generatedAt ?? null,
137
+ hasDeployment: args.manifest.infra.deployment !== undefined,
138
+ target: args.manifest.infra.deployment?.target ?? ledger?.target ?? null,
139
+ trackedFiles: ledger?.files.length ?? 0,
140
+ warnings: uniqueStrings([...supportWarnings, ...(ledger?.warnings ?? [])]),
141
+ };
142
+ }
143
+
100
144
  export async function resolveProjectInfrastructureTarget(args: {
101
145
  readonly manifest: AppManifest;
102
146
  readonly projectPath: string;
package/src/runtime.ts CHANGED
@@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
3
3
  import path from 'path';
4
4
 
5
5
  import type { InfraCommandContext } from './commandContext.js';
6
+ import { readProjectInfrastructureEnvironment } from './projectEnvironment.js';
6
7
 
7
8
  export type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';
8
9
 
@@ -65,16 +66,16 @@ export async function resolveProjectInfrastructurePortForward(args: {
65
66
  readonly projectPath: string;
66
67
  readonly target: string;
67
68
  }): Promise<InfraPortForwardInfo> {
68
- const infraRoot = path.join(args.projectPath, 'infra', args.target);
69
- const envPath = path.join(infraRoot, '.env');
70
- const fallbackEnvPath = path.join(infraRoot, '.env.example');
71
- const sourcePath = (await pathExists(envPath)) ? envPath : fallbackEnvPath;
72
- const env = await readSimpleEnvMap(sourcePath);
73
- const localPort = parsePositivePort(env.get('APP_PORT_FORWARD_LOCAL_PORT'));
69
+ const environment = await readProjectInfrastructureEnvironment({
70
+ keys: ['APP_PORT_FORWARD_LOCAL_PORT'],
71
+ projectPath: args.projectPath,
72
+ target: args.target,
73
+ });
74
+ const localPort = parsePositivePort(environment.APP_PORT_FORWARD_LOCAL_PORT);
74
75
 
75
76
  if (localPort === null) {
76
77
  throw new Error(
77
- `Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT in ${sourcePath}. Regenerate infrastructure first.`,
78
+ 'Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT. Regenerate infrastructure first.',
78
79
  );
79
80
  }
80
81
 
@@ -186,24 +187,6 @@ function createExitError(
186
187
  });
187
188
  }
188
189
 
189
- async function readSimpleEnvMap(filePath: string): Promise<Map<string, string>> {
190
- const out = new Map<string, string>();
191
- const content = await fs.readFile(filePath, 'utf8');
192
- for (const line of content.split(/\r?\n/u)) {
193
- const trimmed = line.trim();
194
- if (!trimmed || trimmed.startsWith('#')) continue;
195
- const separator = trimmed.indexOf('=');
196
- if (separator <= 0) continue;
197
- const key = trimmed.slice(0, separator).trim();
198
- const value = trimmed
199
- .slice(separator + 1)
200
- .trim()
201
- .replace(/^['"]|['"]$/g, '');
202
- out.set(key, value);
203
- }
204
- return out;
205
- }
206
-
207
190
  function parsePositivePort(value: string | undefined): number | null {
208
191
  if (value === undefined) return null;
209
192
  const parsed = Number.parseInt(value.trim(), 10);