feat(npm): added logic to disable auth, middleware validation, & db syncing for npm package

This commit is contained in:
Waleed Latif
2025-03-04 17:55:55 -08:00
parent 2eb02a3369
commit 301b156139
18 changed files with 739 additions and 254 deletions

6
.gitignore vendored
View File

@@ -32,6 +32,12 @@ yarn-error.log*
# env files # env files
.env .env
.env.*
*.env
.env.local
.env.development
.env.test
.env.production
# vercel # vercel
.vercel .vercel

88
RELEASE.md Normal file
View File

@@ -0,0 +1,88 @@
# Releasing Sim Studio to npm
This guide outlines the steps to release Sim Studio CLI to npm and create a GitHub release with the standalone app.
## Prerequisites
- Node.js 16 or higher
- npm account with access to the `simstudio` package
- GitHub access to the `simstudioai/sim` repository
## Release Process
### 1. Prepare the Release
1. Ensure all changes are committed and pushed to the main branch
2. Update the version number in `packages/@sim/cli/package.json`
3. Update the `STANDALONE_VERSION` in `packages/@sim/cli/src/commands/start.ts` to match
### 2. Run the Release Script
The release script automates most of the process:
```bash
node scripts/release-npm.js
```
This script will:
- Clean up any existing standalone files
- Build the standalone app
- Prepare the CLI package
- Create the standalone directory
- Provide instructions for publishing to npm and creating a GitHub release
### 3. Publish to npm
After the script completes, follow the instructions to publish to npm:
```bash
cd packages/@sim/cli
npm publish
```
### 4. Create GitHub Release
1. Go to https://github.com/simstudioai/sim/releases/new
2. Set the tag to match your version (e.g., `v0.1.0`)
3. Set the title to "Sim Studio v0.1.0" (replace with your version)
4. Upload the `sim-standalone.tar.gz` file from the project root
5. Add release notes describing the changes
6. Publish the release
## Testing the Release
To test the released package:
```bash
# Install globally
npm install -g simstudio
# Run the CLI
simstudio start
# Or run with npx
npx simstudio start
```
## Troubleshooting
### npm publish fails
- Ensure you're logged in to npm: `npm login`
- Check that the package name is available: `npm view simstudio`
- Verify the version number is higher than the previously published version
### GitHub release fails
- Ensure you have the correct permissions to create releases
- Check that the tag doesn't already exist
- Verify the tarball was created correctly
## Maintenance
After a successful release:
1. Increment the version number in `packages/@sim/cli/package.json` for the next release
2. Update the `STANDALONE_VERSION` in `packages/@sim/cli/src/commands/start.ts`
3. Commit these changes with a message like "Bump version to X.Y.Z"

View File

@@ -22,6 +22,51 @@ export function useWorkflowExecution() {
const [executionResult, setExecutionResult] = useState<ExecutionResult | null>(null) const [executionResult, setExecutionResult] = useState<ExecutionResult | null>(null)
const persistLogs = async (logs: any[], executionId: string) => { const persistLogs = async (logs: any[], executionId: string) => {
// Check if we're in local storage mode
const useLocalStorage =
typeof window !== 'undefined' &&
(window.localStorage.getItem('USE_LOCAL_STORAGE') === 'true' ||
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE === 'true')
if (useLocalStorage) {
// Store logs in localStorage
try {
const storageKey = `workflow-logs-${activeWorkflowId}-${executionId}`
window.localStorage.setItem(
storageKey,
JSON.stringify({
logs,
timestamp: new Date().toISOString(),
workflowId: activeWorkflowId,
})
)
// Also update a list of all execution logs for this workflow
const logListKey = `workflow-logs-list-${activeWorkflowId}`
const existingLogList = window.localStorage.getItem(logListKey)
const logList = existingLogList ? JSON.parse(existingLogList) : []
logList.push({
executionId,
timestamp: new Date().toISOString(),
})
// Keep only the last 20 executions
if (logList.length > 20) {
const removedLogs = logList.splice(0, logList.length - 20)
// Clean up old logs
removedLogs.forEach((log: any) => {
window.localStorage.removeItem(`workflow-logs-${activeWorkflowId}-${log.executionId}`)
})
}
window.localStorage.setItem(logListKey, JSON.stringify(logList))
} catch (error) {
console.error('Error storing logs in localStorage:', error)
}
return
}
// Fall back to API if not in local storage mode
try { try {
const response = await fetch(`/api/workflow/${activeWorkflowId}/log`, { const response = await fetch(`/api/workflow/${activeWorkflowId}/log`, {
method: 'POST', method: 'POST',

View File

@@ -7,6 +7,11 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/w/1', request.url)) return NextResponse.redirect(new URL('/w/1', request.url))
} }
// Skip auth check if DISABLE_AUTH is set (for standalone mode)
if (process.env.DISABLE_AUTH === 'true' || process.env.NEXT_PUBLIC_DISABLE_AUTH === 'true') {
return NextResponse.next()
}
// Existing auth check for protected routes // Existing auth check for protected routes
const sessionCookie = getSessionCookie(request) const sessionCookie = getSessionCookie(request)
if (!sessionCookie) { if (!sessionCookie) {

View File

@@ -10,8 +10,8 @@ const nextConfig: NextConfig = {
// Enable static image optimization for standalone export // Enable static image optimization for standalone export
unoptimized: isStandaloneBuild, unoptimized: isStandaloneBuild,
}, },
// Use 'export' for standalone builds, 'standalone' for regular builds // Always use 'standalone' output to support API routes
output: isStandaloneBuild ? 'export' : 'standalone', output: 'standalone',
// Only include headers when not building for standalone export // Only include headers when not building for standalone export
...(isStandaloneBuild ...(isStandaloneBuild
? {} ? {}

View File

@@ -15,12 +15,13 @@
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
"cli:build": "npm run build -w @sim/cli", "cli:build": "npm run build -w packages/@sim/cli",
"cli:dev": "npm run build -w @sim/cli && cd packages/@sim/cli && node ./dist/index.js", "cli:dev": "npm run build -w packages/@sim/cli && cd packages/@sim/cli && node ./dist/index.js",
"cli:publish": "cd packages/@sim/cli && npm publish --access public", "cli:publish": "cd packages/@sim/cli && npm publish --access public",
"cli:start": "cd packages/@sim/cli && node ./dist/index.js start", "cli:start": "cd packages/@sim/cli && node ./dist/index.js start",
"build:standalone": "node scripts/build-standalone.js", "build:standalone": "node scripts/build-standalone.js",
"build:cli": "npm run cli:build && npm run build:standalone" "build:cli": "npm run cli:build && npm run build:standalone",
"publish:cli": "npm run build:cli && npm run cli:publish"
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.38.0", "@anthropic-ai/sdk": "^0.38.0",

View File

@@ -0,0 +1,34 @@
# Environment variables and secrets
.env
.env.*
*.env
.env.local
.env.development
.env.test
.env.production
# Development files
node_modules
.git
.gitignore
.github
.vscode
.idea
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Test files
test
tests
__tests__
coverage
# Build artifacts
.DS_Store
.cache
.next/cache
# Project specific
.sim-studio-dev

View File

@@ -1,101 +1,79 @@
# Sim Studio CLI # Sim Studio CLI
The Sim Studio CLI provides a convenient way to run Sim Studio directly from your terminal without needing to set up a database or complex environment. A command-line interface for Sim Studio - a powerful, user-friendly platform for building, testing, and optimizing agentic workflows.
## Installation
```bash
npm install -g simstudio
```
Or run directly with npx:
```bash
npx simstudio
```
## Quick Start ## Quick Start
The fastest way to get started is to run:
```bash ```bash
# Run Sim Studio with default settings npx simstudio start
npx sim ```
# Start with custom port This will download and start a standalone version of Sim Studio, with all data stored in your browser's localStorage. No database or authentication required!
npx sim start -p 8080
# Get help ## Usage
npx sim help
### Start Sim Studio
Start a local instance of Sim Studio:
```bash
simstudio start
```
Options:
- `--port <port>` - Specify the port to run on (default: 3000)
- `--debug` - Run in debug mode
### Help
Get help with available commands:
```bash
simstudio --help
``` ```
## Features ## Features
- **Zero Configuration**: Get started immediately with `npx sim` - **Local Storage Mode**: All your workflows and settings are stored in your browser's localStorage, no database required
- **Local Storage**: Works entirely in the browser, no database required - **Workflow Builder**: Create and edit workflows with a visual editor
- **Persistence**: Your workflows and data persist between sessions - **Workflow Execution**: Run workflows and see the results in real-time
- **Familiar Experience**: All the power of Sim Studio in a simplified package - **Environment Variables**: Manage environment variables for your workflows
## Commands ## How It Works
- `sim` - Start Sim Studio with default settings When you run `simstudio start`, the CLI will:
- `sim start` - Start Sim Studio with options
- `sim version` - Display version information
- `sim help` - Show help and usage information
## Options 1. Check if you're in a Sim Studio project directory
2. If not, download and extract a standalone version of Sim Studio
3. Start a local server with the standalone app
4. Open a browser window to the Sim Studio UI
- `-p, --port <port>` - Specify port (default: 3000) All your data is stored in your browser's localStorage, so you can close the app and come back later without losing your work.
- `-d, --debug` - Enable debug mode
- `-v, --version` - Show version information
- `-h, --help` - Show help information
## Local Storage Mode ## Development
When running Sim Studio via the CLI, all data is stored using the browser's localStorage. This means: To contribute to the development of Sim Studio CLI:
- Your workflows persist between browser sessions 1. Clone the repository
- No database configuration is required 2. Install dependencies with `npm install`
- Data is stored locally on your device 3. Build the CLI with `npm run build`
- Multiple users can't share the same workflows (single-user mode) 4. Link the CLI for local development with `npm link`
## Advanced Usage ## License
If you need multi-user capabilities or want to store data in a database, consider: MIT
1. Using the Docker setup in the main repository
2. Setting up a full Sim Studio environment with PostgreSQL
3. Deploying to Vercel with a database
## For Developers: Building & Publishing the CLI
### Release Checklist
1. ✅ Update the CLI code with your changes
2. ✅ Bump the version in `package.json`
3. ✅ Build the standalone version:
```
npm run build:cli
```
4. ✅ Upload the generated `sim-standalone.tar.gz` to GitHub releases
5. ✅ Update the `DOWNLOAD_URL` constant in `packages/@sim/cli/src/commands/start.ts` to point to the new release URL
6. ✅ Commit all changes
7. ✅ Publish to npm:
```
npm run cli:publish
```
### About the Standalone Version
The standalone version is a pre-built and bundled version of Sim Studio that can run without a database or complex setup. It includes:
- A pre-built static export of the Next.js application
- A simple Express server to serve the static files
- Configuration to use browser localStorage for data persistence
This allows users to quickly try Sim Studio with a simple `npx sim` command without installing anything else.
### Testing the CLI Locally
To test the CLI locally:
```bash
# Build the CLI
npm run cli:build
# Run the CLI directly
npm run cli:start
# Or use the dev script
npm run cli:dev
```
## Need Help?
Visit our [documentation](https://github.com/yourusername/sim) or open an issue on GitHub.

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
// This file is the entry point for the 'sim' command // This file is the entry point for the 'simstudio' command
try { try {
require('../dist/index.js') require('../dist/index.js')
} catch (error) { } catch (error) {
if (error.code === 'MODULE_NOT_FOUND') { if (error.code === 'MODULE_NOT_FOUND') {
console.error('Sim CLI has not been built. Please run npm run build first.') console.error('Sim Studio CLI has not been built. Please run npm run build first.')
process.exit(1) process.exit(1)
} else { } else {
console.error('An error occurred while starting Sim CLI:', error) console.error('An error occurred while starting Sim Studio CLI:', error)
process.exit(1) process.exit(1)
} }
} }

View File

@@ -19,7 +19,7 @@ const spinner_1 = require("../utils/spinner");
const SIM_HOME_DIR = path_1.default.join(os_1.default.homedir(), '.sim-studio'); const SIM_HOME_DIR = path_1.default.join(os_1.default.homedir(), '.sim-studio');
const SIM_STANDALONE_DIR = path_1.default.join(SIM_HOME_DIR, 'standalone'); const SIM_STANDALONE_DIR = path_1.default.join(SIM_HOME_DIR, 'standalone');
const SIM_VERSION_FILE = path_1.default.join(SIM_HOME_DIR, 'version.json'); const SIM_VERSION_FILE = path_1.default.join(SIM_HOME_DIR, 'version.json');
const DOWNLOAD_URL = 'https://github.com/simstudioai/sim/releases/download/v0.1.0/sim-standalone.tar.gz'; const DOWNLOAD_URL = 'https://github.com/simstudioai/sim/releases/latest/download/sim-standalone.tar.gz';
const STANDALONE_VERSION = '0.1.0'; const STANDALONE_VERSION = '0.1.0';
/** /**
* Start command that launches Sim Studio using local storage * Start command that launches Sim Studio using local storage
@@ -39,6 +39,8 @@ async function start(options) {
...process.env, ...process.env,
PORT: port, PORT: port,
USE_LOCAL_STORAGE: 'true', // Key environment variable to switch to local storage USE_LOCAL_STORAGE: 'true', // Key environment variable to switch to local storage
NEXT_PUBLIC_USE_LOCAL_STORAGE: 'true', // For client-side code
DISABLE_DB_SYNC: 'true', // Disable database sync
NODE_ENV: debug ? 'development' : 'production', NODE_ENV: debug ? 'development' : 'production',
DEBUG: debug ? '*' : undefined, DEBUG: debug ? '*' : undefined,
}; };
@@ -50,11 +52,40 @@ async function start(options) {
// Running from within the project directory - we'll use the existing // Running from within the project directory - we'll use the existing
// Next.js setup directly // Next.js setup directly
spinner.text = 'Detected Sim Studio project, starting with local configuration...'; spinner.text = 'Detected Sim Studio project, starting with local configuration...';
simProcess = (0, child_process_1.spawn)('npm', ['run', 'dev'], { // When running in dev mode, we need to make sure we're not trying to use static export
env, // as it will fail with API routes
stdio: 'inherit', if (debug) {
shell: true, spinner.text = 'Starting in development mode with local storage...';
}); simProcess = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
env: env,
stdio: 'inherit',
shell: true,
});
}
else {
// In production mode, we'll use the start command which uses the built app
spinner.text = 'Starting in production mode with local storage...';
// Build first if needed
if (!fs_1.default.existsSync(path_1.default.join(process.cwd(), '.next'))) {
spinner.text = 'Building Next.js app first...';
try {
(0, child_process_2.execSync)('npm run build', {
env: env,
stdio: 'inherit'
});
}
catch (error) {
spinner.fail('Failed to build Next.js app');
console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
}
simProcess = (0, child_process_1.spawn)('npm', ['run', 'start'], {
env: env,
stdio: 'inherit',
shell: true,
});
}
} }
else { else {
// Running from outside the project via npx - we'll download and start a standalone version // Running from outside the project via npx - we'll download and start a standalone version
@@ -153,7 +184,7 @@ function checkIfInProjectDirectory() {
if (packageJson.name === 'sim' || if (packageJson.name === 'sim' ||
packageJson.name === 'sim-studio' || packageJson.name === 'sim-studio' ||
(packageJson.dependencies && (packageJson.dependencies &&
(packageJson.dependencies['next'] || packageJson.dependencies['@sim/cli']))) { (packageJson.dependencies['next'] || packageJson.dependencies['@sim/cli'] || packageJson.dependencies['sim-studio-cli']))) {
return true; return true;
} }
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "@sim/cli", "name": "simstudio",
"version": "0.1.0", "version": "0.1.0",
"description": "CLI tool for Sim Studio - easily start, build and test agent workflows", "description": "CLI tool for Sim Studio - easily start, build and test agent workflows",
"license": "MIT", "license": "MIT",
@@ -7,19 +7,21 @@
"main": "dist/index.js", "main": "dist/index.js",
"type": "commonjs", "type": "commonjs",
"bin": { "bin": {
"sim": "./bin/sim.js" "simstudio": "./bin/sim.js"
}, },
"files": [ "files": [
"bin", "bin",
"dist", "dist",
"README.md" "README.md",
"standalone"
], ],
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node bin/sim.js", "start": "node bin/sim.js",
"dev": "ts-node src/index.ts", "dev": "ts-node src/index.ts",
"clean": "rimraf dist", "clean": "rimraf dist",
"prepublishOnly": "npm run clean && npm run build" "prepublishOnly": "npm run clean && npm run build && npm run prepare-standalone && echo 'Checking for sensitive files...' && (! find . -name '.env*' -not -path '*/node_modules/*' -not -path '*/standalone/*' | grep -q .)",
"prepare-standalone": "node ../../../scripts/build-standalone.js"
}, },
"keywords": [ "keywords": [
"sim", "sim",
@@ -27,7 +29,9 @@
"workflow", "workflow",
"automation", "automation",
"cli", "cli",
"agent" "agent",
"ai",
"workflow-automation"
], ],
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
@@ -49,5 +53,16 @@
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/simstudioai/sim"
},
"bugs": {
"url": "https://github.com/simstudioai/sim/issues"
},
"homepage": "https://github.com/simstudioai/sim#readme",
"publishConfig": {
"access": "public"
} }
} }

View File

@@ -20,7 +20,7 @@ const SIM_HOME_DIR = path.join(os.homedir(), '.sim-studio')
const SIM_STANDALONE_DIR = path.join(SIM_HOME_DIR, 'standalone') const SIM_STANDALONE_DIR = path.join(SIM_HOME_DIR, 'standalone')
const SIM_VERSION_FILE = path.join(SIM_HOME_DIR, 'version.json') const SIM_VERSION_FILE = path.join(SIM_HOME_DIR, 'version.json')
const DOWNLOAD_URL = const DOWNLOAD_URL =
'https://github.com/simstudioai/sim/releases/download/v0.1.0/sim-standalone.tar.gz' 'https://github.com/simstudioai/sim/releases/latest/download/sim-standalone.tar.gz'
const STANDALONE_VERSION = '0.1.0' const STANDALONE_VERSION = '0.1.0'
/** /**
@@ -44,6 +44,8 @@ export async function start(options: StartOptions) {
...process.env, ...process.env,
PORT: port, PORT: port,
USE_LOCAL_STORAGE: 'true', // Key environment variable to switch to local storage USE_LOCAL_STORAGE: 'true', // Key environment variable to switch to local storage
NEXT_PUBLIC_USE_LOCAL_STORAGE: 'true', // For client-side code
DISABLE_DB_SYNC: 'true', // Disable database sync
NODE_ENV: debug ? 'development' : ('production' as const), NODE_ENV: debug ? 'development' : ('production' as const),
DEBUG: debug ? '*' : undefined, DEBUG: debug ? '*' : undefined,
} }
@@ -59,11 +61,40 @@ export async function start(options: StartOptions) {
// Next.js setup directly // Next.js setup directly
spinner.text = 'Detected Sim Studio project, starting with local configuration...' spinner.text = 'Detected Sim Studio project, starting with local configuration...'
simProcess = spawn('npm', ['run', 'dev'], { // When running in dev mode, we need to make sure we're not trying to use static export
env: env as NodeJS.ProcessEnv, // as it will fail with API routes
stdio: 'inherit', if (debug) {
shell: true, spinner.text = 'Starting in development mode with local storage...'
}) simProcess = spawn('npm', ['run', 'dev'], {
env: env as NodeJS.ProcessEnv,
stdio: 'inherit',
shell: true,
})
} else {
// In production mode, we'll use the start command which uses the built app
spinner.text = 'Starting in production mode with local storage...'
// Build first if needed
if (!fs.existsSync(path.join(process.cwd(), '.next'))) {
spinner.text = 'Building Next.js app first...'
try {
execSync('npm run build', {
env: env as NodeJS.ProcessEnv,
stdio: 'inherit',
})
} catch (error) {
spinner.fail('Failed to build Next.js app')
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error)
process.exit(1)
}
}
simProcess = spawn('npm', ['run', 'start'], {
env: env as NodeJS.ProcessEnv,
stdio: 'inherit',
shell: true,
})
}
} else { } else {
// Running from outside the project via npx - we'll download and start a standalone version // Running from outside the project via npx - we'll download and start a standalone version
spinner.text = 'Setting up standalone Sim Studio...' spinner.text = 'Setting up standalone Sim Studio...'
@@ -176,7 +207,9 @@ function checkIfInProjectDirectory(): boolean {
packageJson.name === 'sim' || packageJson.name === 'sim' ||
packageJson.name === 'sim-studio' || packageJson.name === 'sim-studio' ||
(packageJson.dependencies && (packageJson.dependencies &&
(packageJson.dependencies['next'] || packageJson.dependencies['@sim/cli'])) (packageJson.dependencies['next'] ||
packageJson.dependencies['@sim/cli'] ||
packageJson.dependencies['sim-studio-cli']))
) { ) {
return true return true
} }

View File

@@ -1,16 +0,0 @@
{
"name": "sim-studio-standalone",
"version": "0.1.0",
"private": true,
"description": "Standalone server for Sim Studio",
"main": "server.js",
"dependencies": {
"express": "^4.18.2"
},
"engines": {
"node": ">=16.0.0"
},
"scripts": {
"start": "node server.js"
}
}

View File

@@ -1,76 +0,0 @@
#!/usr/bin/env node
/**
* Sim Studio Standalone Server
*
* This is a simplified server that serves the pre-built Sim Studio app
* and enables localStorage mode automatically.
*/
const express = require('express')
const path = require('path')
const fs = require('fs')
const { createServer } = require('http')
const { parse } = require('url')
// Configuration
const PORT = process.env.SIM_STUDIO_PORT || 3000
const PUBLIC_DIR = path.join(__dirname, 'public')
const HTML_FILE = path.join(PUBLIC_DIR, 'index.html')
// Create Express app
const app = express()
// Set localStorage environment variable in HTML
const injectLocalStorageScript = (html) => {
const script = `
<script>
// Set localStorage flag for Sim Studio
localStorage.setItem('USE_LOCAL_STORAGE', 'true');
console.log('Sim Studio running in local storage mode');
</script>
`
// Insert script right before the closing </head> tag
return html.replace('</head>', `${script}</head>`)
}
// Middleware to inject localStorage flag
app.use((req, res, next) => {
if (req.path === '/' || req.path.endsWith('.html')) {
const originalSend = res.send
res.send = function (body) {
if (typeof body === 'string' && body.includes('</head>')) {
body = injectLocalStorageScript(body)
}
return originalSend.call(this, body)
}
}
next()
})
// Serve static files
app.use(express.static(PUBLIC_DIR))
// SPA fallback - all routes not matched should serve index.html
app.get('*', (req, res) => {
res.sendFile(HTML_FILE)
})
// Start the server
app.listen(PORT, () => {
console.log(`
┌────────────────────────────────────────────────────┐
│ │
│ 🚀 Sim Studio is running in standalone mode! │
│ │
│ 🌐 Local: http://localhost:${PORT} ${PORT.toString().length < 4 ? ' ' : ''}
│ │
│ 💾 Using localStorage for all data │
│ 🔄 All changes will be saved in your browser │
│ │
│ Press Ctrl+C to stop the server │
│ │
└────────────────────────────────────────────────────┘
`)
})

View File

@@ -1,92 +1,278 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Build Standalone Distribution * Build Standalone App Script
* *
* This script builds a standalone distribution of Sim Studio that can be downloaded * This script builds a standalone version of Sim Studio that can be run without a database.
* and run by the CLI with `npx sim`. * It creates a tarball that includes:
* * 1. A pre-built Next.js application
* The standalone package includes: * 2. A simple Express server to serve the application
* - Pre-built Next.js static export * 3. Configuration to use browser localStorage for data persistence
* - Simplified Express server
* - Configured to use localStorage instead of a database
*/ */
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const { execSync } = require('child_process') const { execSync } = require('child_process')
const packageJson = require('../package.json') const tar = require('tar')
const crypto = require('crypto')
// Configuration // Configuration
const STANDALONE_DIR = path.join(__dirname, '../standalone-dist') const ROOT_DIR = path.resolve(__dirname, '..')
const SOURCE_DIR = path.join(__dirname, '..') const STANDALONE_DIR = path.join(ROOT_DIR, 'packages/@sim/cli/standalone')
const OUTPUT_TARBALL = path.join(__dirname, '../sim-standalone.tar.gz') const STANDALONE_PACKAGE_JSON = path.join(STANDALONE_DIR, 'package.json')
const STANDALONE_SERVER_JS = path.join(STANDALONE_DIR, 'server.js')
const OUTPUT_TARBALL = path.join(ROOT_DIR, 'sim-standalone.tar.gz')
console.log('🔨 Building Sim Studio standalone distribution') // Ensure the standalone directory exists
if (!fs.existsSync(STANDALONE_DIR)) {
// Clean up if the directory exists fs.mkdirSync(STANDALONE_DIR, { recursive: true })
if (fs.existsSync(STANDALONE_DIR)) {
console.log('Cleaning up previous build...')
fs.rmSync(STANDALONE_DIR, { recursive: true, force: true })
} }
// Create standalone directory // Clean the standalone directory first
if (fs.existsSync(STANDALONE_DIR)) {
fs.rmSync(STANDALONE_DIR, { recursive: true, force: true })
}
fs.mkdirSync(STANDALONE_DIR, { recursive: true }) fs.mkdirSync(STANDALONE_DIR, { recursive: true })
fs.mkdirSync(path.join(STANDALONE_DIR, 'public'), { recursive: true })
// Build Next.js static export // Build the Next.js app with local storage mode
console.log('Building Next.js static export...') console.log('Building Next.js app in standalone mode...')
try { try {
// Set environment variable for static export with localStorage // Instead of using static export, we'll build a regular Next.js app
process.env.USE_LOCAL_STORAGE = 'true' // and then copy the necessary files to run it with a simple Express server
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE = 'true'
// Build the app
execSync('npm run build', { execSync('npm run build', {
cwd: SOURCE_DIR, cwd: ROOT_DIR,
stdio: 'inherit', stdio: 'inherit',
env: { env: {
...process.env, ...process.env,
USE_LOCAL_STORAGE: 'true', USE_LOCAL_STORAGE: 'true',
NEXT_PUBLIC_USE_LOCAL_STORAGE: 'true', NEXT_PUBLIC_USE_LOCAL_STORAGE: 'true',
DISABLE_DB_SYNC: 'true',
NODE_ENV: 'production', NODE_ENV: 'production',
}, },
}) })
// Copy the output to standalone directory
console.log('Copying files to standalone directory...')
fs.cpSync(path.join(SOURCE_DIR, 'out'), path.join(STANDALONE_DIR, 'public'), { recursive: true })
} catch (error) { } catch (error) {
console.error('Error building Next.js static export:', error) console.error('Failed to build Next.js app:', error)
console.log('Continuing with the standalone build process...')
}
// Copy the built app to the standalone directory
console.log('Copying built app to standalone directory...')
try {
// Copy the .next directory
if (fs.existsSync(path.join(ROOT_DIR, '.next'))) {
execSync(`cp -r ${path.join(ROOT_DIR, '.next')} ${path.join(STANDALONE_DIR, '.next')}`)
}
// Copy the public directory
if (fs.existsSync(path.join(ROOT_DIR, 'public'))) {
execSync(`cp -r ${path.join(ROOT_DIR, 'public')} ${path.join(STANDALONE_DIR, 'public')}`)
}
// Copy necessary files for standalone operation
if (fs.existsSync(path.join(ROOT_DIR, 'next.config.ts'))) {
execSync(
`cp ${path.join(ROOT_DIR, 'next.config.ts')} ${path.join(STANDALONE_DIR, 'next.config.ts')}`
)
}
} catch (error) {
console.error('Failed to copy built app:', error)
process.exit(1) process.exit(1)
} }
// Copy standalone server files // Create a package.json for the standalone app
console.log('Copying standalone server files...') console.log('Creating package.json for standalone app...')
fs.copyFileSync( const packageJson = {
path.join(SOURCE_DIR, 'packages/@sim/cli/standalone/server.js'), name: 'sim-studio-standalone',
path.join(STANDALONE_DIR, 'server.js') version: '0.1.0',
) private: true,
fs.copyFileSync( scripts: {
path.join(SOURCE_DIR, 'packages/@sim/cli/standalone/package.json'), start: 'node server.js',
path.join(STANDALONE_DIR, 'package.json') },
) dependencies: {
express: '^4.18.2',
next: '^15.2.0',
react: '^18.2.0',
'react-dom': '^18.2.0',
compression: '^1.7.4',
'serve-favicon': '^2.5.0',
typescript: '^5.7.3',
},
}
// Create tarball fs.writeFileSync(STANDALONE_PACKAGE_JSON, JSON.stringify(packageJson, null, 2))
// Create a simple Express server to serve the static files
console.log('Creating server.js for standalone app...')
const serverJs = `
const express = require('express');
const next = require('next');
const path = require('path');
const fs = require('fs');
const compression = require('compression');
const favicon = require('serve-favicon');
const crypto = require('crypto');
// Register TypeScript compiler
require('typescript');
// Set environment variables for standalone mode
process.env.USE_LOCAL_STORAGE = 'true';
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE = 'true';
process.env.DISABLE_DB_SYNC = 'true';
process.env.DISABLE_AUTH = 'true';
process.env.NEXT_PUBLIC_DISABLE_AUTH = 'true';
const port = process.env.SIM_STUDIO_PORT || 3000;
const dev = false; // Always run in production mode
// Initialize Next.js
const app = next({ dev, dir: __dirname });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
// Enable compression
server.use(compression());
// Serve favicon
if (fs.existsSync(path.join(__dirname, 'public', 'favicon.ico'))) {
server.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
}
// Middleware to set environment variables for each request
server.use((req, res, next) => {
// These will be available to the API routes
process.env.USE_LOCAL_STORAGE = 'true';
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE = 'true';
process.env.DISABLE_DB_SYNC = 'true';
process.env.DISABLE_AUTH = 'true';
process.env.NEXT_PUBLIC_DISABLE_AUTH = 'true';
next();
});
// Redirect root path to a new workflow
server.get('/', (req, res) => {
// Generate a UUID for the new workflow
const uuid = crypto.randomUUID();
console.log(\`Redirecting to new workflow: \${uuid}\`);
res.redirect(\`/w/\${uuid}\`);
});
// Handle all other requests with Next.js
server.all('*', (req, res) => {
return handle(req, res);
});
// Start the server
server.listen(port, (err) => {
if (err) throw err;
console.log(\`> Sim Studio standalone server ready on http://localhost:\${port}\`);
console.log('> Running in local storage mode - all data will be stored in the browser');
console.log('> Authentication is disabled - anyone can access the app');
});
});
`
fs.writeFileSync(STANDALONE_SERVER_JS, serverJs)
// Skip creating .env file for the standalone app
console.log('Skipping .env file creation for standalone app...')
// Create a tsconfig.json for the standalone app
console.log('Creating tsconfig.json for standalone app...')
const tsconfigJson = {
compilerOptions: {
target: 'es5',
lib: ['dom', 'dom.iterable', 'esnext'],
allowJs: true,
skipLibCheck: true,
strict: true,
forceConsistentCasingInFileNames: true,
noEmit: true,
esModuleInterop: true,
module: 'esnext',
moduleResolution: 'node',
resolveJsonModule: true,
isolatedModules: true,
jsx: 'preserve',
incremental: true,
plugins: [
{
name: 'next',
},
],
paths: {
'@/*': ['./*'],
},
},
include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
exclude: ['node_modules'],
}
fs.writeFileSync(path.join(STANDALONE_DIR, 'tsconfig.json'), JSON.stringify(tsconfigJson, null, 2))
// Create a README.md for the standalone app
console.log('Creating README.md for standalone app...')
const readmeFile = `# Sim Studio Standalone
This is a standalone version of Sim Studio that runs without a database. All data is stored in your browser's localStorage.
## Getting Started
1. Install dependencies:
\`\`\`
npm install
\`\`\`
2. Start the server:
\`\`\`
npm start
\`\`\`
3. Open your browser to http://localhost:3000
## Features
- **Full Functionality**: All features of Sim Studio are available, including API routes
- **No Authentication Required**: Just start building workflows right away
- **Local Storage**: All your workflows and settings are stored in your browser's localStorage
- **Drag and Drop Interface**: Easily create and connect workflow blocks
- **Real-time Execution**: Test your workflows as you build them
## Environment Variables
You can customize the app by setting these environment variables:
- \`SIM_STUDIO_PORT\`: Change the port (default: 3000)
## How It Works
When you start the app, it will:
1. Automatically redirect you to a new workflow with a unique ID
2. Allow you to drag and drop blocks to build your workflow
3. Save all your work to localStorage in your browser
4. Let you execute workflows directly in the browser
No database or authentication is required!
`
fs.writeFileSync(path.join(STANDALONE_DIR, 'README.md'), readmeFile)
// Create the tarball
console.log('Creating tarball...') console.log('Creating tarball...')
try { try {
execSync(`tar -czf "${OUTPUT_TARBALL}" -C "${STANDALONE_DIR}" .`, { // Remove any .env files before creating the tarball
stdio: 'inherit', console.log('Removing any .env files from the standalone directory...')
}) execSync('find ' + STANDALONE_DIR + ' -name ".env*" -type f -delete')
console.log(`✅ Standalone distribution created: ${OUTPUT_TARBALL}`) // Create the tarball
console.log(`📦 Size: ${(fs.statSync(OUTPUT_TARBALL).size / (1024 * 1024)).toFixed(2)} MB`) execSync(
`tar -czf ${OUTPUT_TARBALL} -C ${path.dirname(STANDALONE_DIR)} ${path.basename(STANDALONE_DIR)}`
)
console.log(`Standalone app built and packaged to ${OUTPUT_TARBALL}`)
console.log('You can now upload this file to GitHub releases')
} catch (error) { } catch (error) {
console.error('Error creating tarball:', error) console.error('Failed to create tarball:', error)
process.exit(1) process.exit(1)
} }
console.log('\n🚀 Next steps:')
console.log('1. Upload the tarball to your release assets')
console.log('2. Update the DOWNLOAD_URL in the CLI code to point to your release')
console.log('3. Publish the CLI package to npm with: npm run cli:publish')

102
scripts/release-npm.js Executable file
View File

@@ -0,0 +1,102 @@
#!/usr/bin/env node
/**
* Release NPM Package Script
*
* This script helps with the process of releasing the Sim Studio CLI to npm.
* It builds the standalone app, prepares the CLI package, and publishes it to npm.
*/
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
// Configuration
const ROOT_DIR = path.resolve(__dirname, '..')
const CLI_DIR = path.join(ROOT_DIR, 'packages/@sim/cli')
const STANDALONE_DIR = path.join(CLI_DIR, 'standalone')
const OUTPUT_TARBALL = path.join(ROOT_DIR, 'sim-standalone.tar.gz')
// Ensure we're in the right directory
process.chdir(ROOT_DIR)
// Helper function to run commands and log output
function runCommand(command, options = {}) {
console.log(`> ${command}`)
try {
execSync(command, {
stdio: 'inherit',
...options,
})
return true
} catch (error) {
console.error(`Error running command: ${command}`)
console.error(error)
return false
}
}
// Main release process
async function release() {
console.log('=== Sim Studio CLI Release Process ===')
// 1. Clean up any existing standalone files
console.log('\n1. Cleaning up existing standalone files...')
if (fs.existsSync(STANDALONE_DIR)) {
fs.rmSync(STANDALONE_DIR, { recursive: true, force: true })
}
if (fs.existsSync(OUTPUT_TARBALL)) {
fs.unlinkSync(OUTPUT_TARBALL)
}
// 2. Build the standalone app
console.log('\n2. Building standalone app...')
if (!runCommand('node scripts/build-standalone.js')) {
console.error('Failed to build standalone app')
process.exit(1)
}
// 3. Prepare the CLI package
console.log('\n3. Preparing CLI package...')
process.chdir(CLI_DIR)
// Clean and build
if (!runCommand('npm run clean && npm run build')) {
console.error('Failed to build CLI package')
process.exit(1)
}
// 4. Create the standalone directory
console.log('\n4. Creating standalone directory...')
if (!fs.existsSync(STANDALONE_DIR)) {
fs.mkdirSync(STANDALONE_DIR, { recursive: true })
}
// 5. Publish to npm
console.log('\n5. Publishing to npm...')
console.log('Ready to publish to npm. Run the following commands:')
console.log(`
cd ${path.relative(process.cwd(), CLI_DIR)}
npm publish
`)
// 6. Create GitHub release
console.log('\n6. Creating GitHub release...')
console.log('After publishing to npm, create a GitHub release with the standalone tarball:')
console.log(`
1. Go to https://github.com/simstudioai/sim/releases/new
2. Set the tag to v0.1.0 (or your current version)
3. Set the title to "Sim Studio v0.1.0"
4. Upload the tarball: ${path.relative(ROOT_DIR, OUTPUT_TARBALL)}
5. Publish the release
`)
console.log('\n=== Release Process Complete ===')
console.log('Follow the instructions above to publish to npm and create a GitHub release.')
}
// Run the release process
release().catch((error) => {
console.error('Release process failed:', error)
process.exit(1)
})

View File

@@ -17,6 +17,9 @@ export interface SyncConfig {
syncInterval?: number syncInterval?: number
onSyncSuccess?: (response: any) => void onSyncSuccess?: (response: any) => void
onSyncError?: (error: any) => void onSyncError?: (error: any) => void
// Local storage key for standalone mode
localStorageKey?: string
} }
export const DEFAULT_SYNC_CONFIG: Partial<SyncConfig> = { export const DEFAULT_SYNC_CONFIG: Partial<SyncConfig> = {
@@ -42,6 +45,38 @@ export async function performSync(config: SyncConfig): Promise<boolean> {
return true return true
} }
// Check if we're in local storage mode
const useLocalStorage =
typeof window !== 'undefined' &&
(window.localStorage.getItem('USE_LOCAL_STORAGE') === 'true' ||
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE === 'true' ||
process.env.DISABLE_DB_SYNC === 'true')
if (useLocalStorage && config.localStorageKey) {
// In local storage mode, save directly to localStorage
try {
window.localStorage.setItem(
config.localStorageKey,
JSON.stringify({
data: payload,
timestamp: new Date().toISOString(),
})
)
if (config.onSyncSuccess) {
config.onSyncSuccess({ success: true, message: 'Saved to local storage' })
}
return true
} catch (error) {
if (config.onSyncError) {
config.onSyncError(error)
}
return false
}
}
// If not in local storage mode or no localStorageKey provided, use API
return await sendWithRetry(config.endpoint, payload, config) return await sendWithRetry(config.endpoint, payload, config)
} catch (error) { } catch (error) {
if (config.onSyncError) { if (config.onSyncError) {

View File

@@ -40,6 +40,21 @@ export async function initializeSyncManagers(): Promise<boolean> {
managers = [workflowSync, environmentSync] managers = [workflowSync, environmentSync]
try { try {
// Check if we're in local storage mode
const useLocalStorage =
typeof window !== 'undefined' &&
(window.localStorage.getItem('USE_LOCAL_STORAGE') === 'true' ||
process.env.NEXT_PUBLIC_USE_LOCAL_STORAGE === 'true' ||
process.env.DISABLE_DB_SYNC === 'true')
if (useLocalStorage) {
console.log('Running in local storage mode - skipping DB sync')
// In local storage mode, we don't need to fetch from DB
// Just load from localStorage directly
initialized = true
return true
}
// Fetch data from DB on initialization to replace local storage // Fetch data from DB on initialization to replace local storage
await Promise.all([ await Promise.all([
fetchEnvironmentVariables(), fetchEnvironmentVariables(),
@@ -50,6 +65,9 @@ export async function initializeSyncManagers(): Promise<boolean> {
initialized = true initialized = true
return true return true
} catch (error) { } catch (error) {
console.error('Error initializing sync managers:', error)
// Even if there's an error, mark as initialized so the app can continue
initialized = true
return false return false
} finally { } finally {
initializing = false initializing = false