@fabrika/provider-zerops 0.8.1 → 0.9.0-cloudflare.1

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.
Files changed (133) hide show
  1. package/package.json +16 -14
  2. package/src/api/client.ts +18 -0
  3. package/src/api/readers.ts +29 -0
  4. package/src/api/types.ts +64 -0
  5. package/src/api.ts +2 -0
  6. package/src/cli.ts +11 -0
  7. package/src/collaborators.ts +0 -14
  8. package/src/control.ts +35 -57
  9. package/src/execution.ts +113 -102
  10. package/src/index.ts +2 -0
  11. package/src/installation/admin.ts +1 -1
  12. package/src/installation/deploy-options.ts +1 -2
  13. package/src/installation/deploy.ts +225 -80
  14. package/src/installation/generated/platform-console-schema.ts +3 -0
  15. package/src/installation/generated/platform-proxy-manifest.ts +21 -1
  16. package/src/installation/guide.md +48 -18
  17. package/src/installation/hosts.ts +1 -7
  18. package/src/installation/index.ts +13 -16
  19. package/src/installation/init-options.ts +2 -1
  20. package/src/installation/install-environment.ts +47 -108
  21. package/src/installation/install-options.ts +3 -1
  22. package/src/installation/install-secrets.ts +24 -68
  23. package/src/installation/install.ts +9 -4
  24. package/src/installation/sidecar.ts +5 -26
  25. package/src/installation/topology/setups.ts +15 -4
  26. package/src/installation/topology/topology.ts +31 -21
  27. package/src/installation/upgrade-options.ts +2 -2
  28. package/src/managed-env.ts +1 -174
  29. package/src/manifest.ts +11 -14
  30. package/src/namespace/claims.ts +56 -1
  31. package/src/namespace/errors.ts +1 -1
  32. package/src/namespace/manifest.ts +6 -19
  33. package/src/namespace/provisioning.ts +24 -4
  34. package/src/namespace/proxy-attempt.ts +43 -2
  35. package/src/namespace/proxy-reads.ts +12 -22
  36. package/src/namespace/types.ts +2 -0
  37. package/src/namespace-cli.ts +152 -0
  38. package/src/plan.ts +2 -7
  39. package/src/provider.ts +128 -126
  40. package/src/proxy-manifest-carrier.ts +18 -0
  41. package/src/types.ts +0 -2
  42. package/types/src/api/client.d.ts +5 -0
  43. package/types/src/api/errors.d.ts +24 -0
  44. package/types/src/api/polling.d.ts +41 -0
  45. package/types/src/api/readers.d.ts +1115 -0
  46. package/types/src/api/service-env-limit.d.ts +17 -0
  47. package/types/src/api/service-env-response.d.ts +23 -0
  48. package/types/src/api/types.d.ts +687 -0
  49. package/types/src/api.d.ts +6 -0
  50. package/types/src/authoring.d.ts +9 -0
  51. package/types/src/cli-args.d.ts +19 -0
  52. package/types/src/cli.d.ts +13 -0
  53. package/types/src/collaborators.d.ts +16 -0
  54. package/types/src/compile.d.ts +91 -0
  55. package/types/src/control.d.ts +40 -0
  56. package/types/src/database-budget.d.ts +38 -0
  57. package/types/src/descriptor.d.ts +28 -0
  58. package/types/src/execution.d.ts +125 -0
  59. package/types/src/exposure.d.ts +47 -0
  60. package/types/src/index.d.ts +31 -0
  61. package/types/src/installation/admin-options.d.ts +24 -0
  62. package/types/src/installation/admin.d.ts +35 -0
  63. package/types/src/installation/checker-release.d.ts +12 -0
  64. package/types/src/installation/deploy-options.d.ts +37 -0
  65. package/types/src/installation/deploy.d.ts +136 -0
  66. package/types/src/installation/generated/platform-console-schema.d.ts +2 -0
  67. package/types/src/installation/generated/platform-proxy-manifest.d.ts +2 -0
  68. package/types/src/installation/hosts.d.ts +34 -0
  69. package/types/src/installation/index.d.ts +7 -0
  70. package/types/src/installation/init-options.d.ts +38 -0
  71. package/types/src/installation/init.d.ts +60 -0
  72. package/types/src/installation/install-collaborators.d.ts +24 -0
  73. package/types/src/installation/install-environment.d.ts +63 -0
  74. package/types/src/installation/install-options.d.ts +49 -0
  75. package/types/src/installation/install-project.d.ts +13 -0
  76. package/types/src/installation/install-provisioning.d.ts +58 -0
  77. package/types/src/installation/install-secrets.d.ts +17 -0
  78. package/types/src/installation/install-token.d.ts +10 -0
  79. package/types/src/installation/install.d.ts +23 -0
  80. package/types/src/installation/installation-source.d.ts +8 -0
  81. package/types/src/installation/log.d.ts +44 -0
  82. package/types/src/installation/machine-key.d.ts +19 -0
  83. package/types/src/installation/placement-options.d.ts +11 -0
  84. package/types/src/installation/platform-archive.d.ts +7 -0
  85. package/types/src/installation/platform-source.d.ts +8 -0
  86. package/types/src/installation/platform-upload.d.ts +3 -0
  87. package/types/src/installation/sidecar.d.ts +49 -0
  88. package/types/src/installation/source-options.d.ts +1 -0
  89. package/types/src/installation/topology/artifacts.d.ts +8 -0
  90. package/types/src/installation/topology/index.d.ts +5 -0
  91. package/types/src/installation/topology/installation-schemas.d.ts +2242 -0
  92. package/types/src/installation/topology/invariants.d.ts +23 -0
  93. package/types/src/installation/topology/setups.d.ts +59 -0
  94. package/types/src/installation/topology/topology.d.ts +153 -0
  95. package/types/src/installation/topology/validate.d.ts +11 -0
  96. package/types/src/installation/upgrade-options.d.ts +26 -0
  97. package/types/src/installation/upgrade-output.d.ts +6 -0
  98. package/types/src/installation/upgrade.d.ts +88 -0
  99. package/types/src/managed-env.d.ts +24 -0
  100. package/types/src/manifest.d.ts +204 -0
  101. package/types/src/namespace/claims.d.ts +16 -0
  102. package/types/src/namespace/codec.d.ts +334 -0
  103. package/types/src/namespace/constants.d.ts +25 -0
  104. package/types/src/namespace/errors.d.ts +15 -0
  105. package/types/src/namespace/manifest.d.ts +4 -0
  106. package/types/src/namespace/operator.d.ts +12 -0
  107. package/types/src/namespace/provisioning.d.ts +4 -0
  108. package/types/src/namespace/proxy-attempt.d.ts +39 -0
  109. package/types/src/namespace/proxy-reads.d.ts +22 -0
  110. package/types/src/namespace/proxy-secrets.d.ts +8 -0
  111. package/types/src/namespace/proxy-source.d.ts +74 -0
  112. package/types/src/namespace/topology.d.ts +9 -0
  113. package/types/src/namespace/types.d.ts +101 -0
  114. package/types/src/namespace/update.d.ts +6 -0
  115. package/types/src/namespace/validation.d.ts +12 -0
  116. package/types/src/namespace-cli.d.ts +2 -0
  117. package/types/src/namespace.d.ts +9 -0
  118. package/types/src/plan.d.ts +11 -0
  119. package/types/src/provider.d.ts +63 -0
  120. package/types/src/proxy-manifest-carrier.d.ts +24 -0
  121. package/types/src/schema.generated.d.ts +376 -0
  122. package/types/src/service-names.d.ts +4 -0
  123. package/types/src/service-references.d.ts +13 -0
  124. package/types/src/source/constants.d.ts +6 -0
  125. package/types/src/source/port.d.ts +60 -0
  126. package/types/src/source/types.d.ts +4 -0
  127. package/types/src/source/validation.d.ts +19 -0
  128. package/types/src/source.d.ts +2 -0
  129. package/types/src/types.d.ts +119 -0
  130. package/types/src/version-attempt.d.ts +8 -0
  131. package/src/installation/mcp.ts +0 -102
  132. package/src/installation/proxy-manifest.ts +0 -214
  133. package/src/installation/secrets.ts +0 -78
@@ -1,4 +1,5 @@
1
1
  import { followLeaseLoss, isLeaseLoss, ProviderNamespaceError } from '@fabrika/provider-contract'
2
+ import type { InstallationBrand } from '@fabrika/proxy-core/brand'
2
3
  import { encodeProxyManifestJson, FABRIKA_PROXY_MANIFEST_JSON, type ProxyManifest } from '@fabrika/proxy-core/manifest'
3
4
  import {
4
5
  readableContent,
@@ -11,7 +12,12 @@ import {
11
12
  } from '../api'
12
13
  import type { Sleeper } from '../collaborators'
13
14
  import { prepareZeropsDescriptor } from '../descriptor'
14
- import { ZEROPS_PROXY_MANIFEST_PATH, type ZeropsProxyManifestCarrier, zeropsProxyManifestCarrier } from '../proxy-manifest-carrier'
15
+ import {
16
+ ZEROPS_PROXY_MANIFEST_PATH,
17
+ zeropsProxyAcceptsBrand,
18
+ type ZeropsProxyManifestCarrier,
19
+ zeropsProxyManifestCarrier,
20
+ } from '../proxy-manifest-carrier'
15
21
  import { isZeropsUploadDestination } from '../source'
16
22
  import type { ZeropsSourcePort } from '../source/port'
17
23
  import { deleteVersion } from '../version-attempt'
@@ -35,6 +41,8 @@ export interface ZeropsProxyRepublishInput {
35
41
  readonly namespaceId: string
36
42
  readonly target: ZeropsNamespaceTarget
37
43
  readonly manifest: ProxyManifest
44
+ /** The installation's client logo (ADR-0066); added to the manifest only when the pinned proxy can parse it. */
45
+ readonly brand?: InstallationBrand | null
38
46
  readonly source?: ZeropsSourcePort
39
47
  readonly api: ZeropsProxyApi
40
48
  readonly iamUrl: string
@@ -44,6 +52,11 @@ export interface ZeropsProxyRepublishInput {
44
52
  /** Aborts with `lease_lost` when the namespace lease goes, and with `cancel_requested` when a deploy is cancelled. */
45
53
  readonly signal: AbortSignal
46
54
  readonly wait: 'started' | 'active'
55
+ /**
56
+ * Reuse the proxy's ACTIVE version when it was built from this pin with this manifest identity. A deploy and a
57
+ * registry change pass true; provisioning and reconcile rebuild, so an operator's repair always reaches the platform.
58
+ */
59
+ readonly reuseActive: boolean
47
60
  /** Persist the target; the caller fences the write on the namespace lease. */
48
61
  readonly checkpoint: (target: ZeropsNamespaceTarget) => Promise<void>
49
62
  readonly boundary: () => Promise<void>
@@ -88,6 +101,28 @@ const writeProxyIam = async (input: ZeropsProxyRepublishInput, serviceId: string
88
101
  const proxySecretsDigest = async (secrets: ReadonlyMap<string, string>): Promise<string> =>
89
102
  digest([...secrets].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([key, value]) => `${key}=${value}`).join('\n'))
90
103
 
104
+ /**
105
+ * Whether the proxy already runs `manifestSha256` on this pin: the recorded deployment names both, and its version is
106
+ * still the service's ACTIVE one. Any doubt, a failed read included, answers false, and the proxy is rebuilt.
107
+ */
108
+ const servesManifest = async (input: ZeropsProxyRepublishInput, target: ZeropsNamespaceTarget, manifestSha256: string): Promise<boolean> => {
109
+ const deployed = target.proxyDeployment
110
+ const pin = target.proxySource
111
+ if (
112
+ target.ready !== true || deployed === undefined || deployed.manifestSha256 !== manifestSha256
113
+ || deployed.commitSha !== pin.commitSha || deployed.descriptorSha256 !== pin.descriptor.sha256
114
+ || deployed.repository.owner !== pin.repository.owner || deployed.repository.name !== pin.repository.name
115
+ ) return false
116
+ await input.boundary()
117
+ try {
118
+ const version = await input.api.getAppVersion({ appVersionId: deployed.appVersionId, signal: input.signal })
119
+ return version.id === deployed.appVersionId && version.status === ZEROPS_ACTIVE && version.serviceStackId === target.proxyServiceId
120
+ } catch {
121
+ input.signal.throwIfAborted()
122
+ return false
123
+ }
124
+ }
125
+
91
126
  /**
92
127
  * Rebuild the namespace proxy with `manifest` (ADR-0044). The attempt is recorded as `proxyBuild` and never
93
128
  * resumed: an inherited attempt not yet `triggered` is deleted, a triggered one is polled to a terminal state.
@@ -100,7 +135,11 @@ export async function republishProxy(input: ZeropsProxyRepublishInput): Promise<
100
135
  await verifyZeropsNamespaceProxySource(pin)
101
136
  // The pinned descriptor decides: a namespace kept on an older proxy revision still lifts the variable (ADR-0045).
102
137
  const carrier = zeropsProxyManifestCarrier(pin.descriptor.contents)
103
- const manifest = encodeProxyManifestJson(input.manifest)
138
+ // So does it for the logo: an older parser refuses the key, and the namespace keeps the mark until it is re-pinned.
139
+ const brand = input.brand ?? null
140
+ const manifest = encodeProxyManifestJson(
141
+ brand !== null && zeropsProxyAcceptsBrand(pin.descriptor.contents) ? { ...input.manifest, brand } : input.manifest,
142
+ )
104
143
  // The attempt's identity covers the IAM configuration and the proxy secrets too, so rotating any forces a rebuild.
105
144
  const proxySecrets = input.proxySecrets ?? new Map<string, string>()
106
145
  const secrets = proxySecrets.size === 0 ? '' : `\n${await proxySecretsDigest(proxySecrets)}`
@@ -173,6 +212,8 @@ export async function republishProxy(input: ZeropsProxyRepublishInput): Promise<
173
212
  if (inherited.manifestSha256 === manifestSha256) return target
174
213
  }
175
214
 
215
+ if (input.reuseActive && target.proxyBuild === undefined && await servesManifest(input, target, manifestSha256)) return target
216
+
176
217
  if (input.source === undefined) throw new Error('Zerops namespace source client is required')
177
218
  const runId = crypto.randomUUID()
178
219
  const resolved = await input.source.resolve({
@@ -1,36 +1,26 @@
1
- // What a namespace proxy pinned to an older source cannot read. Its manifest parser is strict: one row of a form it does
2
- // not know, or a CSP directive it does not know, refuses the whole manifest and blocks the namespace's next republish.
3
- // So a deploy that would publish either is refused before publication while the namespace's proxy source is not
4
- // this control plane's own.
1
+ // What a namespace proxy pinned to an older source cannot read or serve. Its manifest parser is strict: one row of a form
2
+ // it does not know, or a CSP directive it does not know, refuses the whole manifest and blocks the namespace's next
3
+ // republish; and it has no terminal sign-out. So a deploy that would publish either, or a terminal, is refused before
4
+ // publication while the namespace's proxy source is not this control plane's own.
5
5
 
6
6
  import type { ProviderApprovedAccess } from '@fabrika/provider-contract'
7
- import type { CspOrigins } from '@fabrika/proxy-core/headers'
8
- import { contractProxyTable, type ProxyEndpointRow } from '@fabrika/proxy-core/manifest'
7
+ import { contractProxyTable, type OlderProxyReaders } from '@fabrika/proxy-core/manifest'
9
8
  import type { ZeropsNamespaceProxySource } from './types'
10
9
 
11
- /**
12
- * What the namespace proxy of one older platform release parses of an application's manifest entry: a row, and the
13
- * entry's CSP directives, each by that release's own parser.
14
- */
15
- export interface OlderProxyReader {
16
- readonly row: (row: ProxyEndpointRow) => boolean
17
- readonly csp: (csp: CspOrigins) => boolean
18
- }
19
-
20
- /**
21
- * The namespace proxies of the older releases this platform serves (ADR-0062). Control holds their parsers, as it holds
22
- * their contract readers; a pin's release is not recorded, so what any of them reads is what the pin may read.
23
- */
24
- export type OlderProxyReaders = readonly [OlderProxyReader, ...OlderProxyReader[]]
10
+ export type { OlderProxyReader, OlderProxyReaders } from '@fabrika/proxy-core/manifest'
25
11
 
26
12
  /**
27
13
  * What of the approved contract's manifest entry no older release's proxy parses, as the namespace publishes it: each
28
14
  * such row by its id, and `csp` when no older proxy parses the entry's CSP (`workerSrc`, which only this release knows).
15
+ * A contract that declares a terminal adds `terminal` when no older proxy serves the sign-out its sessions end at
16
+ * (ADR-0067 §5).
29
17
  */
30
18
  export function entriesNeedingCurrentProxy(access: ProviderApprovedAccess, olderProxies: OlderProxyReaders): string[] {
31
19
  const table = contractProxyTable(access.contract, access.contractDigest)
32
20
  const rows = table.endpoints.filter((row) => !olderProxies.some((proxy) => proxy.row(row))).map((row) => row.id)
33
- return olderProxies.some((proxy) => proxy.csp(table.csp)) ? rows : [...rows, 'csp']
21
+ const csp = olderProxies.some((proxy) => proxy.csp(table.csp)) ? [] : ['csp']
22
+ const terminal = access.contract.terminal === undefined || olderProxies.some((proxy) => proxy.signsOut) ? [] : ['terminal']
23
+ return [...rows, ...csp, ...terminal]
34
24
  }
35
25
 
36
26
  /** This control plane's own proxy source, and the parsers of the older releases a pinned proxy may run. */
@@ -58,7 +48,7 @@ export function assertNamespaceProxyReads(
58
48
  return
59
49
  }
60
50
  throw new Error(
61
- `the proxy of deployment namespace \`${namespace}\` is pinned to an older source than this platform's, and no older release's proxy reads ${
51
+ `the proxy of deployment namespace \`${namespace}\` is pinned to an older source than this platform's, and no older release's proxy reads or serves ${
62
52
  rows.map((row) => `\`${row}\``).join(', ')
63
53
  }; re-pin it with \`fabrika control namespaces update --namespace=${namespace} --proxy-source=current\`, then deploy again`,
64
54
  )
@@ -81,6 +81,8 @@ export interface ZeropsNamespaceOptions {
81
81
  readonly iamUrl: string
82
82
  /** Same credential IAM receives as `FABRIKA_IAM_PROXY_KEY`. */
83
83
  readonly iamKey: string
84
+ /** The installation's own project, which no namespace may create into or adopt. Absent until `platform deploy` writes it. */
85
+ readonly installationProjectId?: string
84
86
  readonly api: ZeropsApi
85
87
  readonly sleep?: Sleeper
86
88
  }
@@ -0,0 +1,152 @@
1
+ // `fabrika namespace` on Zerops: the provider options, the plan, and the namespace each command hands Control. The
2
+ // `fabrika` command holds Control's origin and credential, and prints the result.
3
+
4
+ import type { JsonValue, ProviderDeploymentNamespace, ProviderNamespacePlan } from '@fabrika/provider-contract'
5
+ import type { NamespaceCli, NamespaceCommandContext, NamespaceCommandResult } from '@fabrika/provider-contract/app-cli'
6
+ import { parseZeropsCliArgs, type ZeropsCliArgs } from './cli-args'
7
+ import {
8
+ createZeropsNamespaceOperator,
9
+ decodeZeropsNamespaceProxySource,
10
+ verifyZeropsNamespaceProxySource,
11
+ zeropsNamespaceTargetCodec,
12
+ } from './namespace'
13
+ import type { ZeropsNamespacePostgres } from './namespace'
14
+ import { postgresSchema } from './namespace/codec'
15
+
16
+ const NAMESPACE_USAGE = `fabrika namespace — Zerops deployment namespaces
17
+
18
+ Usage:
19
+ fabrika namespace plan --provider=zerops --id=<id> --env=<env> --preset=<cheap|mid|full> [namespace options]
20
+ fabrika namespace create --provider=zerops --id=<id> --env=<env> --preset=<cheap|mid|full> [namespace options] [--control-url=<url>]
21
+ fabrika namespace adopt --provider=zerops --id=<id> --env=<env> --preset=<cheap|mid|full> --project-id=<id> [namespace options] [--control-url=<url>]
22
+ fabrika namespace reconcile --provider=zerops --id=<id> [--control-url=<url>]
23
+
24
+ Options:
25
+ --id=<id> Namespace id.
26
+ --env=<env> Environment.
27
+ --preset=<cheap|mid|full> Provider-owned namespace preset.
28
+ --exclusive-app=<id> App reserved by a full namespace.
29
+ --project-id=<id> Existing Zerops project id for adoption.
30
+ --project-name=<name> Zerops project name (create: default fabrika-<env>-<id>;
31
+ adopt: the existing project's name, default the namespace id).
32
+ --core-package=<tier> LIGHT or SERIOUS.
33
+ --public-access=<mode> custom-domain or zerops-subdomain.
34
+ --postgres-type=<type> Cheap-tier PostgreSQL type.
35
+ --postgres-profile=<profile> Cheap-tier PostgreSQL profile.
36
+ --control-url=<url> Control origin; or FABRIKA_CONTROL_URL.
37
+ --json Print the result as JSON instead of rows.
38
+ -h, --help Show this help.
39
+
40
+ \`plan\` prints one \`<path>\\t<value>\` row per field of the plan, keyed by its path in --json; a multi-line
41
+ value such as the proxy's descriptor file is shown by size only. The other commands print one
42
+ \`<id>\\t<env>\\t<state>\\t<exclusive app>\` row.
43
+
44
+ Mutating namespace commands read the Bearer credential from FABRIKA_CONTROL_TOKEN.
45
+ Plan/create/adopt require FABRIKA_ZEROPS_PROXY_SOURCE_JSON:
46
+ {"repository":{"owner":"...","name":"..."},"commitSha":"...","descriptor":{"path":"zerops.yaml","contents":"...","sha256":"..."}}
47
+ Reconcile uses the persisted namespace source.
48
+ `
49
+
50
+ const required = (value: string | undefined, label: string): string => {
51
+ if (value === undefined || value.trim() === '') {
52
+ throw new Error(`${label} is required`)
53
+ }
54
+ return value.trim()
55
+ }
56
+
57
+ const postgres = (args: ZeropsCliArgs): ZeropsNamespacePostgres | undefined => {
58
+ const type = args.postgresType
59
+ const profile = args.postgresProfile
60
+ if (type === undefined && profile === undefined) return undefined
61
+ if (type === undefined) {
62
+ throw new Error('--postgres-type is required with --postgres-profile')
63
+ }
64
+ const result = postgresSchema.safeParse({ type, profile })
65
+ if (result.success) return result.data
66
+ if (result.error.issues[0]?.path[0] === 'type') throw new Error('--postgres-type must be postgresql:ha@18 or postgresql:single@18')
67
+ throw new Error(`--postgres-profile ${profile} is not supported by ${type}`)
68
+ }
69
+
70
+ const planOptions = (args: ZeropsCliArgs): { [key: string]: JsonValue } | undefined => {
71
+ const values: { [key: string]: JsonValue } = {}
72
+ if (args.projectName !== undefined) values['projectName'] = required(args.projectName, '--project-name')
73
+ if (args.corePackage !== undefined) values['corePackage'] = required(args.corePackage, '--core-package')
74
+ if (args.publicAccess !== undefined) values['publicAccess'] = required(args.publicAccess, '--public-access')
75
+ const database = postgres(args)
76
+ if (database !== undefined) {
77
+ values['postgres'] = database.profile === undefined ? { type: database.type } : { type: database.type, profile: database.profile }
78
+ }
79
+ return Object.keys(values).length === 0 ? undefined : values
80
+ }
81
+
82
+ const namespacePlan = async (
83
+ args: ZeropsCliArgs,
84
+ source: Readonly<Record<string, string | undefined>>,
85
+ ): Promise<ProviderNamespacePlan> => {
86
+ // `FABRIKA_ZEROPS_*` because Zerops reserves the bare `ZEROPS_` prefix and refuses to store a custom
87
+ // variable using it (`userDataZeropsPrefixForbidden`).
88
+ const serialized = required(source['FABRIKA_ZEROPS_PROXY_SOURCE_JSON'], 'FABRIKA_ZEROPS_PROXY_SOURCE_JSON')
89
+ let value: unknown
90
+ try {
91
+ value = JSON.parse(serialized)
92
+ } catch {
93
+ throw new Error('FABRIKA_ZEROPS_PROXY_SOURCE_JSON must contain valid JSON')
94
+ }
95
+ const proxySource = decodeZeropsNamespaceProxySource(value)
96
+ await verifyZeropsNamespaceProxySource(proxySource)
97
+ const operator = createZeropsNamespaceOperator({ proxySource })
98
+ const options = planOptions(args)
99
+ return operator.plan({
100
+ id: required(args.namespaceId, '--id'),
101
+ env: required(args.env, '--env'),
102
+ preset: required(args.preset, '--preset'),
103
+ ...(args.exclusiveAppId === undefined ? {} : { exclusiveAppId: required(args.exclusiveAppId, '--exclusive-app') }),
104
+ ...(options === undefined ? {} : { options }),
105
+ })
106
+ }
107
+
108
+ /** One namespace command. Every input is validated before `context.control()` is read. */
109
+ const runZeropsNamespaceCommand = async (
110
+ subcommand: string,
111
+ argv: readonly string[],
112
+ context: NamespaceCommandContext,
113
+ ): Promise<NamespaceCommandResult> => {
114
+ const args = parseZeropsCliArgs(['namespace', subcommand, ...argv])
115
+ if (subcommand === 'plan') {
116
+ return { plan: await namespacePlan(args, context.source) }
117
+ }
118
+ if (subcommand === 'reconcile') {
119
+ const id = required(args.namespaceId, '--id')
120
+ return { namespace: await context.control().reconcile(id) }
121
+ }
122
+ if (subcommand !== 'create' && subcommand !== 'adopt') {
123
+ throw new Error(`Unknown namespace command: ${subcommand}`)
124
+ }
125
+
126
+ const planned = await namespacePlan(args, context.source)
127
+ const control = context.control()
128
+ if (subcommand === 'create') {
129
+ return { namespace: await control.create(planned.namespace) }
130
+ }
131
+
132
+ const projectId = required(args.projectId, '--project-id')
133
+ const decoded = zeropsNamespaceTargetCodec.decode(planned.namespace.target.payload)
134
+ const namespace: ProviderDeploymentNamespace = {
135
+ ...planned.namespace,
136
+ target: {
137
+ provider: 'zerops',
138
+ version: zeropsNamespaceTargetCodec.version,
139
+ payload: zeropsNamespaceTargetCodec.encode({
140
+ ...decoded,
141
+ // An adopted project keeps its own name, so the plan's default for a created one does not apply.
142
+ projectName: args.projectName ?? planned.namespace.id,
143
+ managed: false,
144
+ projectId,
145
+ ready: false,
146
+ }),
147
+ },
148
+ }
149
+ return { namespace: await control.adopt(namespace) }
150
+ }
151
+
152
+ export const zeropsNamespaceCli: NamespaceCli = { usage: NAMESPACE_USAGE, run: runZeropsNamespaceCommand }
package/src/plan.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { type FabrikaManifest, manifestServiceHostnames } from './manifest'
2
- import type { ZeropsRuntimeTarget } from './types'
3
2
 
4
3
  export type ZeropsStepKind =
5
4
  | 'apply-import'
@@ -11,7 +10,6 @@ export type ZeropsStepKind =
11
10
  | 'upload-source'
12
11
  | 'trigger-build'
13
12
  | 'await-version'
14
- | 'reconcile-schema'
15
13
 
16
14
  export interface ZeropsStep {
17
15
  readonly id: string
@@ -30,7 +28,7 @@ export const resolveDeployHostname = (manifest: FabrikaManifest): string => {
30
28
  }
31
29
 
32
30
  /** Build the provider-owned deploy order without touching the platform. */
33
- export const buildZeropsPlan = (manifest: FabrikaManifest, target: ZeropsRuntimeTarget): readonly ZeropsStep[] => {
31
+ export const buildZeropsPlan = (manifest: FabrikaManifest): readonly ZeropsStep[] => {
34
32
  const serviceHostnames = manifestServiceHostnames(manifest)
35
33
  const steps: ZeropsStep[] = []
36
34
 
@@ -48,7 +46,7 @@ export const buildZeropsPlan = (manifest: FabrikaManifest, target: ZeropsRuntime
48
46
  steps.push({
49
47
  id: 'raise-start-memory',
50
48
  kind: 'raise-start-memory',
51
- description: "Raise the deploy service's minimum RAM to what its init commands need",
49
+ description: "Raise the deploy service's minimum RAM to what its init commands need, and pin a dev environment to one container",
52
50
  })
53
51
  steps.push({
54
52
  id: 'size-database',
@@ -59,9 +57,6 @@ export const buildZeropsPlan = (manifest: FabrikaManifest, target: ZeropsRuntime
59
57
  steps.push({ id: 'upload-source', kind: 'upload-source', description: 'Upload the resolved repository snapshot' })
60
58
  steps.push({ id: 'trigger-build', kind: 'trigger-build', description: 'Trigger the Zerops build+deploy pipeline' })
61
59
  steps.push({ id: 'await-version', kind: 'await-version', description: 'Await the Zerops pipeline and relay its build log' })
62
- if (manifest.app.schema !== undefined && target.propustkaUrl !== undefined) {
63
- steps.push({ id: 'reconcile-schema', kind: 'reconcile-schema', description: 'Reconcile authz schema into IAM' })
64
- }
65
60
 
66
61
  return steps
67
62
  }