From 3fd9cf87a919a7a1e4cefe83bce45fdd867c7276 Mon Sep 17 00:00:00 2001 From: Razvan Mihailescu Date: Sun, 1 Mar 2026 21:25:21 +0200 Subject: [PATCH 01/20] Refactor deployment workflow and CLI commands --- .github/workflows/deploy.yml | 27 ++++++++------------------- src/cli/commands/deploy.ts | 6 +++++- src/cli/commands/init.ts | 4 ++-- src/cli/commands/setup.ts | 2 +- src/lib/config.ts | 12 ++++++++++++ src/lib/ens/safe-batch-deploy.ts | 22 ++++++++++++++++------ 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ad7f66d..a5810ad 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Secure Deploy to ENS + IPFS +name: Autark Deploy to ENS + IPFS on: push: @@ -32,33 +32,22 @@ jobs: run: npm install -g @storacha/client - name: Setup Storacha credentials + if: ${{ secrets.STORACHA_TOKEN != '' }} run: | mkdir -p ~/.storacha echo "${{ secrets.STORACHA_TOKEN }}" > ~/.storacha/token - if: env.STORACHA_TOKEN != '' - - - name: Install secure-deploy CLI - run: | - cd path/to/secure-deploy - npm install - npm run build - npm link - name: Deploy to ENS + IPFS run: | - secure-deploy deploy dist \ - --network sepolia \ - --ens-domain rome.eth \ - --rpc-url ${{ secrets.SEPOLIA_RPC_URL }} \ - --safe-address ${{ secrets.SAFE_ADDRESS }} \ - --safe-threshold 2 \ - --owner-address ${{ secrets.SEPOLIA_OWNER_ADDRESS }} \ - --owner-pk ${{ secrets.SEPOLIA_OWNER_PK }} + npm run cli -- deploy dist --network sepolia env: SEPOLIA_RPC_URL: ${{ secrets.SEPOLIA_RPC_URL }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} SAFE_ADDRESS: ${{ secrets.SAFE_ADDRESS }} - SEPOLIA_OWNER_ADDRESS: ${{ secrets.SEPOLIA_OWNER_ADDRESS }} + SEPOLIA_ENS_DOMAIN: ${{ secrets.SEPOLIA_ENS_DOMAIN }} + ENS_DOMAIN: ${{ secrets.ENS_DOMAIN }} SEPOLIA_OWNER_PK: ${{ secrets.SEPOLIA_OWNER_PK }} + SAFE_API_KEY: ${{ secrets.SAFE_API_KEY }} - name: Comment on commit with deployment info uses: actions/github-script@v7 @@ -74,5 +63,5 @@ jobs: body: `πŸš€ **Deployment Initiated**\n\n` + `Commit: ${shortSha}\n` + `Safe Transaction created for approval.\n\n` + - `View pending transactions: https://app.safe.global/sepolia.safe/${{ secrets.SAFE_ADDRESS }}/transactions/queue` + `View pending transactions: https://app.safe.global/transactions/queue?safe=sep:${{ secrets.SAFE_ADDRESS }}` }) diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 0000fff..498c9cf 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -296,7 +296,11 @@ export async function deployCommand(options: DeployOptions): Promise { chain.id, batchResult, config.ownerPrivateKey!, - logger + logger, + { + rpcUrl: config.rpcUrl, + safeApiKey: config.safeApiKey, + } ) spinner.succeed('Batch transaction submitted to Safe') diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts index cefb53c..1dc7915 100644 --- a/src/cli/commands/init.ts +++ b/src/cli/commands/init.ts @@ -12,7 +12,7 @@ import { Logger } from '../../lib/logger.js' export async function initCommand(): Promise { const logger = new Logger() - logger.header('πŸ”§ Initialize secure-deploy') + logger.header('πŸ”§ Initialize AUTARK') logger.newline() const configTemplate = `# Secure Deploy Configuration @@ -55,7 +55,7 @@ debug: false logger.log('Next steps:') logger.log(' 1. Edit the config file with your values') logger.log(' 2. Or use environment variables (see .env.example)') - logger.log(' 3. Deploy with: secure-deploy deploy ./dist') + logger.log(' 3. Deploy with: autark deploy ./dist') logger.newline() } catch (error: any) { diff --git a/src/cli/commands/setup.ts b/src/cli/commands/setup.ts index 4218a4c..19347f8 100644 --- a/src/cli/commands/setup.ts +++ b/src/cli/commands/setup.ts @@ -171,7 +171,7 @@ export async function setupCommand(options: SetupOptions): Promise { logger.section('πŸ”§ Managing hooks') logger.log(' View hook: cat .git/hooks/pre-push') logger.log(' Remove hook: rm .git/hooks/pre-push') - logger.log(' Reinstall: npx secure-deploy setup --force') + logger.log(' Reinstall: autark setup --force') logger.newline() } catch (error: any) { diff --git a/src/lib/config.ts b/src/lib/config.ts index 9b4912b..5ff6c28 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -57,10 +57,22 @@ export function loadConfig(cliOptions: Partial = {}): Config { // Load from config file (lowest priority) const explorer = cosmiconfigSync('secure-deploy', { searchPlaces: [ + 'secure-deploy.config.yaml', + 'secure-deploy.config.yml', 'secure-deploy.config.js', 'secure-deploy.config.json', '.secure-deployrc', + '.secure-deployrc.yaml', + '.secure-deployrc.yml', '.secure-deployrc.json', + 'autark.config.yaml', + 'autark.config.yml', + 'autark.config.js', + 'autark.config.json', + '.autarkrc', + '.autarkrc.yaml', + '.autarkrc.yml', + '.autarkrc.json', 'package.json', ], }) diff --git a/src/lib/ens/safe-batch-deploy.ts b/src/lib/ens/safe-batch-deploy.ts index 6d29c97..77dcdf2 100644 --- a/src/lib/ens/safe-batch-deploy.ts +++ b/src/lib/ens/safe-batch-deploy.ts @@ -5,6 +5,7 @@ import { encodeFunctionData, parseAbi, type Address, type Hex } from 'viem' import { normalize, namehash } from 'viem/ens' import { NAME_WRAPPER_ADDRESS } from './namewrapper/wrapper.js' +import { FUSES } from './namewrapper/fuses.js' import { PUBLIC_RESOLVER_ADDRESS } from './ens.js' import { encodeContenthash } from './contenthash-encode.js' import { ENSError } from '../errors.js' @@ -61,7 +62,10 @@ export async function encodeSafeBatchDeploy( const resolverAddress = PUBLIC_RESOLVER_ADDRESS[chainId === 11155111 ? 'sepolia' : 'mainnet'] as Address // Fuses: CANNOT_UNWRAP | CANNOT_SET_RESOLVER | PARENT_CANNOT_CONTROL - const fuses = 0x0001 | 0x0080 | 0x10000 + const fuses = + FUSES.CANNOT_UNWRAP | + FUSES.CANNOT_SET_RESOLVER | + FUSES.PARENT_CANNOT_CONTROL const createSubdomainData = encodeFunctionData({ abi: wrapperAbi, @@ -112,7 +116,11 @@ export async function submitToSafeService( chainId: number, batchResult: SafeBatchResult, signerPrivateKey: `0x${string}`, - logger: Logger + logger: Logger, + options?: { + rpcUrl?: string + safeApiKey?: string + } ): Promise { const spinner = logger.spinner('Submitting batch to Safe Transaction Service...') spinner.start() @@ -124,9 +132,11 @@ export async function submitToSafeService( const { ethers } = await import('ethers') // Setup RPC URL - const rpcUrl = chainId === 11155111 - ? process.env.SEPOLIA_RPC_URL - : process.env.MAINNET_RPC_URL + const rpcUrl = + options?.rpcUrl || + (chainId === 11155111 + ? process.env.SEPOLIA_RPC_URL + : process.env.MAINNET_RPC_URL) if (!rpcUrl) { throw new Error('RPC URL not configured') @@ -137,7 +147,7 @@ export async function submitToSafeService( const signer = new ethers.Wallet(signerPrivateKey, provider) // Create Safe API Kit instance - const safeApiKey = process.env.SAFE_API_KEY + const safeApiKey = options?.safeApiKey || process.env.SAFE_API_KEY if (!safeApiKey) { throw new Error('SAFE_API_KEY not configured. Get one at https://developer.safe.global') } From 40f130ad95812aa2a6bf9d1dcb9a28dbb36b306e Mon Sep 17 00:00:00 2001 From: Razvan Mihailescu Date: Sun, 1 Mar 2026 21:39:15 +0200 Subject: [PATCH 02/20] Update README and add Quickstart and Architecture (Short) documentation --- README.md | 6 +- docs/ARCHITECTURE-SHORT.md | 39 ++++++++++++ docs/QUICKSTART.md | 65 ++++++++++++++++++++ docs/README.md | 5 ++ docs/{ => _legacy}/CI-CD-SETUP.md | 23 ++++--- docs/{ => _legacy}/FLOW-CHART.md | 0 docs/{ => _legacy}/GIT-HOOKS.md | 32 +++++----- docs/{ => _legacy}/TECHNICAL-ARCHITECTURE.md | 2 +- docs/{ => _legacy}/USER-GUIDE.md | 40 ++++++------ 9 files changed, 160 insertions(+), 52 deletions(-) create mode 100644 docs/ARCHITECTURE-SHORT.md create mode 100644 docs/QUICKSTART.md create mode 100644 docs/README.md rename docs/{ => _legacy}/CI-CD-SETUP.md (89%) rename docs/{ => _legacy}/FLOW-CHART.md (100%) rename docs/{ => _legacy}/GIT-HOOKS.md (89%) rename docs/{ => _legacy}/TECHNICAL-ARCHITECTURE.md (99%) rename docs/{ => _legacy}/USER-GUIDE.md (98%) diff --git a/README.md b/README.md index a12d44a..d2f2a50 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Autark is a crypto-anarchic `DevSecOps` framework for more secure, and self-sovereign frontend deployments; embracing immutable, decentralized, and multi-party-verified frontend governance through Safe + ENS + IPFS. -[Demo](https://www.youtube.com/watch?v=-pGsHpUI0J0) | [User Guide](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/USER-GUIDE.md) | [Technical Architecture](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/TECHNICAL-ARCHITECTURE.md) | [Flow Chart](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/FLOW-CHART.md) | [Submission](https://taikai.network/ethrome/hackathons/2025/projects/cmgx4r1we02d112kkxt8y1sxi/idea) | [Safe DAO Proposal](https://forum.safe.global/t/grant-proposal-supporting-autark-a-secure-self-sovereign-frontend-deployment-framework-built-on-safe/6799) +[Demo](https://www.youtube.com/watch?v=-pGsHpUI0J0) | [Quickstart](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/QUICKSTART.md) | [Architecture (Short)](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/ARCHITECTURE-SHORT.md) | [Docs Index](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/README.md) | [Submission](https://taikai.network/ethrome/hackathons/2025/projects/cmgx4r1we02d112kkxt8y1sxi/idea) | [Safe DAO Proposal](https://forum.safe.global/t/grant-proposal-supporting-autark-a-secure-self-sovereign-frontend-deployment-framework-built-on-safe/6799) --- @@ -52,7 +52,7 @@ Autark replaces β€œtrust” with verifiable processes and cryptographic finality > Each release becomes an immutable record, and an auditable artifact of a more secure frontend versioning deployment. -Explore: detailed [Technical Architecture](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/TECHNICAL-ARCHITECTURE.md) and extended [Flow Chart](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/FLOW-CHART.md). +Explore: concise [Architecture (Short)](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/ARCHITECTURE-SHORT.md) and [legacy docs archive](https://github.com/MihRazvan/ETHRome_hackathon/tree/main/docs/_legacy). --- @@ -64,7 +64,7 @@ autark init autark deploy dist ``` -Explore: detailed [User Guide](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/USER-GUIDE.md). +Explore: concise [Quickstart](https://github.com/MihRazvan/ETHRome_hackathon/blob/main/docs/QUICKSTART.md). --- diff --git a/docs/ARCHITECTURE-SHORT.md b/docs/ARCHITECTURE-SHORT.md new file mode 100644 index 0000000..a30f20e --- /dev/null +++ b/docs/ARCHITECTURE-SHORT.md @@ -0,0 +1,39 @@ +# Architecture (Short) + +Autark adds a governance gate to frontend deployments. + +## Goal + +Prevent single-actor frontend takeover by requiring Safe multisig approval before ENS content is updated. + +## Deployment Pipeline + +1. Build frontend output (`dist/`) +2. Upload output to IPFS via Storacha +3. Resolve next versioned ENS subdomain (`vN.parent.eth`) +4. Create Safe proposal: +- Safe-owns-parent: batch `setSubnodeRecord + setContenthash` +- Personal-owns-parent: create subdomain first, then Safe proposal for `setContenthash` +5. Multisig signers review and execute +6. Subdomain is immutable through NameWrapper fuses + +## Security Properties + +- Multi-party approval checkpoint (Safe threshold) +- Content-addressed artifacts (IPFS CID) +- Versioned immutable releases (`v0`, `v1`, ...) +- On-chain audit trail for release operations + +## Key Modules + +- CLI entry: `src/cli/index.ts` +- Main deploy flow: `src/cli/commands/deploy.ts` +- Config merge/validation: `src/lib/config.ts` +- IPFS upload: `src/lib/ipfs/upload.ts` +- ENS version and planning: `src/lib/ens/version.ts`, `src/lib/ens/deploy.ts` +- Safe integration: `src/lib/safe/client.ts`, `src/lib/ens/safe-batch-deploy.ts` + +## Notes + +- The current repo also contains legacy experimental scripts under `src/test`, `src/core`, and `src/providers`. +- Long-form architecture and hackathon docs are archived in `docs/_legacy/`. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md new file mode 100644 index 0000000..efdae58 --- /dev/null +++ b/docs/QUICKSTART.md @@ -0,0 +1,65 @@ +# Quickstart + +## Prerequisites + +- Node.js `>= 20.10.0` +- Storacha CLI installed and authenticated +- Wrapped ENS parent domain on the target network +- Safe multisig configured + +## Install + +```bash +npm install +npm run build +``` + +## Configure + +Create `.env` in the project root: + +```bash +DEPLOY_NETWORK=sepolia +SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com +SEPOLIA_ENS_DOMAIN=your-domain.eth +SAFE_ADDRESS=0x... +SAFE_API_KEY=sk_... +SEPOLIA_OWNER_PK=0x... +``` + +Optionally create `secure-deploy.config.yaml` via: + +```bash +npm run cli -- init +``` + +## Deploy + +```bash +npm run cli -- deploy dist +``` + +Common flags: + +```bash +npm run cli -- deploy dist --network sepolia --dry-run +``` + +## Check Status + +```bash +npm run cli -- status +npm run cli -- status --subdomain v0.your-domain.eth +``` + +## Auto Deploy Hook (Optional) + +```bash +npm run cli -- setup +``` + +This installs `.git/hooks/pre-push` to trigger deploy proposals on a selected branch. + +## Legacy docs + +Older long-form hackathon docs are archived in `docs/_legacy/`. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..42cade8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +# Docs + +- [Quickstart](./QUICKSTART.md) +- [Architecture (Short)](./ARCHITECTURE-SHORT.md) +- [Legacy docs archive](./_legacy/) diff --git a/docs/CI-CD-SETUP.md b/docs/_legacy/CI-CD-SETUP.md similarity index 89% rename from docs/CI-CD-SETUP.md rename to docs/_legacy/CI-CD-SETUP.md index b2e6cc1..91cf6b7 100644 --- a/docs/CI-CD-SETUP.md +++ b/docs/_legacy/CI-CD-SETUP.md @@ -28,8 +28,9 @@ Go to your repository Settings β†’ Secrets and variables β†’ Actions, and add th |-------------|-------------|---------| | `SEPOLIA_RPC_URL` | Alchemy/Infura RPC endpoint | `https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY` | | `SAFE_ADDRESS` | Your Safe multisig address | `0xA5ED8dd265c8e9154FaBf8E66Cb3aF16002261A3` | -| `SEPOLIA_OWNER_ADDRESS` | Safe signer address | `0xE53eC90471B604f24b5Ab66A61F18e30579D2b1F` | +| `SEPOLIA_ENS_DOMAIN` | ENS parent domain on Sepolia | `myproject.eth` | | `SEPOLIA_OWNER_PK` | Safe signer private key | `0xfd4e02...` | +| `SAFE_API_KEY` | Safe Transaction Service API key | `sk_live_abc123...` | | `STORACHA_TOKEN` | (Optional) Storacha auth token | Get from `storacha token` | **Security Note:** The `SEPOLIA_OWNER_PK` is only used to *propose* transactions to Safe. It cannot execute transactions alone (requires threshold approvals). @@ -40,7 +41,7 @@ Copy the workflow file to your project: ```bash mkdir -p .github/workflows -cp path/to/secure-deploy/.github/workflows/deploy.yml .github/workflows/ +cp path/to/autark/.github/workflows/deploy.yml .github/workflows/ ``` ### 3. Customize Workflow @@ -55,9 +56,7 @@ Edit `.github/workflows/deploy.yml` to match your project: # Update the build output directory - name: Deploy to ENS + IPFS run: | - secure-deploy deploy dist \ # Change 'dist' to your build directory - --network sepolia \ - --ens-domain rome.eth \ # Change to your domain + npm run cli -- deploy dist --network sepolia # Change 'dist' to your build directory ``` ### 4. Deployment Modes @@ -180,12 +179,12 @@ jobs: deploy-staging: if: github.ref == 'refs/heads/develop' steps: - - run: secure-deploy deploy dist --ens-domain staging.rome.eth + - run: autark deploy dist --ens-domain staging.rome.eth deploy-production: if: github.ref == 'refs/heads/main' steps: - - run: secure-deploy deploy dist --ens-domain rome.eth + - run: autark deploy dist --ens-domain rome.eth ``` ### Add Slack/Discord Notifications @@ -207,7 +206,7 @@ jobs: ### View Deployment Status - **GitHub Actions**: Repository β†’ Actions tab -- **Safe Proposals**: https://app.safe.global/sepolia.safe/YOUR_SAFE_ADDRESS/transactions/queue +- **Safe Proposals**: https://app.safe.global/transactions/queue?safe=sep:YOUR_SAFE_ADDRESS - **ENS Status**: `npm run status` ### Debugging Failed Deployments @@ -230,21 +229,21 @@ jobs: ## Example: Complete Setup ```bash -# 1. Setup secure-deploy in your project +# 1. Setup autark in your project cd my-frontend-app -npm install --save-dev @your-org/secure-deploy +npm install -g autark # 2. Add deploy script to package.json { "scripts": { - "deploy": "secure-deploy deploy dist" + "deploy": "autark deploy dist" } } # 3. Copy workflow file mkdir -p .github/workflows curl -o .github/workflows/deploy.yml \ - https://raw.githubusercontent.com/your-org/secure-deploy/main/.github/workflows/deploy.yml + https://raw.githubusercontent.com/MihRazvan/ETHRome_hackathon/main/.github/workflows/deploy.yml # 4. Configure secrets in GitHub UI # (See step 1 above) diff --git a/docs/FLOW-CHART.md b/docs/_legacy/FLOW-CHART.md similarity index 100% rename from docs/FLOW-CHART.md rename to docs/_legacy/FLOW-CHART.md diff --git a/docs/GIT-HOOKS.md b/docs/_legacy/GIT-HOOKS.md similarity index 89% rename from docs/GIT-HOOKS.md rename to docs/_legacy/GIT-HOOKS.md index dafcaa0..562564c 100644 --- a/docs/GIT-HOOKS.md +++ b/docs/_legacy/GIT-HOOKS.md @@ -1,12 +1,12 @@ # Git Hooks - Automatic Deployment -Enable automatic deployments triggered by `git push` using secure-deploy's built-in git hooks. +Enable automatic deployments triggered by `git push` using autark's built-in git hooks. ## Quick Start ```bash # In your project directory -npx secure-deploy setup +autark setup # Follow the prompts: # - Which branch? [staging] @@ -19,7 +19,7 @@ git push origin staging # Auto-deploys! πŸš€ ## How It Works -When you run `secure-deploy setup`, it installs a **pre-push git hook** that: +When you run `autark setup`, it installs a **pre-push git hook** that: 1. βœ… Detects when you push to the deployment branch (e.g., `staging`) 2. πŸ—οΈ Runs your build if needed @@ -35,7 +35,7 @@ When you run `secure-deploy setup`, it installs a **pre-push git hook** that: ### Interactive Setup (Recommended) ```bash -npx secure-deploy setup +autark setup ``` Prompts you for: @@ -46,10 +46,10 @@ Prompts you for: ```bash # Specify branch via flag -npx secure-deploy setup --branch main +autark setup --branch main # Force overwrite existing hook -npx secure-deploy setup --force +autark setup --force ``` ## Example Workflows @@ -78,7 +78,7 @@ git push origin main # πŸš€ Production deployment! ```bash $ git push origin staging -πŸš€ secure-deploy: Auto-deploying staging branch... +πŸš€ autark: Auto-deploying staging branch... πŸ“¦ Step 3: Upload to IPFS βœ” Uploaded: bafybei... @@ -130,7 +130,7 @@ cat .git/hooks/pre-push nano .git/hooks/pre-push # Or regenerate it -npx secure-deploy setup --force +autark setup --force ``` ## Managing Hooks @@ -166,7 +166,7 @@ rm .git/hooks/pre-push ### Reinstall ```bash -npx secure-deploy setup --force +autark setup --force ``` ## Multiple Branches @@ -189,9 +189,9 @@ Example multi-branch hook: branch=$(git symbolic-ref --short HEAD 2>/dev/null) if [[ "$branch" == "staging" ]]; then - npx secure-deploy deploy dist --ens-domain staging.yourproject.eth + autark deploy dist --ens-domain staging.yourproject.eth elif [[ "$branch" == "main" ]]; then - npx secure-deploy deploy dist --ens-domain yourproject.eth + autark deploy dist --ens-domain yourproject.eth fi ``` @@ -254,14 +254,14 @@ Git hooks are **not** committed to the repository by default. To share with your ```markdown ## Setup 1. npm install -2. npx secure-deploy setup +2. autark setup ``` **Option 2: Add to package.json postinstall** ```json { "scripts": { - "postinstall": "npx secure-deploy setup --branch staging --force" + "postinstall": "autark setup --branch staging --force" } } ``` @@ -287,7 +287,7 @@ Git hooks are **not** committed to the repository by default. To share with your ## Advanced: Post-Commit vs Pre-Push -Currently, secure-deploy uses **pre-push** hooks. Here's why: +Currently, autark uses **pre-push** hooks. Here's why: **Pre-Push (Current)** - βœ… Runs before code reaches remote @@ -307,7 +307,7 @@ cat > .git/hooks/post-commit << 'EOF' #!/bin/bash branch=$(git symbolic-ref --short HEAD) if [[ "$branch" == "staging" ]]; then - npx secure-deploy deploy dist + autark deploy dist fi EOF chmod +x .git/hooks/post-commit @@ -330,7 +330,7 @@ chmod +x .git/hooks/post-commit ```bash # One-time setup -npx secure-deploy setup +autark setup # Then deploy anytime with: git push origin staging diff --git a/docs/TECHNICAL-ARCHITECTURE.md b/docs/_legacy/TECHNICAL-ARCHITECTURE.md similarity index 99% rename from docs/TECHNICAL-ARCHITECTURE.md rename to docs/_legacy/TECHNICAL-ARCHITECTURE.md index 0548d86..fbdf824 100644 --- a/docs/TECHNICAL-ARCHITECTURE.md +++ b/docs/_legacy/TECHNICAL-ARCHITECTURE.md @@ -289,7 +289,7 @@ Users who want latest can use myapp.eth β†’ points to latest **Could have required user to specify:** ```bash -autark deploy dist --mode safe-owns-parent +autark deploy dist ``` **Instead, we auto-detect:** diff --git a/docs/USER-GUIDE.md b/docs/_legacy/USER-GUIDE.md similarity index 98% rename from docs/USER-GUIDE.md rename to docs/_legacy/USER-GUIDE.md index 535102d..d54768a 100644 --- a/docs/USER-GUIDE.md +++ b/docs/_legacy/USER-GUIDE.md @@ -429,14 +429,14 @@ https://ethereum-sepolia-rpc.publicnode.com **Result:** -Creates `.autarkrc.json`: -```json -{ - "ensDomain": "myproject.eth", - "safeAddress": "0xA5ED8dd265c8e9154FaBf8E66Cb3aF16002261A3", - "network": "sepolia", - "rpcUrl": "https://sepolia.infura.io/v3/abc123..." -} +Creates `secure-deploy.config.yaml`: +```yaml +network: sepolia +rpcUrl: https://sepolia.infura.io/v3/abc123... +ensDomain: myproject.eth +safeAddress: 0xA5ED8dd265c8e9154FaBf8E66Cb3aF16002261A3 +safeApiKey: sk_live_abc123def456... +ownerPrivateKey: 0xabc123def456... ``` ### Step 3: Create .env File @@ -456,10 +456,10 @@ SAFE_ADDRESS=0xA5ED8dd265c8e9154FaBf8E66Cb3aF16002261A3 SAFE_API_KEY=sk_live_abc123def456... # ENS domain -ENS_DOMAIN=myproject.eth +SEPOLIA_ENS_DOMAIN=myproject.eth # Private key (one of the Safe signers) -OWNER_PRIVATE_KEY=0xabc123def456... +SEPOLIA_OWNER_PK=0xabc123def456... ``` **Getting your private key:** @@ -479,7 +479,7 @@ OWNER_PRIVATE_KEY=0xabc123def456... ```bash echo ".env" >> .gitignore -echo ".autarkrc.json" >> .gitignore +echo "secure-deploy.config.yaml" >> .gitignore ``` **Verify:** @@ -1168,8 +1168,8 @@ jobs: env: SEPOLIA_RPC_URL: ${{ secrets.SEPOLIA_RPC_URL }} SAFE_ADDRESS: ${{ secrets.SAFE_ADDRESS }} - ENS_DOMAIN: ${{ secrets.ENS_DOMAIN }} - OWNER_PRIVATE_KEY: ${{ secrets.OWNER_PRIVATE_KEY }} + SEPOLIA_ENS_DOMAIN: ${{ secrets.SEPOLIA_ENS_DOMAIN }} + SEPOLIA_OWNER_PK: ${{ secrets.SEPOLIA_OWNER_PK }} SAFE_API_KEY: ${{ secrets.SAFE_API_KEY }} ``` @@ -1205,8 +1205,8 @@ cat delegation.b64 |------|-------|---------| | `SEPOLIA_RPC_URL` | Your RPC endpoint | `https://sepolia.infura.io/v3/abc123...` | | `SAFE_ADDRESS` | Your Safe address | `0xA5ED8dd265c8e9154FaBf8E66Cb3aF16002261A3` | -| `ENS_DOMAIN` | Your ENS domain | `myproject.eth` | -| `OWNER_PRIVATE_KEY` | Private key of Safe signer | `0xabc123...` | +| `SEPOLIA_ENS_DOMAIN` | Your ENS domain | `myproject.eth` | +| `SEPOLIA_OWNER_PK` | Private key of Safe signer | `0xabc123...` | | `SAFE_API_KEY` | Safe API key | `sk_live_abc123...` | | `STORACHA_DELEGATION` | Base64 encoded delegation | (paste contents of delegation.b64) | @@ -1522,7 +1522,7 @@ cat .env | grep SAFE_ADDRESS **2. Wrong network** ```bash # Verify network -cat .autarkrc.json +cat secure-deploy.config.yaml # Should match network in Safe UI (top right) ``` @@ -1567,12 +1567,12 @@ Error: Invalid private key **Solution:** ```bash # Ensure private key has 0x prefix -OWNER_PRIVATE_KEY=0xabc123... # βœ“ Correct -OWNER_PRIVATE_KEY=abc123... # βœ— Wrong +SEPOLIA_OWNER_PK=0xabc123... # βœ“ Correct +SEPOLIA_OWNER_PK=abc123... # βœ— Wrong # Ensure no quotes in .env -OWNER_PRIVATE_KEY=0xabc123... # βœ“ Correct -OWNER_PRIVATE_KEY="0xabc..." # βœ— Wrong (in .env files) +SEPOLIA_OWNER_PK=0xabc123... # βœ“ Correct +SEPOLIA_OWNER_PK="0xabc..." # βœ— Wrong (in .env files) ``` ### Issue: "Git hook not triggering" From 72dc6b8e3b556854d65304f93dab1822c1e323ce Mon Sep 17 00:00:00 2001 From: Razvan Mihailescu Date: Sun, 1 Mar 2026 21:49:38 +0200 Subject: [PATCH 03/20] Add autark configuration file and update CLI commands --- ...e-deploy.config.yaml => autark.config.yaml | 2 +- docs/QUICKSTART.md | 2 +- package.json | 3 +- src/cli/commands/init.ts | 6 +- src/cli/index.ts | 129 ++++++++------ src/lib/config.ts | 19 +- src/test/unit/config-cli-smoke.test.ts | 163 ++++++++++++++++++ 7 files changed, 253 insertions(+), 71 deletions(-) rename secure-deploy.config.yaml => autark.config.yaml (95%) create mode 100644 src/test/unit/config-cli-smoke.test.ts diff --git a/secure-deploy.config.yaml b/autark.config.yaml similarity index 95% rename from secure-deploy.config.yaml rename to autark.config.yaml index 3aac464..b5a3e4a 100644 --- a/secure-deploy.config.yaml +++ b/autark.config.yaml @@ -1,4 +1,4 @@ -# Secure Deploy Configuration +# AUTARK Configuration # You can also use environment variables or CLI flags # Network Configuration diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index efdae58..c9554c8 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -27,7 +27,7 @@ SAFE_API_KEY=sk_... SEPOLIA_OWNER_PK=0x... ``` -Optionally create `secure-deploy.config.yaml` via: +Optionally create `autark.config.yaml` via: ```bash npm run cli -- init diff --git a/package.json b/package.json index 7944a16..949fb79 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "build": "rm -rf dist && tsc && chmod +x ./dist/cli/index.js", "dev": "tsx src/cli/index.ts", "cli": "tsx src/cli/index.ts", - "test": "node --test", + "test": "tsx --test src/test/unit/**/*.test.ts", + "test:smoke": "tsx --test src/test/unit/**/*.test.ts", "test:ipfs": "tsx src/test/test-ipfs-simple.ts", "test:ens": "tsx src/test/test-ens.ts", "test:complete": "tsx src/test/test-complete-versioning.ts", diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts index 1dc7915..1f01f89 100644 --- a/src/cli/commands/init.ts +++ b/src/cli/commands/init.ts @@ -15,7 +15,7 @@ export async function initCommand(): Promise { logger.header('πŸ”§ Initialize AUTARK') logger.newline() - const configTemplate = `# Secure Deploy Configuration + const configTemplate = `# AUTARK Configuration # You can also use environment variables or CLI flags # Network Configuration @@ -46,10 +46,10 @@ debug: false ` try { - const configPath = resolve(process.cwd(), 'secure-deploy.config.yaml') + const configPath = resolve(process.cwd(), 'autark.config.yaml') writeFileSync(configPath, configTemplate) - logger.success('Created config file: secure-deploy.config.yaml') + logger.success('Created config file: autark.config.yaml') logger.newline() logger.log('Next steps:') diff --git a/src/cli/index.ts b/src/cli/index.ts index 4eac1fd..9e53f65 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -23,65 +23,82 @@ const packageJson = JSON.parse( readFileSync(resolve(__dirname, '../../package.json'), 'utf-8') ) -const program = new Command() +export interface CliHandlers { + deploy: typeof deployCommand + status: typeof statusCommand + init: typeof initCommand + setup: typeof setupCommand +} -program - .name('autark') - .description('Deploy frontends with Safe multisig + immutable ENS versioning') - .version(packageJson.version) +export function createProgram(handlers: CliHandlers = { + deploy: deployCommand, + status: statusCommand, + init: initCommand, + setup: setupCommand, +}): Command { + const program = new Command() -// Deploy command -program - .command('deploy') - .description('Deploy a directory to IPFS and ENS via Safe') - .argument('', 'Directory to deploy') - .option('--ens-domain ', 'ENS parent domain') - .option('--safe-address
', 'Safe multisig address') - .option('--owner-private-key ', 'Owner private key for Safe signing') - .option('--rpc-url ', 'RPC URL') - .option('--safe-api-key ', 'Safe API key') - .option('--network ', 'Network (mainnet, sepolia, goerli)', 'sepolia') - .option('--skip-git-check', 'Skip git working directory check') - .option('--dry-run', 'Preview deployment without executing') - .option('--quiet', 'Minimal output') - .option('--debug', 'Debug output') - .action(async (directory, options) => { - await deployCommand({ directory, ...options }) - }) + program + .name('autark') + .description('Deploy frontends with Safe multisig + immutable ENS versioning') + .version(packageJson.version) -// Status command -program - .command('status') - .description('Check deployment status') - .option('--subdomain ', 'Check specific subdomain') - .option('--ens-domain ', 'ENS parent domain') - .option('--rpc-url ', 'RPC URL') - .option('--network ', 'Network (mainnet, sepolia, goerli)', 'sepolia') - .option('--quiet', 'Minimal output') - .option('--debug', 'Debug output') - .action(async (options) => { - await statusCommand(options) - }) + // Deploy command + program + .command('deploy') + .description('Deploy a directory to IPFS and ENS via Safe') + .argument('', 'Directory to deploy') + .option('--ens-domain ', 'ENS parent domain') + .option('--safe-address
', 'Safe multisig address') + .option('--owner-private-key ', 'Owner private key for Safe signing') + .option('--rpc-url ', 'RPC URL') + .option('--safe-api-key ', 'Safe API key') + .option('--network ', 'Network (mainnet, sepolia, goerli)', 'sepolia') + .option('--skip-git-check', 'Skip git working directory check') + .option('--dry-run', 'Preview deployment without executing') + .option('--quiet', 'Minimal output') + .option('--debug', 'Debug output') + .action(async (directory, options) => { + await handlers.deploy({ directory, ...options }) + }) -// Init command -program - .command('init') - .description('Initialize configuration file') - .action(async () => { - await initCommand() - }) + // Status command + program + .command('status') + .description('Check deployment status') + .option('--subdomain ', 'Check specific subdomain') + .option('--ens-domain ', 'ENS parent domain') + .option('--rpc-url ', 'RPC URL') + .option('--network ', 'Network (mainnet, sepolia, goerli)', 'sepolia') + .option('--quiet', 'Minimal output') + .option('--debug', 'Debug output') + .action(async (options) => { + await handlers.status(options) + }) -// Setup command -program - .command('setup') - .description('Setup git hooks for automatic deployment') - .option('--branch ', 'Branch to trigger deployments (default: staging)') - .option('--force', 'Overwrite existing hooks') - .option('--quiet', 'Minimal output') - .option('--debug', 'Debug output') - .action(async (options) => { - await setupCommand(options) - }) + // Init command + program + .command('init') + .description('Initialize configuration file') + .action(async () => { + await handlers.init() + }) -// Parse CLI arguments -program.parse() + // Setup command + program + .command('setup') + .description('Setup git hooks for automatic deployment') + .option('--branch ', 'Branch to trigger deployments (default: staging)') + .option('--force', 'Overwrite existing hooks') + .option('--quiet', 'Minimal output') + .option('--debug', 'Debug output') + .action(async (options) => { + await handlers.setup(options) + }) + + return program +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + createProgram().parse() +} diff --git a/src/lib/config.ts b/src/lib/config.ts index 5ff6c28..79d93aa 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -55,16 +55,8 @@ export interface DeployConfig extends Config { */ export function loadConfig(cliOptions: Partial = {}): Config { // Load from config file (lowest priority) - const explorer = cosmiconfigSync('secure-deploy', { + const explorer = cosmiconfigSync('autark', { searchPlaces: [ - 'secure-deploy.config.yaml', - 'secure-deploy.config.yml', - 'secure-deploy.config.js', - 'secure-deploy.config.json', - '.secure-deployrc', - '.secure-deployrc.yaml', - '.secure-deployrc.yml', - '.secure-deployrc.json', 'autark.config.yaml', 'autark.config.yml', 'autark.config.js', @@ -73,6 +65,15 @@ export function loadConfig(cliOptions: Partial = {}): Config { '.autarkrc.yaml', '.autarkrc.yml', '.autarkrc.json', + // Legacy names (backward compatibility) + 'secure-deploy.config.yaml', + 'secure-deploy.config.yml', + 'secure-deploy.config.js', + 'secure-deploy.config.json', + '.secure-deployrc', + '.secure-deployrc.yaml', + '.secure-deployrc.yml', + '.secure-deployrc.json', 'package.json', ], }) diff --git a/src/test/unit/config-cli-smoke.test.ts b/src/test/unit/config-cli-smoke.test.ts new file mode 100644 index 0000000..4efdbed --- /dev/null +++ b/src/test/unit/config-cli-smoke.test.ts @@ -0,0 +1,163 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +import { loadConfig } from '../../lib/config.js' +import { createProgram } from '../../cli/index.js' + +const CONFIG_ENV_KEYS = [ + 'DEPLOY_NETWORK', + 'SEPOLIA_RPC_URL', + 'MAINNET_RPC_URL', + 'SEPOLIA_ENS_DOMAIN', + 'ENS_DOMAIN', + 'SAFE_ADDRESS', + 'SAFE_API_KEY', + 'SEPOLIA_OWNER_ADDRESS', + 'SEPOLIA_OWNER_PK', + 'KEY', + 'PROOF', + 'GITHUB_TOKEN', + 'GITHUB_REPO', +] as const + +type EnvSnapshot = Record + +function snapshotEnv(): EnvSnapshot { + const snapshot: EnvSnapshot = {} + for (const key of CONFIG_ENV_KEYS) snapshot[key] = process.env[key] + return snapshot +} + +function restoreEnv(snapshot: EnvSnapshot): void { + for (const key of CONFIG_ENV_KEYS) { + const value = snapshot[key] + if (value === undefined) { + delete process.env[key] + } else { + process.env[key] = value + } + } +} + +function clearConfigEnv(): void { + for (const key of CONFIG_ENV_KEYS) { + delete process.env[key] + } +} + +test('smoke: loadConfig reads canonical autark.config.yaml', () => { + const env = snapshotEnv() + const cwd = process.cwd() + const tempDir = mkdtempSync(join(tmpdir(), 'autark-config-smoke-')) + + try { + clearConfigEnv() + + writeFileSync( + join(tempDir, 'autark.config.yaml'), + [ + 'network: sepolia', + 'rpcUrl: https://rpc.example', + 'ensDomain: smoke.eth', + 'safeAddress: \"0x1111111111111111111111111111111111111111\"', + 'ownerPrivateKey: \"0x2222222222222222222222222222222222222222222222222222222222222222\"', + 'safeApiKey: smoke-key', + ].join('\n') + ) + + process.chdir(tempDir) + const config = loadConfig({}) + + assert.equal(config.ensDomain, 'smoke.eth') + assert.equal(config.rpcUrl, 'https://rpc.example') + assert.equal(config.network, 'sepolia') + assert.equal(config.safeApiKey, 'smoke-key') + } finally { + process.chdir(cwd) + restoreEnv(env) + rmSync(tempDir, { recursive: true, force: true }) + } +}) + +test('smoke: config priority is CLI > env > file', () => { + const env = snapshotEnv() + const cwd = process.cwd() + const tempDir = mkdtempSync(join(tmpdir(), 'autark-priority-smoke-')) + + try { + clearConfigEnv() + + writeFileSync( + join(tempDir, 'autark.config.yaml'), + [ + 'network: sepolia', + 'rpcUrl: https://file.example', + 'ensDomain: file.eth', + ].join('\n') + ) + + process.env.SEPOLIA_ENS_DOMAIN = 'env.eth' + process.chdir(tempDir) + + const config = loadConfig({ ensDomain: 'cli.eth' }) + assert.equal(config.ensDomain, 'cli.eth') + assert.equal(config.rpcUrl, 'https://file.example') + } finally { + process.chdir(cwd) + restoreEnv(env) + rmSync(tempDir, { recursive: true, force: true }) + } +}) + +test('smoke: CLI deploy flags map to expected option keys', async () => { + let capturedDeployOptions: Record | undefined + + const program = createProgram({ + deploy: async (opts) => { + capturedDeployOptions = opts as unknown as Record + }, + status: async () => {}, + init: async () => {}, + setup: async () => {}, + }) + + await program.parseAsync( + [ + 'node', + 'autark', + 'deploy', + 'dist', + '--ens-domain', + 'mapped.eth', + '--safe-address', + '0x3333333333333333333333333333333333333333', + '--owner-private-key', + '0x4444444444444444444444444444444444444444444444444444444444444444', + '--rpc-url', + 'https://rpc.mapping.example', + '--safe-api-key', + 'mapping-key', + '--network', + 'sepolia', + '--dry-run', + '--skip-git-check', + '--quiet', + ], + { from: 'node' } + ) + + assert.ok(capturedDeployOptions) + assert.equal(capturedDeployOptions?.directory, 'dist') + assert.equal(capturedDeployOptions?.ensDomain, 'mapped.eth') + assert.equal(capturedDeployOptions?.safeAddress, '0x3333333333333333333333333333333333333333') + assert.equal(capturedDeployOptions?.ownerPrivateKey, '0x4444444444444444444444444444444444444444444444444444444444444444') + assert.equal(capturedDeployOptions?.rpcUrl, 'https://rpc.mapping.example') + assert.equal(capturedDeployOptions?.safeApiKey, 'mapping-key') + assert.equal(capturedDeployOptions?.network, 'sepolia') + assert.equal(capturedDeployOptions?.dryRun, true) + assert.equal(capturedDeployOptions?.skipGitCheck, true) + assert.equal(capturedDeployOptions?.quiet, true) +}) From c220104ebc6b2aa613cdcab7cc67269920c3efed Mon Sep 17 00:00:00 2001 From: Razvan Mihailescu Date: Sat, 7 Mar 2026 17:03:24 +0200 Subject: [PATCH 04/20] Update index.html and script.js for PL Genesis Hackathon branding --- src/test/fixtures/example-site/index.html | 12 ++++++------ src/test/fixtures/example-site/script.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/fixtures/example-site/index.html b/src/test/fixtures/example-site/index.html index 7c90b92..bb92e15 100644 --- a/src/test/fixtures/example-site/index.html +++ b/src/test/fixtures/example-site/index.html @@ -3,7 +3,7 @@ - Autark - ETHRome Hackathon + Autark - PL Genesis Hackathon