An experimental TypeSpec syntax for Lexicon
1#!/bin/bash
2set -e
3
4# Usage: ./scripts/publish-all.sh <version> [--dry]
5# Example: ./scripts/publish-all.sh 0.4.0
6# Example: ./scripts/publish-all.sh 0.4.0 --dry
7
8if [ -z "$1" ]; then
9 echo "Error: Version argument required"
10 echo "Usage: ./scripts/publish-all.sh <version> [--dry]"
11 echo "Example: ./scripts/publish-all.sh 0.4.0"
12 echo "Example: ./scripts/publish-all.sh 0.4.0 --dry"
13 exit 1
14fi
15
16VERSION="$1"
17DRY_RUN=false
18
19if [ "$2" = "--dry" ]; then
20 DRY_RUN=true
21fi
22
23# Validate version format (basic semver check)
24if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
25 echo "Error: Invalid version format. Use semver format (e.g., 0.4.0 or 0.4.0-beta.1)"
26 exit 1
27fi
28
29echo "📦 Publishing all packages at version $VERSION"
30echo ""
31
32# Get the root directory
33ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
34cd "$ROOT_DIR"
35
36# Find all package.json files in packages/*
37ALL_PACKAGES=($(find packages -maxdepth 2 -name "package.json" -not -path "*/node_modules/*" | sort))
38
39# Filter out private packages and topologically sort by dependencies
40PACKAGES=($(node -e "
41 const fs = require('fs');
42 const allPackages = process.argv.slice(1);
43
44 // Filter out private packages
45 const packages = allPackages.filter(path => {
46 const pkg = JSON.parse(fs.readFileSync(path, 'utf-8'));
47 return !pkg.private;
48 });
49
50 // Build dependency graph
51 const graph = new Map();
52 const pkgNames = new Map();
53
54 packages.forEach(path => {
55 const pkg = JSON.parse(fs.readFileSync(path, 'utf-8'));
56 pkgNames.set(pkg.name, path);
57
58 const deps = new Set();
59 [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies].forEach(depObj => {
60 if (depObj) {
61 Object.keys(depObj).forEach(dep => {
62 if (dep.startsWith('@typelex/')) {
63 deps.add(dep);
64 }
65 });
66 }
67 });
68
69 graph.set(pkg.name, deps);
70 });
71
72 // Topological sort - packages with more dependents first
73 const sorted = [];
74 const processed = new Set();
75
76 function visit(pkgName) {
77 if (processed.has(pkgName)) return;
78 processed.add(pkgName);
79
80 // Visit all dependencies first
81 const deps = graph.get(pkgName) || new Set();
82 deps.forEach(dep => {
83 if (graph.has(dep)) {
84 visit(dep);
85 }
86 });
87
88 sorted.push(pkgName);
89 }
90
91 // Visit all packages
92 graph.forEach((_, pkgName) => visit(pkgName));
93
94 // Output sorted package paths
95 sorted.forEach(name => {
96 if (pkgNames.has(name)) {
97 console.log(pkgNames.get(name));
98 }
99 });
100" "${ALL_PACKAGES[@]}"))
101
102if [ ${#PACKAGES[@]} -eq 0 ]; then
103 echo "Error: No publishable packages found in packages/"
104 exit 1
105fi
106
107echo "Found ${#PACKAGES[@]} publishable packages (topologically sorted):"
108for pkg in "${PACKAGES[@]}"; do
109 PKG_NAME=$(node -p "require('./$pkg').name")
110 echo " - $PKG_NAME"
111done
112echo ""
113
114# Update all package.json files with the new version
115echo "🔄 Updating versions in all packages..."
116for pkg in "${PACKAGES[@]}"; do
117 PKG_DIR=$(dirname "$pkg")
118 PKG_NAME=$(node -p "require('./$pkg').name")
119
120 echo " Updating $PKG_NAME..."
121
122 # Update version
123 node -e "
124 const fs = require('fs');
125 const path = '$pkg';
126 const pkg = require('./' + path);
127 pkg.version = '$VERSION';
128
129 // Helper to preserve semver prefix (^, ~, etc.) and workspace: protocol
130 function updateVersion(currentVersion, newVersion) {
131 // Preserve workspace: protocol for monorepo
132 if (currentVersion.startsWith('workspace:')) {
133 return currentVersion;
134 }
135 // Preserve semver prefix
136 const match = currentVersion.match(/^([~^>=<]*)(.*)$/);
137 if (match) {
138 return match[1] + newVersion;
139 }
140 return newVersion;
141 }
142
143 // Helper to update dependencies
144 function updateDeps(deps) {
145 if (!deps) return;
146 for (const dep in deps) {
147 if (dep.startsWith('@typelex/')) {
148 deps[dep] = updateVersion(deps[dep], '$VERSION');
149 }
150 }
151 }
152
153 updateDeps(pkg.dependencies);
154 updateDeps(pkg.devDependencies);
155 updateDeps(pkg.peerDependencies);
156
157 fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
158 "
159done
160
161echo ""
162echo "✅ All versions updated to $VERSION"
163echo ""
164
165if [ "$DRY_RUN" = true ]; then
166 echo "✅ Dry run complete! Version updates have been applied."
167 echo ""
168 echo "📋 Updated packages:"
169 for pkg in "${PACKAGES[@]}"; do
170 PKG_NAME=$(node -p "require('./$pkg').name")
171 echo " - $PKG_NAME@$VERSION"
172 done
173 echo ""
174 echo "💡 Review the changes, then run without --dry to publish."
175 exit 0
176fi
177
178# Ask for confirmation
179read -p "🚀 Ready to publish all packages to npm. Continue? (y/N) " -n 1 -r
180echo
181if [[ ! $REPLY =~ ^[Yy]$ ]]; then
182 echo "❌ Publish cancelled"
183 exit 1
184fi
185
186echo ""
187echo "📤 Publishing packages..."
188echo ""
189
190# Publish each package
191PUBLISHED=()
192FAILED=()
193
194for pkg in "${PACKAGES[@]}"; do
195 PKG_DIR=$(dirname "$pkg")
196 PKG_NAME=$(node -p "require('./$pkg').name")
197
198 echo "Publishing $PKG_NAME..."
199
200 if (cd "$PKG_DIR" && npm publish --access public); then
201 echo " ✅ $PKG_NAME published successfully"
202 PUBLISHED+=("$PKG_NAME")
203 else
204 echo " ❌ $PKG_NAME failed to publish"
205 FAILED+=("$PKG_NAME")
206 fi
207
208 echo ""
209done
210
211# Summary
212echo "📊 Summary:"
213echo ""
214echo "Published (${#PUBLISHED[@]}):"
215for pkg in "${PUBLISHED[@]}"; do
216 echo " ✅ $pkg"
217done
218
219if [ ${#FAILED[@]} -gt 0 ]; then
220 echo ""
221 echo "Failed (${#FAILED[@]}):"
222 for pkg in "${FAILED[@]}"; do
223 echo " ❌ $pkg"
224 done
225 exit 1
226fi
227
228echo ""
229echo "🎉 All packages published successfully!"