@ankhorage/infra 7.1.0 → 7.1.2

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,17 @@
1
1
  # @ankhorage/infra
2
2
 
3
+ ## 7.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - e707284: Render standalone Infra help from the canonical provider manifest through the shared Ankh CLI help runtime.
8
+
9
+ ## 7.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 7440682: Expose the project-scoped credential port constructor from `@ankhorage/infra/project` for downstream host composition without exposing credential storage details.
14
+
3
15
  ## 7.1.0
4
16
 
5
17
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"runInfraCliAsync.d.ts","sourceRoot":"","sources":["../../src/cli/runInfraCliAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAO9B,iGAAiG;AACjG,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CAkBhC;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACnD"}
1
+ {"version":3,"file":"runInfraCliAsync.d.ts","sourceRoot":"","sources":["../../src/cli/runInfraCliAsync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAO9B,iGAAiG;AACjG,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CAwBhC;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACnD"}
@@ -1,6 +1,8 @@
1
+ import { isHelpToken, renderProviderHelp } from '@ankhorage/ankh';
2
+ import { INFRA_PACKAGE_DESCRIPTION } from '../constants.js';
1
3
  import { createDefaultInfraCommandContext } from './createDefaultInfraCommandContext.js';
4
+ import { createInfraRuntimeProvider } from './createInfraRuntimeProvider.js';
2
5
  import { findInfraCommand } from './findInfraCommand.js';
3
- import { renderInfraRootHelp } from './renderInfraRootHelp.js';
4
6
  import { renderUnknownInfraCommand } from './renderUnknownInfraCommand.js';
5
7
  import { runInfraCommandAsync } from './runInfraCommandAsync.js';
6
8
  /*** Dispatch the standalone executable arguments through the canonical Infra command surface. */
@@ -9,7 +11,11 @@ export async function runInfraCliAsync(argv, options = {}) {
9
11
  const runCommand = options.runCommandImpl ?? runInfraCommandAsync;
10
12
  const [firstToken, ...restTokens] = argv;
11
13
  if (firstToken === undefined || isHelpToken(firstToken)) {
12
- context.writeStdout(renderInfraRootHelp(context.version));
14
+ context.writeStdout(renderProviderHelp({
15
+ commandPrefix: ['ankhorage-infra'],
16
+ description: INFRA_PACKAGE_DESCRIPTION,
17
+ manifest: createInfraRuntimeProvider(),
18
+ }));
13
19
  return { exitCode: 0 };
14
20
  }
15
21
  if (isVersionToken(firstToken)) {
@@ -23,10 +29,6 @@ export async function runInfraCliAsync(argv, options = {}) {
23
29
  }
24
30
  return runCommand({ argv: restTokens, command, context }, { services: options.services });
25
31
  }
26
- /*** Recognize standalone help tokens. */
27
- function isHelpToken(value) {
28
- return value === '--help' || value === '-h' || value === 'help';
29
- }
30
32
  /*** Recognize standalone version tokens. */
31
33
  function isVersionToken(value) {
32
34
  return value === '--version' || value === '-v' || value === 'version';
@@ -1 +1 @@
1
- {"version":3,"file":"runInfraCliAsync.js","sourceRoot":"","sources":["../../src/cli/runInfraCliAsync.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,iGAAiG;AACjG,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAuB,EACvB,UAA2B,EAAE;IAE7B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gCAAgC,EAAE,CAAC;IACtE,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,IAAI,oBAAoB,CAAC;IAClE,MAAM,CAAC,UAAU,EAAE,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;QAC5C,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,WAAW,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC5F,CAAC;AAQD,yCAAyC;AACzC,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC;AAClE,CAAC;AAED,4CAA4C;AAC5C,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;AACxE,CAAC","sourcesContent":["import type {\n InfraCommandContext,\n InfraCommandRunResult,\n InfraCommandServices,\n RunInfraCommandImpl,\n} from '../types/infraCli.js';\nimport { createDefaultInfraCommandContext } from './createDefaultInfraCommandContext.js';\nimport { findInfraCommand } from './findInfraCommand.js';\nimport { renderInfraRootHelp } from './renderInfraRootHelp.js';\nimport { renderUnknownInfraCommand } from './renderUnknownInfraCommand.js';\nimport { runInfraCommandAsync } from './runInfraCommandAsync.js';\n\n/*** Dispatch the standalone executable arguments through the canonical Infra command surface. */\nexport async function runInfraCliAsync(\n argv: readonly string[],\n options: InfraCliOptions = {},\n): Promise<InfraCommandRunResult> {\n const context = options.context ?? createDefaultInfraCommandContext();\n const runCommand = options.runCommandImpl ?? runInfraCommandAsync;\n const [firstToken, ...restTokens] = argv;\n if (firstToken === undefined || isHelpToken(firstToken)) {\n context.writeStdout(renderInfraRootHelp(context.version));\n return { exitCode: 0 };\n }\n if (isVersionToken(firstToken)) {\n context.writeStdout(`${context.version}\\n`);\n return { exitCode: 0 };\n }\n const command = findInfraCommand(firstToken);\n if (command === null) {\n context.writeStderr(renderUnknownInfraCommand(firstToken));\n return { exitCode: 1 };\n }\n return runCommand({ argv: restTokens, command, context }, { services: options.services });\n}\n\ninterface InfraCliOptions {\n readonly context?: InfraCommandContext;\n readonly runCommandImpl?: RunInfraCommandImpl;\n readonly services?: Partial<InfraCommandServices>;\n}\n\n/*** Recognize standalone help tokens. */\nfunction isHelpToken(value: string): boolean {\n return value === '--help' || value === '-h' || value === 'help';\n}\n\n/*** Recognize standalone version tokens. */\nfunction isVersionToken(value: string): boolean {\n return value === '--version' || value === '-v' || value === 'version';\n}\n"]}
1
+ {"version":3,"file":"runInfraCliAsync.js","sourceRoot":"","sources":["../../src/cli/runInfraCliAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAO5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,iGAAiG;AACjG,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAuB,EACvB,UAA2B,EAAE;IAE7B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gCAAgC,EAAE,CAAC;IACtE,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,IAAI,oBAAoB,CAAC;IAClE,MAAM,CAAC,UAAU,EAAE,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,WAAW,CACjB,kBAAkB,CAAC;YACjB,aAAa,EAAE,CAAC,iBAAiB,CAAC;YAClC,WAAW,EAAE,yBAAyB;YACtC,QAAQ,EAAE,0BAA0B,EAAE;SACvC,CAAC,CACH,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;QAC5C,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,WAAW,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC5F,CAAC;AAQD,4CAA4C;AAC5C,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;AACxE,CAAC","sourcesContent":["import { isHelpToken, renderProviderHelp } from '@ankhorage/ankh';\n\nimport { INFRA_PACKAGE_DESCRIPTION } from '../constants.js';\nimport type {\n InfraCommandContext,\n InfraCommandRunResult,\n InfraCommandServices,\n RunInfraCommandImpl,\n} from '../types/infraCli.js';\nimport { createDefaultInfraCommandContext } from './createDefaultInfraCommandContext.js';\nimport { createInfraRuntimeProvider } from './createInfraRuntimeProvider.js';\nimport { findInfraCommand } from './findInfraCommand.js';\nimport { renderUnknownInfraCommand } from './renderUnknownInfraCommand.js';\nimport { runInfraCommandAsync } from './runInfraCommandAsync.js';\n\n/*** Dispatch the standalone executable arguments through the canonical Infra command surface. */\nexport async function runInfraCliAsync(\n argv: readonly string[],\n options: InfraCliOptions = {},\n): Promise<InfraCommandRunResult> {\n const context = options.context ?? createDefaultInfraCommandContext();\n const runCommand = options.runCommandImpl ?? runInfraCommandAsync;\n const [firstToken, ...restTokens] = argv;\n if (firstToken === undefined || isHelpToken(firstToken)) {\n context.writeStdout(\n renderProviderHelp({\n commandPrefix: ['ankhorage-infra'],\n description: INFRA_PACKAGE_DESCRIPTION,\n manifest: createInfraRuntimeProvider(),\n }),\n );\n return { exitCode: 0 };\n }\n if (isVersionToken(firstToken)) {\n context.writeStdout(`${context.version}\\n`);\n return { exitCode: 0 };\n }\n const command = findInfraCommand(firstToken);\n if (command === null) {\n context.writeStderr(renderUnknownInfraCommand(firstToken));\n return { exitCode: 1 };\n }\n return runCommand({ argv: restTokens, command, context }, { services: options.services });\n}\n\ninterface InfraCliOptions {\n readonly context?: InfraCommandContext;\n readonly runCommandImpl?: RunInfraCommandImpl;\n readonly services?: Partial<InfraCommandServices>;\n}\n\n/*** Recognize standalone version tokens. */\nfunction isVersionToken(value: string): boolean {\n return value === '--version' || value === '-v' || value === 'version';\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  export declare const INFRA_PACKAGE_NAME: string;
2
2
  export declare const INFRA_PACKAGE_VERSION: string;
3
+ export declare const INFRA_PACKAGE_DESCRIPTION: string;
3
4
  export declare const INFRA_COMMAND_CATEGORY: "infra";
4
5
  export declare const INFRA_CAPABILITIES: readonly ["infra.validate", "infra.plan", "infra.generate", "infra.up", "infra.status", "infra.outputs", "infra.down", "infra.destroy"];
5
6
  export declare const INFRA_PACKAGE_METADATA: {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,kBAAkB,QAAmB,CAAC;AACnD,eAAO,MAAM,qBAAqB,QAAsB,CAAC;AACzD,eAAO,MAAM,sBAAsB,SAAiD,CAAC;AACrF,eAAO,MAAM,kBAAkB,yIASiB,CAAC;AAEjD,eAAO,MAAM,sBAAsB;;;;CAIK,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,kBAAkB,QAAmB,CAAC;AACnD,eAAO,MAAM,qBAAqB,QAAsB,CAAC;AACzD,eAAO,MAAM,yBAAyB,QAA0B,CAAC;AACjE,eAAO,MAAM,sBAAsB,SAAiD,CAAC;AACrF,eAAO,MAAM,kBAAkB,yIASiB,CAAC;AAEjD,eAAO,MAAM,sBAAsB;;;;CAIK,CAAC"}
package/dist/constants.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import packageJson from '../package.json';
2
2
  export const INFRA_PACKAGE_NAME = packageJson.name;
3
3
  export const INFRA_PACKAGE_VERSION = packageJson.version;
4
+ export const INFRA_PACKAGE_DESCRIPTION = packageJson.description;
4
5
  export const INFRA_COMMAND_CATEGORY = 'infra';
5
6
  export const INFRA_CAPABILITIES = [
6
7
  'infra.validate',
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAMA,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAE1C,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC,IAAI,CAAC;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC,OAAO,CAAC;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAA8C,CAAC;AACrF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,gBAAgB;IAChB,YAAY;IACZ,gBAAgB;IAChB,UAAU;IACV,cAAc;IACd,eAAe;IACf,YAAY;IACZ,eAAe;CAC+B,CAAC;AAEjD,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,QAAQ,EAAE,sBAAsB;IAChC,QAAQ,EAAE,qBAAqB;IAC/B,YAAY,EAAE,kBAAkB;CACM,CAAC","sourcesContent":["import type {\n AnkhCapabilityId,\n AnkhCommandCategory,\n AnkhPackageMetadata,\n} from '@ankhorage/contracts/cli';\n\nimport packageJson from '../package.json';\n\nexport const INFRA_PACKAGE_NAME = packageJson.name;\nexport const INFRA_PACKAGE_VERSION = packageJson.version;\nexport const INFRA_COMMAND_CATEGORY = 'infra' as const satisfies AnkhCommandCategory;\nexport const INFRA_CAPABILITIES = [\n 'infra.validate',\n 'infra.plan',\n 'infra.generate',\n 'infra.up',\n 'infra.status',\n 'infra.outputs',\n 'infra.down',\n 'infra.destroy',\n] as const satisfies readonly AnkhCapabilityId[];\n\nexport const INFRA_PACKAGE_METADATA = {\n category: INFRA_COMMAND_CATEGORY,\n provider: './dist/cli/index.js',\n capabilities: INFRA_CAPABILITIES,\n} as const satisfies AnkhPackageMetadata;\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAMA,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAE1C,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC,IAAI,CAAC;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC,OAAO,CAAC;AACzD,MAAM,CAAC,MAAM,yBAAyB,GAAG,WAAW,CAAC,WAAW,CAAC;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAA8C,CAAC;AACrF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,gBAAgB;IAChB,YAAY;IACZ,gBAAgB;IAChB,UAAU;IACV,cAAc;IACd,eAAe;IACf,YAAY;IACZ,eAAe;CAC+B,CAAC;AAEjD,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,QAAQ,EAAE,sBAAsB;IAChC,QAAQ,EAAE,qBAAqB;IAC/B,YAAY,EAAE,kBAAkB;CACM,CAAC","sourcesContent":["import type {\n AnkhCapabilityId,\n AnkhCommandCategory,\n AnkhPackageMetadata,\n} from '@ankhorage/contracts/cli';\n\nimport packageJson from '../package.json';\n\nexport const INFRA_PACKAGE_NAME = packageJson.name;\nexport const INFRA_PACKAGE_VERSION = packageJson.version;\nexport const INFRA_PACKAGE_DESCRIPTION = packageJson.description;\nexport const INFRA_COMMAND_CATEGORY = 'infra' as const satisfies AnkhCommandCategory;\nexport const INFRA_CAPABILITIES = [\n 'infra.validate',\n 'infra.plan',\n 'infra.generate',\n 'infra.up',\n 'infra.status',\n 'infra.outputs',\n 'infra.down',\n 'infra.destroy',\n] as const satisfies readonly AnkhCapabilityId[];\n\nexport const INFRA_PACKAGE_METADATA = {\n category: INFRA_COMMAND_CATEGORY,\n provider: './dist/cli/index.js',\n capabilities: INFRA_CAPABILITIES,\n} as const satisfies AnkhPackageMetadata;\n"]}
@@ -1,3 +1,4 @@
1
+ export { createProjectInfraCredentialPort } from '../features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.js';
1
2
  export { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';
2
3
  export { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';
3
4
  export { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC;AACnG,YAAY,EACV,kCAAkC,EAClC,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,yFAAyF,CAAC;AAC3I,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC;AACnG,YAAY,EACV,kCAAkC,EAClC,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,0BAA0B,CAAC"}
@@ -1,3 +1,4 @@
1
+ export { createProjectInfraCredentialPort } from '../features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.js';
1
2
  export { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';
2
3
  export { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';
3
4
  export { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC","sourcesContent":["export { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';\nexport { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';\nexport { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';\nexport { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';\nexport { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';\nexport { createProjectInfraLifecycle } from '../features/environment-lifecycle/composition/createProjectInfraLifecycle.js';\nexport { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';\nexport type {\n CreateProjectInfraLifecycleOptions,\n InfraArtifactWriteResult,\n ProjectInfraDestroyRequest,\n ProjectInfraLifecycle,\n ProjectInfraOperationRequest,\n ResolvedInfraProject,\n} from '../types/infraProject.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,yFAAyF,CAAC;AAC3I,OAAO,EAAE,yBAAyB,EAAE,MAAM,kFAAkF,CAAC;AAC7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,oFAAoF,CAAC;AACjI,OAAO,EAAE,wBAAwB,EAAE,MAAM,iFAAiF,CAAC;AAC3H,OAAO,EAAE,iCAAiC,EAAE,MAAM,0FAA0F,CAAC;AAC7I,OAAO,EAAE,0BAA0B,EAAE,MAAM,mFAAmF,CAAC;AAC/H,OAAO,EAAE,2BAA2B,EAAE,MAAM,8EAA8E,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC","sourcesContent":["export { createProjectInfraCredentialPort } from '../features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.js';\nexport { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';\nexport { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';\nexport { resolveInfraProjectAsync } from '../features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js';\nexport { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';\nexport { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';\nexport { createProjectInfraLifecycle } from '../features/environment-lifecycle/composition/createProjectInfraLifecycle.js';\nexport { resolveProjectFile } from '../features/environment-lifecycle/utils/resolveProjectFile.js';\nexport type {\n CreateProjectInfraLifecycleOptions,\n InfraArtifactWriteResult,\n ProjectInfraDestroyRequest,\n ProjectInfraLifecycle,\n ProjectInfraOperationRequest,\n ResolvedInfraProject,\n} from '../types/infraProject.js';\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/infra",
3
3
  "type": "module",
4
- "version": "7.1.0",
4
+ "version": "7.1.2",
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": {
@@ -35,6 +35,7 @@
35
35
  "openapi"
36
36
  ],
37
37
  "dependencies": {
38
+ "@ankhorage/ankh": "^0.9.1",
38
39
  "@ankhorage/contracts": "^21.0.0",
39
40
  "@ankhorage/utility": "^1.2.0"
40
41
  },
@@ -111,9 +112,8 @@
111
112
  "knip:test": "bun run knip:check"
112
113
  },
113
114
  "devDependencies": {
114
- "@ankhorage/ankh": "^0.8.11",
115
115
  "@ankhorage/cerbos": "^0.2.0",
116
- "@ankhorage/devtools": "^1.14.0",
116
+ "@ankhorage/devtools": "^1.15.3",
117
117
  "@ankhorage/docker-compose": "^0.5.3",
118
118
  "@ankhorage/hetzner": "^0.3.0",
119
119
  "@ankhorage/k3s": "^0.4.1",
@@ -121,7 +121,7 @@
121
121
  "@ankhorage/minikube": "^0.4.4",
122
122
  "@ankhorage/paradox": "^0.1.24",
123
123
  "@ankhorage/r2": "^0.2.0",
124
- "@ankhorage/supabase": "^0.5.9",
124
+ "@ankhorage/supabase": "^0.5.10",
125
125
  "@ankhorage/supabase-vault": "^0.3.1",
126
126
  "@types/bun": "^1.4.1",
127
127
  "@types/node": "^25.6.0",
@@ -1,15 +1,31 @@
1
+ import { renderProviderHelp } from '@ankhorage/ankh';
1
2
  import { describe, expect, test } from 'bun:test';
2
3
 
4
+ import { INFRA_PACKAGE_DESCRIPTION } from '../constants.js';
3
5
  import { createCapturedCommandContext } from '../testSupport.js';
6
+ import { createInfraRuntimeProvider } from './createInfraRuntimeProvider.js';
4
7
  import { runInfraCliAsync } from './runInfraCliAsync.js';
5
8
 
6
9
  describe('standalone Infra CLI', () => {
7
- test('prints help/version and dispatches all commands through the shared runner', async () => {
8
- const help = createCapturedCommandContext('/workspace', '9.9.9');
10
+ test('renders every help alias from the canonical provider manifest', async () => {
11
+ const expectedHelp = renderProviderHelp({
12
+ commandPrefix: ['ankhorage-infra'],
13
+ description: INFRA_PACKAGE_DESCRIPTION,
14
+ manifest: createInfraRuntimeProvider(),
15
+ });
16
+
17
+ for (const argv of [[], ['-h'], ['--help'], ['help']] as const) {
18
+ const captured = createCapturedCommandContext('/workspace', '9.9.9');
19
+ const result = await runInfraCliAsync(argv, { context: captured.context });
20
+
21
+ expect(result).toEqual({ exitCode: 0 });
22
+ expect(captured.stdout.value).toBe(expectedHelp);
23
+ expect(captured.stderr.value).toBe('');
24
+ }
25
+ });
26
+
27
+ test('prints version and dispatches all commands through the shared runner', async () => {
9
28
  const version = createCapturedCommandContext('/workspace', '9.9.9');
10
- expect((await runInfraCliAsync([], { context: help.context })).exitCode).toBe(0);
11
- expect(help.stdout.value).toContain('ankhorage-infra <command>');
12
- expect(help.stdout.value).toContain('destroy');
13
29
  expect((await runInfraCliAsync(['--version'], { context: version.context })).exitCode).toBe(0);
14
30
  expect(version.stdout.value).toBe('9.9.9\n');
15
31
 
@@ -1,3 +1,6 @@
1
+ import { isHelpToken, renderProviderHelp } from '@ankhorage/ankh';
2
+
3
+ import { INFRA_PACKAGE_DESCRIPTION } from '../constants.js';
1
4
  import type {
2
5
  InfraCommandContext,
3
6
  InfraCommandRunResult,
@@ -5,8 +8,8 @@ import type {
5
8
  RunInfraCommandImpl,
6
9
  } from '../types/infraCli.js';
7
10
  import { createDefaultInfraCommandContext } from './createDefaultInfraCommandContext.js';
11
+ import { createInfraRuntimeProvider } from './createInfraRuntimeProvider.js';
8
12
  import { findInfraCommand } from './findInfraCommand.js';
9
- import { renderInfraRootHelp } from './renderInfraRootHelp.js';
10
13
  import { renderUnknownInfraCommand } from './renderUnknownInfraCommand.js';
11
14
  import { runInfraCommandAsync } from './runInfraCommandAsync.js';
12
15
 
@@ -19,7 +22,13 @@ export async function runInfraCliAsync(
19
22
  const runCommand = options.runCommandImpl ?? runInfraCommandAsync;
20
23
  const [firstToken, ...restTokens] = argv;
21
24
  if (firstToken === undefined || isHelpToken(firstToken)) {
22
- context.writeStdout(renderInfraRootHelp(context.version));
25
+ context.writeStdout(
26
+ renderProviderHelp({
27
+ commandPrefix: ['ankhorage-infra'],
28
+ description: INFRA_PACKAGE_DESCRIPTION,
29
+ manifest: createInfraRuntimeProvider(),
30
+ }),
31
+ );
23
32
  return { exitCode: 0 };
24
33
  }
25
34
  if (isVersionToken(firstToken)) {
@@ -40,11 +49,6 @@ interface InfraCliOptions {
40
49
  readonly services?: Partial<InfraCommandServices>;
41
50
  }
42
51
 
43
- /*** Recognize standalone help tokens. */
44
- function isHelpToken(value: string): boolean {
45
- return value === '--help' || value === '-h' || value === 'help';
46
- }
47
-
48
52
  /*** Recognize standalone version tokens. */
49
53
  function isVersionToken(value: string): boolean {
50
54
  return value === '--version' || value === '-v' || value === 'version';
@@ -1,12 +1,14 @@
1
+ import { renderProviderHelp } from '@ankhorage/ankh';
1
2
  import type { AppManifest } from '@ankhorage/contracts';
2
3
  import type { InfraEnvironmentSpec, InfraLedger, InfraResult } from '@ankhorage/contracts/infra';
3
4
  import { describe, expect, test } from 'bun:test';
4
5
 
5
6
  import { INFRA_COMMANDS } from './cli/constants.js';
7
+ import { createInfraRuntimeProvider } from './cli/createInfraRuntimeProvider.js';
6
8
  import { createProviderCommandDescriptors } from './cli/createProviderCommandDescriptors.js';
7
9
  import { findInfraCommand } from './cli/findInfraCommand.js';
8
- import { renderInfraRootHelp } from './cli/renderInfraRootHelp.js';
9
10
  import { runInfraCommandAsync } from './cli/runInfraCommandAsync.js';
11
+ import { INFRA_PACKAGE_DESCRIPTION } from './constants.js';
10
12
  import { createAppManifest, createCapturedCommandContext } from './testSupport.js';
11
13
  import type { InfraCommandServices, InfraLifecycleOperations } from './types/infraCli.js';
12
14
  import type { InfraDestroyOperationRequest } from './types/infraOrchestration.js';
@@ -16,7 +18,11 @@ describe('canonical Infra commands', () => {
16
18
  const names = ['validate', 'plan', 'generate', 'up', 'status', 'outputs', 'down', 'destroy'];
17
19
  expect(INFRA_COMMANDS.map(({ standaloneName }) => standaloneName)).toEqual(names);
18
20
  expect(createProviderCommandDescriptors().map(({ path }) => path.join(' '))).toEqual(names);
19
- const help = renderInfraRootHelp('9.9.9');
21
+ const help = renderProviderHelp({
22
+ commandPrefix: ['ankhorage-infra'],
23
+ description: INFRA_PACKAGE_DESCRIPTION,
24
+ manifest: createInfraRuntimeProvider(),
25
+ });
20
26
  for (const name of names) expect(help).toContain(name);
21
27
  expect(help).not.toContain('port-forward');
22
28
  expect(help).not.toContain('reset');
package/src/constants.ts CHANGED
@@ -8,6 +8,7 @@ import packageJson from '../package.json';
8
8
 
9
9
  export const INFRA_PACKAGE_NAME = packageJson.name;
10
10
  export const INFRA_PACKAGE_VERSION = packageJson.version;
11
+ export const INFRA_PACKAGE_DESCRIPTION = packageJson.description;
11
12
  export const INFRA_COMMAND_CATEGORY = 'infra' as const satisfies AnkhCommandCategory;
12
13
  export const INFRA_CAPABILITIES = [
13
14
  'infra.validate',
@@ -1,4 +1,6 @@
1
1
  import type {
2
+ InfraControlPlaneCredentialRef,
3
+ InfraCredentialPort,
2
4
  InfraLedger,
3
5
  InfraManifest,
4
6
  InfraResourceIdentity,
@@ -230,9 +232,7 @@ function createDependencies(fixture: ProductionFixture): InfraOrchestrationDepen
230
232
  }
231
233
  },
232
234
  },
233
- credentials: {
234
- resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
235
- },
235
+ credentials: createCredentialPort(),
236
236
  secrets: {
237
237
  resolveAsync: () =>
238
238
  Promise.resolve({
@@ -245,6 +245,32 @@ function createDependencies(fixture: ProductionFixture): InfraOrchestrationDepen
245
245
  };
246
246
  }
247
247
 
248
+ function createCredentialPort(): InfraCredentialPort {
249
+ return {
250
+ findAsync: (reference) => Promise.resolve(findCredential(reference)),
251
+ resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
252
+ persistAsync: (reference) =>
253
+ Promise.resolve({
254
+ ok: false,
255
+ diagnostics: [
256
+ {
257
+ severity: 'error',
258
+ code: 'unexpected-credential-persist',
259
+ message: `Production fixture must not persist credential ${reference.name}.`,
260
+ },
261
+ ],
262
+ }),
263
+ };
264
+ }
265
+
266
+ function findCredential(
267
+ reference: InfraControlPlaneCredentialRef,
268
+ ): InfraResult<Readonly<Record<string, string>> | null> {
269
+ return reference.name === 'SUPABASE_BOOTSTRAP'
270
+ ? resolveCredential(reference.name)
271
+ : success(null);
272
+ }
273
+
248
274
  function resolveCredential(name: string): InfraResult<Readonly<Record<string, string>>> {
249
275
  if (name === 'HCLOUD_TOKEN') return success({ token: 'phase10-hcloud-token' });
250
276
  if (name === 'HETZNER_SSH') {
@@ -1,4 +1,9 @@
1
- import type { InfraManifest, InfraResult } from '@ankhorage/contracts/infra';
1
+ import type {
2
+ InfraControlPlaneCredentialRef,
3
+ InfraCredentialPort,
4
+ InfraManifest,
5
+ InfraResult,
6
+ } from '@ankhorage/contracts/infra';
2
7
  import {
3
8
  createInfraAdapter as createHetznerInfraAdapter,
4
9
  type HetznerCloudApi,
@@ -189,9 +194,7 @@ function createDependencies(fixture: ProductionR2Fixture): InfraOrchestrationDep
189
194
  }
190
195
  },
191
196
  },
192
- credentials: {
193
- resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
194
- },
197
+ credentials: createCredentialPort(),
195
198
  secrets: {
196
199
  resolveAsync: () =>
197
200
  Promise.resolve({
@@ -204,6 +207,32 @@ function createDependencies(fixture: ProductionR2Fixture): InfraOrchestrationDep
204
207
  };
205
208
  }
206
209
 
210
+ function createCredentialPort(): InfraCredentialPort {
211
+ return {
212
+ findAsync: (reference) => Promise.resolve(findCredential(reference)),
213
+ resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
214
+ persistAsync: (reference) =>
215
+ Promise.resolve({
216
+ ok: false,
217
+ diagnostics: [
218
+ {
219
+ severity: 'error',
220
+ code: 'unexpected-credential-persist',
221
+ message: `Production fixture must not persist credential ${reference.name}.`,
222
+ },
223
+ ],
224
+ }),
225
+ };
226
+ }
227
+
228
+ function findCredential(
229
+ reference: InfraControlPlaneCredentialRef,
230
+ ): InfraResult<Readonly<Record<string, string>> | null> {
231
+ return reference.name === 'SUPABASE_BOOTSTRAP'
232
+ ? resolveCredential(reference.name)
233
+ : success(null);
234
+ }
235
+
207
236
  function resolveCredential(name: string): InfraResult<Readonly<Record<string, string>>> {
208
237
  if (name === 'HCLOUD_TOKEN') return success({ token: 'phase10-hcloud-token' });
209
238
  if (name === 'HETZNER_SSH') {
@@ -1,6 +1,8 @@
1
- import { createHmac } from 'node:crypto';
1
+ import { createHash } from 'node:crypto';
2
2
 
3
3
  import type {
4
+ InfraControlPlaneCredentialRef,
5
+ InfraCredentialPort,
4
6
  InfraLedger,
5
7
  InfraManifest,
6
8
  InfraResourceIdentity,
@@ -19,16 +21,7 @@ import type { InfraOrchestrationDependencies } from './types/infraOrchestration.
19
21
  const projectId = 'infra145-compose-providers';
20
22
  const baseUrl = 'http://127.0.0.1:54322';
21
23
  const bucket = 'phase9-acceptance';
22
- const jwtSecret = 'phase9-jwt-secret-that-is-at-least-thirty-two-characters';
23
- const credentials = {
24
- postgresPassword: 'phase9-postgres-password',
25
- jwtSecret,
26
- anonKey: createJwt('anon'),
27
- serviceRoleKey: createJwt('service_role'),
28
- realtimeSecretKeyBase: 'r'.repeat(64),
29
- realtimeDatabaseEncryptionKey: '0123456789abcdef',
30
- pgMetaCryptoKey: 'phase9-meta-crypto-key-that-is-at-least-32-characters',
31
- } as const;
24
+ const bootstrapCredential = { source: 'control-plane', name: 'SUPABASE_BOOTSTRAP' } as const;
32
25
  const manifest = {
33
26
  environments: {
34
27
  local: {
@@ -59,7 +52,8 @@ const manifest = {
59
52
  test.skipIf(process.env.ANKH_INFRA_DOCKER_COMPOSE_PROVIDERS_E2E !== '1')(
60
53
  'runs Supabase and Cerbos through the provider-neutral Docker Compose lifecycle',
61
54
  async () => {
62
- const dependencies = createDependencies();
55
+ const credentialPort = new MemoryCredentialPort();
56
+ const dependencies = createDependencies(credentialPort);
63
57
  let ledger: InfraLedger | undefined;
64
58
 
65
59
  try {
@@ -72,7 +66,10 @@ test.skipIf(process.env.ANKH_INFRA_DOCKER_COMPOSE_PROVIDERS_E2E !== '1')(
72
66
  await upInfraEnvironmentAsync({ projectId, manifest }, dependencies),
73
67
  );
74
68
  ledger = firstLedger;
75
- assertProviderNeutralOutputs(firstOutputs);
69
+ expect(credentialPort.persistCount).toBe(1);
70
+ const firstCredentials = credentialPort.readRequired();
71
+ const firstCredentialFingerprint = credentialFingerprint(firstCredentials);
72
+ assertProviderNeutralOutputs(firstOutputs, firstCredentials);
76
73
 
77
74
  const status = requireSuccess(
78
75
  await statusInfraEnvironmentAsync({ projectId, manifest, previous: ledger }, dependencies),
@@ -98,7 +95,9 @@ test.skipIf(process.env.ANKH_INFRA_DOCKER_COMPOSE_PROVIDERS_E2E !== '1')(
98
95
  await upInfraEnvironmentAsync({ projectId, manifest, previous: ledger }, dependencies),
99
96
  );
100
97
  ledger = resumedLedger;
101
- assertProviderNeutralOutputs(resumedOutputs);
98
+ expect(credentialPort.persistCount).toBe(1);
99
+ expect(credentialFingerprint(credentialPort.readRequired())).toBe(firstCredentialFingerprint);
100
+ assertProviderNeutralOutputs(resumedOutputs, firstCredentials);
102
101
 
103
102
  const health = await fetch(`${baseUrl}/auth/v1/health`);
104
103
  expect(health.ok).toBe(true);
@@ -123,20 +122,10 @@ test.skipIf(process.env.ANKH_INFRA_DOCKER_COMPOSE_PROVIDERS_E2E !== '1')(
123
122
  900_000,
124
123
  );
125
124
 
126
- function createDependencies(): InfraOrchestrationDependencies {
125
+ function createDependencies(credentials: InfraCredentialPort): InfraOrchestrationDependencies {
127
126
  return {
128
127
  adapterResolver: createNodeInfraAdapterPackageResolver(),
129
- credentials: {
130
- resolveAsync: (reference) =>
131
- Promise.resolve(
132
- reference.name === 'SUPABASE_BOOTSTRAP'
133
- ? success(credentials)
134
- : failure(
135
- 'unexpected-control-plane-credential',
136
- `Unexpected control-plane credential ${reference.name}.`,
137
- ),
138
- ),
139
- },
128
+ credentials,
140
129
  secrets: {
141
130
  resolveAsync: (reference) =>
142
131
  Promise.resolve(
@@ -149,6 +138,73 @@ function createDependencies(): InfraOrchestrationDependencies {
149
138
  };
150
139
  }
151
140
 
141
+ class MemoryCredentialPort implements InfraCredentialPort {
142
+ private values: Readonly<Record<string, string>> | null = null;
143
+ persistCount = 0;
144
+
145
+ findAsync(
146
+ reference: InfraControlPlaneCredentialRef,
147
+ ): Promise<InfraResult<Readonly<Record<string, string>> | null>> {
148
+ if (reference.name !== bootstrapCredential.name) return Promise.resolve(success(null));
149
+ return Promise.resolve(success(this.values === null ? null : { ...this.values }));
150
+ }
151
+
152
+ resolveAsync(
153
+ reference: InfraControlPlaneCredentialRef,
154
+ ): Promise<InfraResult<Readonly<Record<string, string>>>> {
155
+ if (reference.name === bootstrapCredential.name && this.values !== null) {
156
+ return Promise.resolve(success({ ...this.values }));
157
+ }
158
+ return Promise.resolve(
159
+ failure(
160
+ 'unexpected-control-plane-credential',
161
+ `Unexpected or missing control-plane credential ${reference.name}.`,
162
+ ),
163
+ );
164
+ }
165
+
166
+ persistAsync(
167
+ reference: InfraControlPlaneCredentialRef,
168
+ values: Readonly<Record<string, string>>,
169
+ ): Promise<InfraResult<null>> {
170
+ if (reference.name !== bootstrapCredential.name) {
171
+ return Promise.resolve(
172
+ failure(
173
+ 'unexpected-control-plane-credential',
174
+ `Unexpected control-plane credential ${reference.name}.`,
175
+ ),
176
+ );
177
+ }
178
+ this.values = { ...values };
179
+ this.persistCount += 1;
180
+ return Promise.resolve(success(null));
181
+ }
182
+
183
+ readRequired(): Readonly<Record<string, string>> {
184
+ if (this.values === null) throw new Error('Expected generated Supabase bootstrap credentials.');
185
+ return { ...this.values };
186
+ }
187
+ }
188
+
189
+ function credentialFingerprint(values: Readonly<Record<string, string>>): string {
190
+ const canonical = Object.entries(values)
191
+ .sort(([left], [right]) => left.localeCompare(right))
192
+ .map(([key, value]) => `${key}\u0000${value}`)
193
+ .join('\u0001');
194
+ return createHash('sha256').update(canonical).digest('hex');
195
+ }
196
+
197
+ function privilegedCredentialValues(values: Readonly<Record<string, string>>): readonly string[] {
198
+ const { anonKey: _anonKey, ...privileged } = values;
199
+ return Object.values(privileged);
200
+ }
201
+
202
+ function readAnonKey(values: Readonly<Record<string, string>>): string {
203
+ const { anonKey } = values;
204
+ if (anonKey === undefined) throw new Error('Expected generated Supabase anonKey.');
205
+ return anonKey;
206
+ }
207
+
152
208
  function createDestroyRequest(
153
209
  previous: InfraLedger,
154
210
  confirmedResources: readonly InfraResourceIdentity[],
@@ -167,14 +223,14 @@ function persistentIdentities(ledger: InfraLedger): readonly InfraResourceIdenti
167
223
  return ledger.resources.filter(({ persistent }) => persistent).map(({ identity }) => identity);
168
224
  }
169
225
 
170
- function assertProviderNeutralOutputs(outputs: InfraLedger['outputs']): void {
226
+ function assertProviderNeutralOutputs(
227
+ outputs: InfraLedger['outputs'],
228
+ credentials: Readonly<Record<string, string>>,
229
+ ): void {
171
230
  const serialized = JSON.stringify(outputs);
172
- expect(serialized).not.toContain(credentials.serviceRoleKey);
173
- expect(serialized).not.toContain(credentials.postgresPassword);
174
- expect(serialized).not.toContain(credentials.jwtSecret);
175
- expect(serialized).not.toContain(credentials.realtimeSecretKeyBase);
176
- expect(serialized).not.toContain(credentials.realtimeDatabaseEncryptionKey);
177
- expect(serialized).not.toContain(credentials.pgMetaCryptoKey);
231
+ for (const secret of privilegedCredentialValues(credentials)) {
232
+ expect(serialized).not.toContain(secret);
233
+ }
178
234
  expect(serialized.toLowerCase()).not.toContain('kubernetes');
179
235
  expect(serialized.toLowerCase()).not.toContain('minikube');
180
236
  expect(
@@ -190,7 +246,7 @@ function assertProviderNeutralOutputs(outputs: InfraLedger['outputs']): void {
190
246
  ({ owner, environmentVariable, value }) =>
191
247
  owner.adapter === 'supabase' &&
192
248
  environmentVariable === 'EXPO_PUBLIC_SUPABASE_ANON_KEY' &&
193
- value === credentials.anonKey,
249
+ value === readAnonKey(credentials),
194
250
  ),
195
251
  ).toBe(true);
196
252
  expect(
@@ -209,23 +265,6 @@ function assertProviderNeutralOutputs(outputs: InfraLedger['outputs']): void {
209
265
  ).toBe(true);
210
266
  }
211
267
 
212
- function createJwt(role: 'anon' | 'service_role'): string {
213
- const encodedHeader = encodeJwtPart({ alg: 'HS256', typ: 'JWT' });
214
- const encodedPayload = encodeJwtPart({
215
- role,
216
- iss: 'supabase',
217
- iat: 1_700_000_000,
218
- exp: 4_102_444_800,
219
- });
220
- const unsigned = `${encodedHeader}.${encodedPayload}`;
221
- const signature = createHmac('sha256', jwtSecret).update(unsigned).digest('base64url');
222
- return `${unsigned}.${signature}`;
223
- }
224
-
225
- function encodeJwtPart(value: Readonly<Record<string, string | number>>): string {
226
- return Buffer.from(JSON.stringify(value)).toString('base64url');
227
- }
228
-
229
268
  function requireSuccess<T>(result: InfraResult<T>): T {
230
269
  if (!result.ok) throw new Error(result.diagnostics.map(({ message }) => message).join(' '));
231
270
  return result.value;