@crvouga/dev-fleet 0.0.0-stage → 0.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/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
- package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
- package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
- package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
- package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
- package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
- package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
- package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
- package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
- package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
- package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/agents/antigravity-proxy.ts +3 -0
- package/agents/audit.md +89 -0
- package/agents/claude-proxy +5 -0
- package/agents/claude-proxy.ts +3 -0
- package/agents/codex-proxy.ts +3 -0
- package/agents/grok-proxy.ts +3 -0
- package/agents/managed-proxy.ts +55 -0
- package/agents/opencode-proxy.ts +3 -0
- package/agents/registry.json +209 -0
- package/agents/registry.schema.json +149 -0
- package/agents/setup.md +158 -0
- package/agents/t3.json +40 -0
- package/ansible/macos/manager.yml +30 -0
- package/ansible/macos/policy.json +9 -0
- package/ansible/monitoring/README.md +215 -0
- package/ansible/monitoring/agent-darwin.yml +198 -0
- package/ansible/monitoring/agent.yml +172 -0
- package/ansible/monitoring/beszel-agent.service +42 -0
- package/ansible/monitoring/compose.yml +74 -0
- package/ansible/monitoring/hub-darwin.yml +102 -0
- package/ansible/monitoring/hub-route-darwin.yml +51 -0
- package/ansible/monitoring/lock.json +16 -0
- package/ansible/net/remote-probe.sh +13 -0
- package/ansible/pyproject.toml +12 -0
- package/ansible/ubuntu/authorize-sudo.sh +51 -0
- package/ansible/ubuntu/baseline.yml +89 -0
- package/ansible/ubuntu/disk-gc.py +336 -0
- package/ansible/ubuntu/disk-guard.py +104 -0
- package/ansible/ubuntu/policy.json +52 -0
- package/ansible/ubuntu/recovery.yml +15 -0
- package/ansible/ubuntu/t3/policy.json +7 -0
- package/ansible/ubuntu/t3-desktop.yml +220 -0
- package/ansible/ubuntu/t3-health.sh +33 -0
- package/ansible/ubuntu/t3-policy.yml +76 -0
- package/ansible/ubuntu/t3-route.yml +28 -0
- package/ansible/ubuntu/tailscale.yml +78 -0
- package/ansible/ubuntu/tasks/credentials.yml +47 -0
- package/ansible/ubuntu/tasks/docker.yml +31 -0
- package/ansible/ubuntu/tasks/packages.yml +56 -0
- package/ansible/ubuntu/tasks/power.yml +51 -0
- package/ansible/ubuntu/tasks/recovery.yml +39 -0
- package/ansible/ubuntu/tasks/resources.yml +312 -0
- package/ansible/ubuntu/tasks/services.yml +31 -0
- package/ansible/ubuntu/tasks/ssh.yml +71 -0
- package/ansible/ubuntu/tasks/user.yml +120 -0
- package/ansible/uv.lock +190 -0
- package/bin/devfleet.mjs +36 -0
- package/bootstrap/bootstrap.ps1 +50 -0
- package/bootstrap/bootstrap.sh +80 -0
- package/bootstrap/devfleet +7 -0
- package/bootstrap/docker-desktop.sh +33 -0
- package/bootstrap/doctor.ts +234 -0
- package/bootstrap/fleet-manager-run.sh +103 -0
- package/bootstrap/fleet-remote-bootstrap.sh +258 -0
- package/bootstrap/global-config.ts +171 -0
- package/bootstrap/install-launcher.sh +10 -0
- package/bootstrap/native-aws.ts +86 -0
- package/bootstrap/native-t3.ts +154 -0
- package/bootstrap/npm.ts +358 -0
- package/bootstrap/release-version.ts +157 -0
- package/bootstrap/setup-status.ts +51 -0
- package/bootstrap/setup.ts +88 -0
- package/bootstrap/shell-path.ts +105 -0
- package/bootstrap/verify-t3-archive.py +49 -0
- package/bootstrap/worker-auth.ts +65 -0
- package/bootstrap/worker-github.ts +47 -0
- package/bootstrap/worker-tools.ts +24 -0
- package/cliproxy/AGENTS.md +11 -0
- package/cliproxy/README.md +269 -0
- package/cliproxy/clients/bootstrap.py +143 -0
- package/cliproxy/clients/launcher.py +57 -0
- package/cliproxy/compose.yaml +90 -0
- package/cliproxy/config.template.yaml +70 -0
- package/cliproxy/docs/cloud-adoption.md +16 -0
- package/cliproxy/docs/deployment.png +0 -0
- package/cliproxy/docs/deployment.svg +109 -0
- package/cliproxy/docs/health-workflow.example.yml +185 -0
- package/cliproxy/docs/migration.md +131 -0
- package/cliproxy/docs/model-health-report.schema.json +364 -0
- package/cliproxy/docs/model-health.md +393 -0
- package/cliproxy/image.lock.json +5 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
- package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
- package/cliproxy/models.json +1618 -0
- package/cliproxy/requirements.lock +78 -0
- package/cliproxy/scripts/accounts.py +38 -0
- package/cliproxy/scripts/audit.py +123 -0
- package/cliproxy/scripts/backup +234 -0
- package/cliproxy/scripts/check.py +34 -0
- package/cliproxy/scripts/checkpoint.py +226 -0
- package/cliproxy/scripts/cli.py +64 -0
- package/cliproxy/scripts/cliproxy_host.py +257 -0
- package/cliproxy/scripts/cloud_state.py +150 -0
- package/cliproxy/scripts/container-health +6 -0
- package/cliproxy/scripts/docker +24 -0
- package/cliproxy/scripts/external-health.py +79 -0
- package/cliproxy/scripts/fleet_config.py +43 -0
- package/cliproxy/scripts/fleet_sync.py +274 -0
- package/cliproxy/scripts/initialize +84 -0
- package/cliproxy/scripts/install-dependencies +48 -0
- package/cliproxy/scripts/install.py +406 -0
- package/cliproxy/scripts/model_health.py +16 -0
- package/cliproxy/scripts/modelhealth/__init__.py +5 -0
- package/cliproxy/scripts/modelhealth/checks.py +429 -0
- package/cliproxy/scripts/modelhealth/cli.py +117 -0
- package/cliproxy/scripts/modelhealth/config.py +186 -0
- package/cliproxy/scripts/modelhealth/emulator.py +536 -0
- package/cliproxy/scripts/modelhealth/expect.py +88 -0
- package/cliproxy/scripts/modelhealth/findings.py +183 -0
- package/cliproxy/scripts/modelhealth/protocol.py +330 -0
- package/cliproxy/scripts/modelhealth/redact.py +28 -0
- package/cliproxy/scripts/modelhealth/report.py +150 -0
- package/cliproxy/scripts/modelhealth/runner.py +390 -0
- package/cliproxy/scripts/modelhealth/scenario.py +131 -0
- package/cliproxy/scripts/modelhealth/schema.py +63 -0
- package/cliproxy/scripts/modelhealth/simulate.py +111 -0
- package/cliproxy/scripts/modelhealth/transport.py +213 -0
- package/cliproxy/scripts/monitor +68 -0
- package/cliproxy/scripts/ops +527 -0
- package/cliproxy/scripts/public-endpoint +127 -0
- package/cliproxy/scripts/recover_node.py +177 -0
- package/cliproxy/scripts/restore-verify +60 -0
- package/cliproxy/scripts/retention.py +144 -0
- package/cliproxy/scripts/service-start +7 -0
- package/cliproxy/scripts/validate-config +56 -0
- package/cliproxy/scripts/vault-access.py +57 -0
- package/cliproxy/scripts/vault-backup-login +55 -0
- package/cliproxy/scripts/vault-recovery +56 -0
- package/cliproxy/scripts/vault-renew +30 -0
- package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
- package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
- package/cliproxy/systemd/cliproxy-backup.service +13 -0
- package/cliproxy/systemd/cliproxy-backup.timer +8 -0
- package/cliproxy/systemd/cliproxy-fleet.service +17 -0
- package/cliproxy/systemd/cliproxy-gc.service +13 -0
- package/cliproxy/systemd/cliproxy-gc.timer +8 -0
- package/cliproxy/systemd/cliproxy-monitor.service +8 -0
- package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
- package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
- package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
- package/cliproxy/vendor/LICENSE +22 -0
- package/cliproxy/vendor/config.upstream.yaml +1349 -0
- package/docs/consumer-setup.md +408 -0
- package/docs/declared.md +89 -0
- package/docs/machine-setup.md +399 -0
- package/docs/operations.md +276 -0
- package/docs/releasing.md +103 -0
- package/docs/state.md +254 -0
- package/docs/topology.md +67 -0
- package/docs/ubuntu-sync.md +351 -0
- package/docs/worker-contract.md +67 -0
- package/examples/fleet.json +100 -0
- package/examples/roster.json +13 -0
- package/llms.txt +35 -0
- package/mise.lock +1521 -0
- package/mise.toml +110 -0
- package/package.json +74 -4
- package/schema/fleet.schema.json +420 -0
- package/schema/roster.schema.json +77 -0
- package/src/agents/account-relay.ts +111 -0
- package/src/agents/accounts.ts +85 -0
- package/src/agents/audit.ts +77 -0
- package/src/agents/cli.ts +13 -0
- package/src/agents/command.ts +204 -0
- package/src/agents/converge.ts +123 -0
- package/src/agents/doctor.ts +107 -0
- package/src/agents/errors.ts +2 -0
- package/src/agents/files.ts +73 -0
- package/src/agents/fleet-verify.ts +165 -0
- package/src/agents/launch.ts +290 -0
- package/src/agents/native-verify.ts +371 -0
- package/src/agents/provider-errors.ts +57 -0
- package/src/agents/registry.ts +61 -0
- package/src/agents/render.ts +110 -0
- package/src/agents/round-trip.ts +192 -0
- package/src/agents/secrets.ts +13 -0
- package/src/agents/toolchain.ts +34 -0
- package/src/agents/verify-cache.ts +103 -0
- package/src/agents/verify.ts +127 -0
- package/src/auth-checks.ts +163 -0
- package/src/cli/agent-guide.ts +129 -0
- package/src/cli/app.ts +122 -0
- package/src/cli/cli.ts +28 -0
- package/src/cli/command-index.ts +57 -0
- package/src/cli/contracts.ts +25 -0
- package/src/cli/fleet-reference.ts +17 -0
- package/src/cli/fleet-state.ts +499 -0
- package/src/cli/fleet-sync-checkout.ts +130 -0
- package/src/cli/fleet-sync-enrollment.ts +140 -0
- package/src/cli/fleet-sync-forward.ts +134 -0
- package/src/cli/fleet-sync-manager.ts +249 -0
- package/src/cli/fleet-sync-monitoring.ts +185 -0
- package/src/cli/fleet-sync-output.ts +317 -0
- package/src/cli/fleet-sync-privilege.ts +27 -0
- package/src/cli/fleet-sync-progress.ts +109 -0
- package/src/cli/fleet-sync-remote.ts +242 -0
- package/src/cli/fleet-sync-report.ts +26 -0
- package/src/cli/fleet-sync-schedule.ts +138 -0
- package/src/cli/fleet-sync-state.ts +86 -0
- package/src/cli/fleet-sync-vault.ts +166 -0
- package/src/cli/fleet-sync.ts +375 -0
- package/src/cli/fleet.ts +165 -0
- package/src/cli/managed-health.ts +219 -0
- package/src/cli/node-deployment.ts +34 -0
- package/src/cli/node-sync.ts +121 -0
- package/src/cli/node-t3.ts +31 -0
- package/src/cli/options.ts +141 -0
- package/src/cli/probe-source.ts +7 -0
- package/src/cli/program.ts +185 -0
- package/src/cli/project.ts +51 -0
- package/src/cli/register-fleet.ts +296 -0
- package/src/cli/registry.ts +185 -0
- package/src/cli/repair-apply.ts +67 -0
- package/src/cli/repair-guard.ts +11 -0
- package/src/cli/repair-inspect.ts +150 -0
- package/src/cli/repair-script.ts +76 -0
- package/src/cli/repair.ts +209 -0
- package/src/cli/roster.ts +105 -0
- package/src/cli/search.ts +35 -0
- package/src/cli/settings.ts +67 -0
- package/src/cli/source-operations.ts +102 -0
- package/src/cli/t3/command.ts +90 -0
- package/src/cli/t3/register.ts +55 -0
- package/src/cli/tailnet.ts +127 -0
- package/src/cli/ui-actions.ts +133 -0
- package/src/cli/ui-controller.ts +113 -0
- package/src/cli/ui-data.ts +106 -0
- package/src/cli/ui-execute.ts +163 -0
- package/src/cli/ui-repairs.ts +34 -0
- package/src/cli/ui-reports.ts +211 -0
- package/src/cli/ui-targets.ts +100 -0
- package/src/cli/ui-terminal.ts +109 -0
- package/src/cli/ui-types.ts +49 -0
- package/src/cli/ui-wizard.ts +101 -0
- package/src/cli/ui.ts +9 -0
- package/src/cli/worker-health-output.ts +37 -0
- package/src/config.ts +186 -0
- package/src/configure.ts +255 -0
- package/src/definition.ts +290 -0
- package/src/diagnostics.ts +92 -0
- package/src/doctor-check.ts +10 -0
- package/src/errors.ts +1 -0
- package/src/fingerprint.ts +120 -0
- package/src/fleet-config.ts +174 -0
- package/src/input-hashes.ts +73 -0
- package/src/machine/local-source.ts +52 -0
- package/src/machine/lock-error.ts +2 -0
- package/src/machine/platform.ts +44 -0
- package/src/machine/source.ts +68 -0
- package/src/machine/state.ts +173 -0
- package/src/machine-health.ts +95 -0
- package/src/managed-source.ts +47 -0
- package/src/manager-failure.ts +48 -0
- package/src/manager-sync.ts +405 -0
- package/src/manager.ts +62 -0
- package/src/monitoring/agent-evidence.ts +146 -0
- package/src/monitoring/apply.ts +183 -0
- package/src/monitoring/beszel.ts +198 -0
- package/src/monitoring/enroll.ts +111 -0
- package/src/monitoring/hub-access.ts +114 -0
- package/src/monitoring/hub-backup.ts +121 -0
- package/src/monitoring/hub-viewer.ts +142 -0
- package/src/monitoring/hub.ts +93 -0
- package/src/monitoring/manager-agent.ts +73 -0
- package/src/monitoring/manager-hub.ts +248 -0
- package/src/monitoring/remote.ts +65 -0
- package/src/network-definition.ts +83 -0
- package/src/node-converge-linux.ts +374 -0
- package/src/node-converge-shared.ts +83 -0
- package/src/node-converge.ts +40 -0
- package/src/onboard.ts +35 -0
- package/src/package-info.ts +45 -0
- package/src/platform/ansible-receipt.ts +128 -0
- package/src/platform/ansible.ts +66 -0
- package/src/platform/darwin.ts +203 -0
- package/src/platform/detect.ts +11 -0
- package/src/platform/fallback.ts +110 -0
- package/src/platform/index.ts +38 -0
- package/src/platform/launchd.ts +98 -0
- package/src/platform/linux.ts +139 -0
- package/src/platform/ps.ts +45 -0
- package/src/platform/resource-verify.ts +132 -0
- package/src/platform/systemd-user.ts +95 -0
- package/src/platform/t3-policy.ts +137 -0
- package/src/platform/t3-route.ts +68 -0
- package/src/platform/t3-service.ts +75 -0
- package/src/platform/types.ts +123 -0
- package/src/platform/ubuntu-audit.ts +66 -0
- package/src/platform/ubuntu-verify.ts +105 -0
- package/src/platform/windows.ts +148 -0
- package/src/power-recovery.ts +55 -0
- package/src/probe.ts +51 -0
- package/src/protocol.ts +119 -0
- package/src/proxy-converge.ts +85 -0
- package/src/remote-bootstrap.ts +210 -0
- package/src/remote-entry.ts +89 -0
- package/src/remote-lock.ts +12 -0
- package/src/repair-catalog.ts +206 -0
- package/src/repair-plan.ts +78 -0
- package/src/repair-runner.ts +84 -0
- package/src/report.ts +71 -0
- package/src/runner.ts +104 -0
- package/src/secrets.ts +169 -0
- package/src/service-auth.ts +115 -0
- package/src/ssh.ts +135 -0
- package/src/state/bucket.ts +208 -0
- package/src/state/check.ts +74 -0
- package/src/state/connection.ts +92 -0
- package/src/state/enrollment.ts +45 -0
- package/src/state/legacy.ts +89 -0
- package/src/state/litestream.ts +143 -0
- package/src/state/operator.ts +20 -0
- package/src/state/paths.ts +40 -0
- package/src/state/roster.ts +62 -0
- package/src/state/store.ts +169 -0
- package/src/state/sync.ts +352 -0
- package/src/sync.ts +104 -0
- package/src/t3/accounts.ts +81 -0
- package/src/t3/desired.ts +165 -0
- package/src/t3/files.ts +81 -0
- package/src/t3/names-check.ts +53 -0
- package/src/t3/names.ts +155 -0
- package/src/t3/plan.ts +82 -0
- package/src/t3/providers.ts +74 -0
- package/src/t3/reconcile.ts +123 -0
- package/src/t3-endpoint.ts +104 -0
- package/src/t3-linked-check.ts +123 -0
- package/src/tailnet-health.ts +231 -0
- package/src/tailnet-matching.ts +37 -0
- package/src/tailscale.ts +141 -0
- package/src/toolchain-checks.ts +108 -0
- package/src/topology.ts +24 -0
- package/src/transport.ts +122 -0
- package/src/types.ts +99 -0
- package/src/vault-config.ts +38 -0
- package/src/worker-access-checks.ts +168 -0
- package/src/worker-capacity.ts +34 -0
- package/src/worker-host-checks.ts +181 -0
- package/src/worker-policy.ts +64 -0
- package/src/worker-readiness.ts +64 -0
- package/src/workstation-checks.ts +29 -0
package/bootstrap/npm.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bun run bootstrap`: the one-time npm setup for publishing this package.
|
|
3
|
+
*
|
|
4
|
+
* npm can publish new versions through OIDC but cannot create a package that
|
|
5
|
+
* does not exist yet. So once, with your own npm login: publish the seed
|
|
6
|
+
* version, make the Release workflow the package's trusted publisher, and tag
|
|
7
|
+
* the commit that was published. Every step measures first and skips what is
|
|
8
|
+
* already done, so it is safe to run again after any interruption.
|
|
9
|
+
*
|
|
10
|
+
* bun run bootstrap walk through and do it
|
|
11
|
+
* bun run bootstrap --yes do not ask before publishing
|
|
12
|
+
* bun run bootstrap --skip-check do not run `bun run check` first
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { join, resolve } from 'node:path';
|
|
18
|
+
import { confirm } from '@inquirer/prompts';
|
|
19
|
+
|
|
20
|
+
export const SEED_VERSION = '0.1.0';
|
|
21
|
+
export const WORKFLOW_FILE = 'release.yml';
|
|
22
|
+
const MIN_NPM = [11, 10] as const;
|
|
23
|
+
|
|
24
|
+
export interface Exec {
|
|
25
|
+
/** Run quietly and return the output. */
|
|
26
|
+
capture(command: string[], cwd?: string): Result;
|
|
27
|
+
/** Run with the terminal attached, for prompts and browser sign-in. */
|
|
28
|
+
interactive(command: string[], cwd?: string): boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Result {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
stdout: string;
|
|
34
|
+
stderr: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Target {
|
|
38
|
+
/** `@scope/name` from package.json. */
|
|
39
|
+
name: string;
|
|
40
|
+
/** `owner/repo` from package.json. */
|
|
41
|
+
repo: string;
|
|
42
|
+
root: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface State {
|
|
46
|
+
npmVersion: string | null;
|
|
47
|
+
user: string | null;
|
|
48
|
+
published: boolean;
|
|
49
|
+
/** Null until the package exists: there is nothing to look at yet. */
|
|
50
|
+
trusted: boolean | null;
|
|
51
|
+
tagLocal: boolean;
|
|
52
|
+
tagRemote: boolean;
|
|
53
|
+
/** Commit npm recorded for the seed version, when it was published. */
|
|
54
|
+
publishedCommit: string | null;
|
|
55
|
+
clean: boolean;
|
|
56
|
+
onMain: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type StepId = 'login' | 'publish' | 'trust' | 'tag';
|
|
60
|
+
|
|
61
|
+
export interface Step {
|
|
62
|
+
id: StepId;
|
|
63
|
+
title: string;
|
|
64
|
+
done: boolean;
|
|
65
|
+
/** Why the step cannot run right now. */
|
|
66
|
+
blocked: string | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const seedTag = `v${SEED_VERSION}`;
|
|
70
|
+
|
|
71
|
+
export function readTarget(root: string): Target {
|
|
72
|
+
const manifest = JSON.parse(
|
|
73
|
+
readFileSync(join(root, 'package.json'), 'utf8')
|
|
74
|
+
) as { name: string; repository: string };
|
|
75
|
+
return {
|
|
76
|
+
name: manifest.name,
|
|
77
|
+
repo: manifest.repository.replace(/^github:/, ''),
|
|
78
|
+
root,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function npmIsRecent(version: string | null): boolean {
|
|
83
|
+
const [major = 0, minor = 0] = (version ?? '').split('.').map(Number);
|
|
84
|
+
return major > MIN_NPM[0] || (major === MIN_NPM[0] && minor >= MIN_NPM[1]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function measure(exec: Exec, target: Target): State {
|
|
88
|
+
const { name, repo, root } = target;
|
|
89
|
+
const npm = exec.capture(['npm', '--version']);
|
|
90
|
+
const whoami = exec.capture(['npm', 'whoami']);
|
|
91
|
+
const view = exec.capture([
|
|
92
|
+
'npm',
|
|
93
|
+
'view',
|
|
94
|
+
`${name}@${SEED_VERSION}`,
|
|
95
|
+
'gitHead',
|
|
96
|
+
]);
|
|
97
|
+
const any = exec.capture(['npm', 'view', name, 'name']);
|
|
98
|
+
if (!any.ok && !/E404/.test(any.stderr))
|
|
99
|
+
throw new Error('Could not reach the npm registry. Check the network.');
|
|
100
|
+
const published = any.ok;
|
|
101
|
+
const trust = published
|
|
102
|
+
? exec.capture(['npm', 'trust', 'list', name, '--json'])
|
|
103
|
+
: null;
|
|
104
|
+
exec.capture(['git', 'fetch', '--quiet', 'origin', 'main', '--tags'], root);
|
|
105
|
+
const head = exec.capture(['git', 'rev-parse', 'HEAD'], root).stdout.trim();
|
|
106
|
+
const main = exec
|
|
107
|
+
.capture(['git', 'rev-parse', 'origin/main'], root)
|
|
108
|
+
.stdout.trim();
|
|
109
|
+
return {
|
|
110
|
+
npmVersion: npm.ok ? npm.stdout.trim() : null,
|
|
111
|
+
user: whoami.ok ? whoami.stdout.trim() : null,
|
|
112
|
+
published,
|
|
113
|
+
trusted: trust
|
|
114
|
+
? trust.ok &&
|
|
115
|
+
trust.stdout.includes(repo) &&
|
|
116
|
+
trust.stdout.includes(WORKFLOW_FILE)
|
|
117
|
+
: null,
|
|
118
|
+
tagLocal: exec.capture(
|
|
119
|
+
['git', 'rev-parse', '--verify', '--quiet', `refs/tags/${seedTag}`],
|
|
120
|
+
root
|
|
121
|
+
).ok,
|
|
122
|
+
tagRemote:
|
|
123
|
+
exec
|
|
124
|
+
.capture(
|
|
125
|
+
['git', 'ls-remote', '--tags', 'origin', `refs/tags/${seedTag}`],
|
|
126
|
+
root
|
|
127
|
+
)
|
|
128
|
+
.stdout.trim() !== '',
|
|
129
|
+
publishedCommit: view.ok ? view.stdout.trim() || null : null,
|
|
130
|
+
clean:
|
|
131
|
+
exec.capture(['git', 'status', '--porcelain'], root).stdout.trim() === '',
|
|
132
|
+
onMain: head !== '' && head === main,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function plan(state: State): Step[] {
|
|
137
|
+
const needsLogin = state.user === null;
|
|
138
|
+
const publishBlocked = !state.clean
|
|
139
|
+
? 'The working tree has uncommitted changes; the seed is published from a commit.'
|
|
140
|
+
: !state.onMain
|
|
141
|
+
? 'HEAD is not origin/main. Merge the change, then run this on main.'
|
|
142
|
+
: null;
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
id: 'login',
|
|
146
|
+
title: 'Sign in to npm',
|
|
147
|
+
done: !needsLogin,
|
|
148
|
+
blocked: null,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'publish',
|
|
152
|
+
title: `Publish ${SEED_VERSION}, the seed version`,
|
|
153
|
+
done: state.published,
|
|
154
|
+
blocked: state.published ? null : publishBlocked,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'trust',
|
|
158
|
+
title: `Trust ${WORKFLOW_FILE} to publish every later version`,
|
|
159
|
+
done: state.trusted === true,
|
|
160
|
+
blocked: null,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'tag',
|
|
164
|
+
title: `Tag ${seedTag} on the commit that was published`,
|
|
165
|
+
// npm recorded no commit for a seed published without one, so there is
|
|
166
|
+
// nothing to tag: the Release workflow then counts from the newest
|
|
167
|
+
// version on npm (bootstrap/release-version.ts).
|
|
168
|
+
done:
|
|
169
|
+
state.tagRemote || (state.published && state.publishedCommit === null),
|
|
170
|
+
blocked: null,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface Deps {
|
|
176
|
+
exec: Exec;
|
|
177
|
+
/** Publish the seed version from a checkout of HEAD. */
|
|
178
|
+
seed(target: Target): boolean;
|
|
179
|
+
log(line: string): void;
|
|
180
|
+
ask(question: string): Promise<boolean>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface Options {
|
|
184
|
+
yes: boolean;
|
|
185
|
+
skipCheck: boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function show(deps: Deps, state: State, steps: Step[]): void {
|
|
189
|
+
deps.log(
|
|
190
|
+
`npm ${state.npmVersion ?? 'missing'} · ${state.user ? `signed in as ${state.user}` : 'not signed in'}`
|
|
191
|
+
);
|
|
192
|
+
for (const step of steps)
|
|
193
|
+
deps.log(
|
|
194
|
+
`${step.done ? '✓' : step.blocked ? '✗' : '→'} ${step.title}${step.blocked ? ` (${step.blocked})` : ''}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Publish from a throwaway checkout of HEAD so the working tree is never
|
|
199
|
+
* edited and npm records the commit it published.
|
|
200
|
+
*/
|
|
201
|
+
function publishSeed(exec: Exec, target: Target): boolean {
|
|
202
|
+
const dir = mkdtempSync(join(tmpdir(), 'dev-fleet-seed-'));
|
|
203
|
+
const checkout = join(dir, 'checkout');
|
|
204
|
+
try {
|
|
205
|
+
exec.capture(
|
|
206
|
+
['git', 'worktree', 'add', '--detach', checkout, 'HEAD'],
|
|
207
|
+
target.root
|
|
208
|
+
);
|
|
209
|
+
const file = join(checkout, 'package.json');
|
|
210
|
+
const manifest = JSON.parse(readFileSync(file, 'utf8')) as object;
|
|
211
|
+
// As the Release workflow does: the package names the commit it is.
|
|
212
|
+
const gitHead = exec
|
|
213
|
+
.capture(['git', 'rev-parse', 'HEAD'], checkout)
|
|
214
|
+
.stdout.trim();
|
|
215
|
+
writeFileSync(
|
|
216
|
+
file,
|
|
217
|
+
`${JSON.stringify({ ...manifest, version: SEED_VERSION, gitHead }, null, 2)}\n`
|
|
218
|
+
);
|
|
219
|
+
return exec.interactive(['npm', 'publish', '--access', 'public'], checkout);
|
|
220
|
+
} finally {
|
|
221
|
+
exec.capture(
|
|
222
|
+
['git', 'worktree', 'remove', '--force', checkout],
|
|
223
|
+
target.root
|
|
224
|
+
);
|
|
225
|
+
rmSync(dir, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function tagPublished(exec: Exec, target: Target, state: State): boolean {
|
|
230
|
+
const commit =
|
|
231
|
+
state.publishedCommit ??
|
|
232
|
+
exec
|
|
233
|
+
.capture(['npm', 'view', `${target.name}@${SEED_VERSION}`, 'gitHead'])
|
|
234
|
+
.stdout.trim();
|
|
235
|
+
if (!commit) return false;
|
|
236
|
+
if (!state.tagLocal)
|
|
237
|
+
exec.capture(['git', 'tag', seedTag, commit], target.root);
|
|
238
|
+
return exec.interactive(
|
|
239
|
+
['git', 'push', 'origin', `refs/tags/${seedTag}`],
|
|
240
|
+
target.root
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function bootstrap(
|
|
245
|
+
deps: Deps,
|
|
246
|
+
target: Target,
|
|
247
|
+
options: Options
|
|
248
|
+
): Promise<number> {
|
|
249
|
+
const { exec, log } = deps;
|
|
250
|
+
let state = measure(exec, target);
|
|
251
|
+
const steps = plan(state);
|
|
252
|
+
show(deps, state, steps);
|
|
253
|
+
if (!npmIsRecent(state.npmVersion)) {
|
|
254
|
+
log(
|
|
255
|
+
`npm ${MIN_NPM.join('.')} or newer is required (for "npm trust"). Run: npm install -g npm@latest`
|
|
256
|
+
);
|
|
257
|
+
return 1;
|
|
258
|
+
}
|
|
259
|
+
const todo = steps.filter((step) => !step.done);
|
|
260
|
+
if (todo.length === 0) {
|
|
261
|
+
log('Nothing to do: publishing is bootstrapped.');
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
const blocked = todo.find((step) => step.blocked);
|
|
265
|
+
if (blocked) return 1;
|
|
266
|
+
if (
|
|
267
|
+
!options.yes &&
|
|
268
|
+
!(await deps.ask(`Run ${todo.length} step(s) as listed?`))
|
|
269
|
+
)
|
|
270
|
+
return 130;
|
|
271
|
+
if (todo.some((s) => s.id === 'publish') && !options.skipCheck) {
|
|
272
|
+
log('Running bun run check before publishing…');
|
|
273
|
+
if (!exec.interactive(['bun', 'run', 'check'], target.root)) return 1;
|
|
274
|
+
}
|
|
275
|
+
for (const step of todo) {
|
|
276
|
+
log(`→ ${step.title}`);
|
|
277
|
+
if (!(await run(step.id, deps, target, state))) {
|
|
278
|
+
log(`✗ ${step.title} failed; run bun run bootstrap again to continue.`);
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
state = measure(exec, target);
|
|
282
|
+
}
|
|
283
|
+
show(deps, state, plan(state));
|
|
284
|
+
return plan(state).every((step) => step.done) ? 0 : 1;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function run(
|
|
288
|
+
id: StepId,
|
|
289
|
+
deps: Deps,
|
|
290
|
+
target: Target,
|
|
291
|
+
state: State
|
|
292
|
+
): Promise<boolean> {
|
|
293
|
+
const { exec } = deps;
|
|
294
|
+
switch (id) {
|
|
295
|
+
case 'login':
|
|
296
|
+
return exec.interactive(['npm', 'login', '--auth-type=web']);
|
|
297
|
+
case 'publish':
|
|
298
|
+
return deps.seed(target);
|
|
299
|
+
case 'trust':
|
|
300
|
+
return exec.interactive([
|
|
301
|
+
'npm',
|
|
302
|
+
'trust',
|
|
303
|
+
'github',
|
|
304
|
+
target.name,
|
|
305
|
+
'--repo',
|
|
306
|
+
target.repo,
|
|
307
|
+
'--file',
|
|
308
|
+
WORKFLOW_FILE,
|
|
309
|
+
'--allow-publish',
|
|
310
|
+
'--yes',
|
|
311
|
+
]);
|
|
312
|
+
case 'tag':
|
|
313
|
+
return tagPublished(exec, target, state);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const system: Exec = {
|
|
318
|
+
capture(command, cwd) {
|
|
319
|
+
const result = spawnSync(command[0]!, command.slice(1), {
|
|
320
|
+
...(cwd ? { cwd } : {}),
|
|
321
|
+
encoding: 'utf8',
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
ok: result.status === 0,
|
|
325
|
+
stdout: result.stdout ?? '',
|
|
326
|
+
stderr: result.stderr ?? '',
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
interactive(command, cwd) {
|
|
330
|
+
const result = spawnSync(command[0]!, command.slice(1), {
|
|
331
|
+
...(cwd ? { cwd } : {}),
|
|
332
|
+
stdio: 'inherit',
|
|
333
|
+
});
|
|
334
|
+
return result.status === 0;
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if (import.meta.main) {
|
|
339
|
+
const args = process.argv.slice(2);
|
|
340
|
+
const options: Options = {
|
|
341
|
+
yes: args.includes('--yes'),
|
|
342
|
+
skipCheck: args.includes('--skip-check'),
|
|
343
|
+
};
|
|
344
|
+
if (!options.yes && !process.stdin.isTTY) {
|
|
345
|
+
console.error('Not a terminal: pass --yes to run unattended.');
|
|
346
|
+
process.exit(2);
|
|
347
|
+
}
|
|
348
|
+
process.exitCode = await bootstrap(
|
|
349
|
+
{
|
|
350
|
+
exec: system,
|
|
351
|
+
seed: (target) => publishSeed(system, target),
|
|
352
|
+
log: (line) => console.log(line),
|
|
353
|
+
ask: (message) => confirm({ message, default: true }),
|
|
354
|
+
},
|
|
355
|
+
readTarget(resolve(import.meta.dir, '..')),
|
|
356
|
+
options
|
|
357
|
+
);
|
|
358
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prints the version the Release workflow should publish for `HEAD`, or
|
|
3
|
+
* nothing when there is nothing to release (the reason goes to stderr).
|
|
4
|
+
*
|
|
5
|
+
* The newest `vX.Y.Z` tag is the last release. Without one, the newest
|
|
6
|
+
* version on npm is: it was published by hand before the workflow tagged
|
|
7
|
+
* anything, and npm may not have recorded its commit, so every commit up to
|
|
8
|
+
* `HEAD` counts. Commits since the last release pick the bump, Conventional
|
|
9
|
+
* Commits style:
|
|
10
|
+
*
|
|
11
|
+
* `feat!:` / `BREAKING CHANGE:` major (minor while the major is 0)
|
|
12
|
+
* `feat:` minor
|
|
13
|
+
* anything else patch
|
|
14
|
+
*
|
|
15
|
+
* Exits 1 when nothing can be released because publishing was never set up
|
|
16
|
+
* (no tag and nothing on npm), so the Release workflow does not report a
|
|
17
|
+
* skipped release as a success.
|
|
18
|
+
*
|
|
19
|
+
* bun bootstrap/release-version.ts
|
|
20
|
+
*/
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
|
|
25
|
+
export interface Git {
|
|
26
|
+
/** Run git and return stdout, or null when it exits non-zero. */
|
|
27
|
+
(args: string[]): string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type Decision =
|
|
31
|
+
| { release: true; version: string; previous: string }
|
|
32
|
+
| { release: false; reason: string; setup?: true };
|
|
33
|
+
|
|
34
|
+
const TAG = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
35
|
+
const BREAKING_SUBJECT = /^\w+(\([^)]*\))?!:/;
|
|
36
|
+
const BREAKING_FOOTER = /^BREAKING[ -]CHANGE:/m;
|
|
37
|
+
const FEATURE = /^feat(\([^)]*\))?:/;
|
|
38
|
+
|
|
39
|
+
export type Bump = 'major' | 'minor' | 'patch';
|
|
40
|
+
|
|
41
|
+
export function bumpFor(messages: string[]): Bump {
|
|
42
|
+
let bump: Bump = 'patch';
|
|
43
|
+
for (const message of messages) {
|
|
44
|
+
const subject = message.split('\n', 1)[0]!.trim();
|
|
45
|
+
if (BREAKING_SUBJECT.test(subject) || BREAKING_FOOTER.test(message))
|
|
46
|
+
return 'major';
|
|
47
|
+
if (FEATURE.test(subject)) bump = 'minor';
|
|
48
|
+
}
|
|
49
|
+
return bump;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function bumpVersion(tag: string, bump: Bump): string {
|
|
53
|
+
const match = TAG.exec(tag);
|
|
54
|
+
if (!match) throw new Error(`Not a release tag: ${tag}`);
|
|
55
|
+
const [major, minor, patch] = match.slice(1).map(Number) as [
|
|
56
|
+
number,
|
|
57
|
+
number,
|
|
58
|
+
number,
|
|
59
|
+
];
|
|
60
|
+
if (bump === 'major' && major > 0) return `${major + 1}.0.0`;
|
|
61
|
+
if (bump === 'patch') return `${major}.${minor}.${patch + 1}`;
|
|
62
|
+
return `${major}.${minor + 1}.0`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function latestTag(git: Git): string | null {
|
|
66
|
+
const tags = git(['tag', '--list', 'v*', '--sort=-v:refname']) ?? '';
|
|
67
|
+
return (
|
|
68
|
+
tags
|
|
69
|
+
.split('\n')
|
|
70
|
+
.map((tag) => tag.trim())
|
|
71
|
+
.find((tag) => TAG.test(tag)) ?? null
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The newest stable `X.Y.Z` of these npm versions, or null. */
|
|
76
|
+
export function newestPublished(versions: string[]): string | null {
|
|
77
|
+
const stable = versions
|
|
78
|
+
.filter((version) => TAG.test(`v${version}`))
|
|
79
|
+
.map((version) => version.split('.').map(Number) as number[]);
|
|
80
|
+
stable.sort((a, b) => b[0]! - a[0]! || b[1]! - a[1]! || b[2]! - a[2]!);
|
|
81
|
+
return stable[0]?.join('.') ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* `published` is the newest stable version on npm, or null when the package
|
|
86
|
+
* is not there.
|
|
87
|
+
*/
|
|
88
|
+
export function decide(git: Git, published: string | null = null): Decision {
|
|
89
|
+
const tag = latestTag(git);
|
|
90
|
+
if (!tag && !published)
|
|
91
|
+
return {
|
|
92
|
+
release: false,
|
|
93
|
+
setup: true,
|
|
94
|
+
reason:
|
|
95
|
+
'No vX.Y.Z tag and nothing on npm yet: run `bun run bootstrap` once first.',
|
|
96
|
+
};
|
|
97
|
+
const previous = tag ?? `v${published}`;
|
|
98
|
+
if (tag && git(['merge-base', '--is-ancestor', 'HEAD', tag]) !== null)
|
|
99
|
+
return { release: false, reason: `HEAD is already in ${tag}.` };
|
|
100
|
+
const log =
|
|
101
|
+
git(['log', '--format=%B%x00', tag ? `${tag}..HEAD` : 'HEAD']) ?? '';
|
|
102
|
+
const messages = log
|
|
103
|
+
.split('\0')
|
|
104
|
+
.map((message) => message.trim())
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
if (messages.length === 0)
|
|
107
|
+
return { release: false, reason: `No commits since ${previous}.` };
|
|
108
|
+
return {
|
|
109
|
+
release: true,
|
|
110
|
+
version: bumpVersion(previous, bumpFor(messages)),
|
|
111
|
+
previous,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const system: Git = (args) => {
|
|
116
|
+
const result = spawnSync('git', args, { encoding: 'utf8' });
|
|
117
|
+
return result.status === 0 ? (result.stdout ?? '') : null;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** The versions of the package on npm; empty when it is not there. */
|
|
121
|
+
function npmVersions(): string[] {
|
|
122
|
+
const name = (
|
|
123
|
+
JSON.parse(
|
|
124
|
+
readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf8')
|
|
125
|
+
) as { name: string }
|
|
126
|
+
).name;
|
|
127
|
+
const result = spawnSync('npm', ['view', name, 'versions', '--json'], {
|
|
128
|
+
encoding: 'utf8',
|
|
129
|
+
});
|
|
130
|
+
if (result.status !== 0) return [];
|
|
131
|
+
const versions: unknown = JSON.parse(result.stdout || '[]');
|
|
132
|
+
return Array.isArray(versions)
|
|
133
|
+
? versions.filter((v): v is string => typeof v === 'string')
|
|
134
|
+
: typeof versions === 'string'
|
|
135
|
+
? [versions]
|
|
136
|
+
: [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (import.meta.main) {
|
|
140
|
+
const decision = decide(system, newestPublished(npmVersions()));
|
|
141
|
+
const summary = process.env['GITHUB_STEP_SUMMARY'];
|
|
142
|
+
const note = (line: string) => {
|
|
143
|
+
if (summary) appendFileSync(summary, `${line}\n`);
|
|
144
|
+
};
|
|
145
|
+
if (decision.release) {
|
|
146
|
+
console.error(`${decision.previous} -> v${decision.version}`);
|
|
147
|
+
note(`Releasing v${decision.version} (after ${decision.previous}).`);
|
|
148
|
+
console.log(decision.version);
|
|
149
|
+
} else if (decision.setup) {
|
|
150
|
+
console.error(`::error::${decision.reason}`);
|
|
151
|
+
note(`**Nothing was released.** ${decision.reason}`);
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
} else {
|
|
154
|
+
console.error(decision.reason);
|
|
155
|
+
note(`Nothing to release: ${decision.reason}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Nonsecret installer evidence; never contains commands, paths or raw errors. */
|
|
2
|
+
import { lstatSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute } from 'node:path';
|
|
4
|
+
export type SetupPhase =
|
|
5
|
+
| 'managed-config'
|
|
6
|
+
| 'mise'
|
|
7
|
+
| 'aws'
|
|
8
|
+
| 't3'
|
|
9
|
+
| 'global-config'
|
|
10
|
+
| 'shell'
|
|
11
|
+
| 'dependencies';
|
|
12
|
+
export function setupFailureCode(error: unknown): string | undefined {
|
|
13
|
+
const known: Record<string, string> = {
|
|
14
|
+
'Pinned T3 artifact unavailable.': 'T3_DOWNLOAD_UNAVAILABLE',
|
|
15
|
+
'T3 artifact checksum mismatch.': 'T3_CHECKSUM_MISMATCH',
|
|
16
|
+
'T3 cached artifact drift; preserve running service and review maintenance recovery.':
|
|
17
|
+
'T3_CACHE_DRIFT',
|
|
18
|
+
'T3 extraction failed.': 'T3_EXTRACTION_FAILED',
|
|
19
|
+
'Managed T3 files differ from the pinned artifact; maintenance is required.':
|
|
20
|
+
'T3_INSTALLATION_DRIFT',
|
|
21
|
+
'Pinned T3 executable verification failed.': 'T3_EXECUTABLE_FAILED',
|
|
22
|
+
};
|
|
23
|
+
return error instanceof Error && Object.hasOwn(known, error.message)
|
|
24
|
+
? known[error.message]
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
27
|
+
export function setupStatus(path: string | undefined) {
|
|
28
|
+
if (path && !isAbsolute(path))
|
|
29
|
+
throw new Error('Setup status path must be absolute.');
|
|
30
|
+
return (
|
|
31
|
+
phase: SetupPhase,
|
|
32
|
+
status: 'running' | 'failed' | 'passed',
|
|
33
|
+
code?: string
|
|
34
|
+
): void => {
|
|
35
|
+
if (!path) return;
|
|
36
|
+
try {
|
|
37
|
+
if (!lstatSync(path).isFile())
|
|
38
|
+
throw new Error('Unsafe setup status file.');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
41
|
+
}
|
|
42
|
+
writeFileSync(
|
|
43
|
+
path,
|
|
44
|
+
JSON.stringify({ version: 1, phase, status, ...(code ? { code } : {}) }) +
|
|
45
|
+
'\n',
|
|
46
|
+
{
|
|
47
|
+
mode: 0o600,
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { workerTools } from './worker-tools';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/** Used by both OS bootstraps, dev-fleet tools sync and dev-fleet update. No workspace dependencies. */
|
|
5
|
+
import { setupStatus, setupFailureCode, type SetupPhase } from './setup-status';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import {
|
|
8
|
+
assertManagedConfig,
|
|
9
|
+
globalConfigDir,
|
|
10
|
+
publishGlobalConfig,
|
|
11
|
+
workspaceRoot,
|
|
12
|
+
} from './global-config';
|
|
13
|
+
import { configureShellPath } from './shell-path';
|
|
14
|
+
import { installNativeAws } from './native-aws';
|
|
15
|
+
import { installNativeT3 } from './native-t3';
|
|
16
|
+
|
|
17
|
+
function run(args: string[]): void {
|
|
18
|
+
const result = spawnSync(args[0]!, args.slice(1), {
|
|
19
|
+
cwd: workspaceRoot,
|
|
20
|
+
stdio: 'inherit',
|
|
21
|
+
});
|
|
22
|
+
if (result.error) throw result.error;
|
|
23
|
+
if (result.status !== 0)
|
|
24
|
+
throw new Error(`${args.join(' ')} failed (${result.status})`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function syncTools(
|
|
28
|
+
observe: (phase: SetupPhase) => void = () => {},
|
|
29
|
+
worker = false
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
observe('managed-config');
|
|
32
|
+
const dir = globalConfigDir();
|
|
33
|
+
assertManagedConfig(dir);
|
|
34
|
+
observe('mise');
|
|
35
|
+
const config = Bun.TOML.parse(
|
|
36
|
+
readFileSync(join(workspaceRoot, 'mise.toml'), 'utf8')
|
|
37
|
+
) as { tools: Record<string, unknown>; vars: { worker_tools: unknown } };
|
|
38
|
+
const selected = worker
|
|
39
|
+
? Object.keys(workerTools(config.tools, config.vars.worker_tools))
|
|
40
|
+
: [];
|
|
41
|
+
run(['mise', 'install', '--locked', ...selected]);
|
|
42
|
+
observe('aws');
|
|
43
|
+
if (!worker) await installNativeAws();
|
|
44
|
+
observe('t3');
|
|
45
|
+
await installNativeT3();
|
|
46
|
+
observe('global-config');
|
|
47
|
+
publishGlobalConfig(dir);
|
|
48
|
+
run(['mise', 'reshim']);
|
|
49
|
+
observe('shell');
|
|
50
|
+
configureShellPath();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (import.meta.main) {
|
|
54
|
+
const flag = process.argv.indexOf('--status-file');
|
|
55
|
+
const report = setupStatus(flag < 0 ? undefined : process.argv[flag + 1]);
|
|
56
|
+
let phase: SetupPhase = 'managed-config';
|
|
57
|
+
try {
|
|
58
|
+
await syncTools((current) => {
|
|
59
|
+
phase = current;
|
|
60
|
+
report(phase, 'running');
|
|
61
|
+
}, process.argv.includes('--worker'));
|
|
62
|
+
if (!process.argv.includes('--tools-only')) {
|
|
63
|
+
phase = 'dependencies';
|
|
64
|
+
report(phase, 'running');
|
|
65
|
+
run([
|
|
66
|
+
'mise',
|
|
67
|
+
'exec',
|
|
68
|
+
'--locked',
|
|
69
|
+
'--',
|
|
70
|
+
'bun',
|
|
71
|
+
'install',
|
|
72
|
+
'--frozen-lockfile',
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
report(phase, 'passed');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
report(phase, 'failed', setupFailureCode(error));
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Shared toolchain phase ${phase} failed; inspect the declared installer prerequisites and retry.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
console.log(
|
|
83
|
+
'Shared toolchain installed globally. Reopen terminals/editors, or refresh this terminal with: exec "$SHELL" -l (Unix).'
|
|
84
|
+
);
|
|
85
|
+
console.log(
|
|
86
|
+
'Verify: mise exec -- bun bootstrap/doctor.ts (add --docker to require a running Docker engine).'
|
|
87
|
+
);
|
|
88
|
+
}
|