ReviewOS

also looking at this

stacks/ts-cloud

feat(operations): fleet inventory, attach preflight, and the exports that were missing

#192
Merged glennmichael123 wants to merge feat/fleet-inventory-and-attach-preflight into main
11 files +1313 -5
packages/ts-cloud/src/deploy/site-config-editor.tsmodified+63-0
Changes to packages/ts-cloud/src/deploy/site-config-editor.ts
@@ -358,3 +358,66 @@ function findMatchingBrace(text: string, start: number): number {
358358
359359 throw new Error('Could not find the closing brace for sites: { ... } in cloud.config.ts')
360360}
361
362export interface SetAttachToInput {
363 configText: string
364 /** Slug of the project that owns the box this one is joining. */
365 owner: string
366}
367
368/**
369 * Set `cloud.attachTo`, joining this project to a server another project owns.
370 *
371 * Deliberately narrow. This edits TypeScript source with text, which is only
372 * defensible while it refuses everything it does not certainly understand, so
373 * it handles exactly the shape the templates generate:
374 *
375 * cloud: {
376 * provider: 'hetzner',
377 * },
378 *
379 * Anything else - two `cloud:` blocks, a nested object inside one, no block at
380 * all - throws with what it saw, and the caller prints the edit for a person to
381 * make. A config mangled by a clever regex is a far worse outcome than a config
382 * the tool declined to touch.
383 *
384 * Idempotent: a config already attached to `owner` comes back byte for byte.
385 *
386 * @see https://github.com/stacksjs/ts-cloud/issues/167
387 */
388export function setAttachToInCloudConfig(input: SetAttachToInput): string {
389 const { configText, owner } = input
390 const blocks = [...configText.matchAll(/\n( {2})cloud: \{\n([\s\S]*?)\n\1\},\n/g)]
391
392 const [match] = blocks
393 if (!match) throw new Error('No `cloud: { ... }` block found in the cloud config')
394 if (blocks.length > 1) {
395 throw new Error(`Found ${blocks.length} \`cloud: { ... }\` blocks in the cloud config, so which one to edit is ambiguous`)
396 }
397
398 const [whole, indent, body] = match
399 if (indent === undefined || body === undefined) {
400 throw new Error('The `cloud: { ... }` block did not parse into an indent and a body')
401 }
402
403 if (body.includes('{')) {
404 throw new Error('The `cloud: { ... }` block holds a nested object, which this edit does not attempt to rewrite')
405 }
406
407 const existing = body.match(/^\s*attachTo:\s*(['"])([^'"]*)\1\s*,?\s*$/m)
408 if (existing) {
409 const [line, quote = '\'', current = ''] = existing
410 if (current === owner) return configText
411
412 return configText.replace(whole, whole.replace(line, line.replace(`${quote}${current}${quote}`, `'${owner}'`)))
413 }
414
415 const inner = `${indent} `
416 return configText.replace(
417 whole,
418 whole.replace(
419 `\n${indent}},\n`,
420 `\n${inner}// Deploy onto the box '${owner}' owns rather than provisioning one.\n${inner}attachTo: '${owner}',\n${indent}},\n`,
421 ),
422 )
423}