5151 SANITY_STUDIO_DATASET : ${{ vars.SANITY_STUDIO_DATASET }}
5252 SANITY_STUDIO_PROJECT_ID : ${{ vars.SANITY_STUDIO_PROJECT_ID }}
5353
54+ - name : Deploy Sanity Ops App (Sanity App SDK)
55+ working-directory : apps/ops
56+ run : |
57+ ORG_ID=$(node -e "
58+ fetch('https://api.sanity.io/v2021-06-07/projects/${{ vars.SANITY_STUDIO_PROJECT_ID }}', {
59+ headers: { Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN }
60+ })
61+ .then(r => r.json())
62+ .then(d => {
63+ if (!d.organizationId) throw new Error('No organizationId in project response');
64+ console.log(d.organizationId);
65+ })
66+ .catch(err => { console.error(err); process.exit(1); });
67+ ")
68+ export SANITY_ORGANIZATION_ID="$ORG_ID"
69+ EXISTING_APP_ID=$(node -e "
70+ fetch('https://api.sanity.io/v2024-08-01/user-applications?appType=coreApp&organizationId=' + process.env.SANITY_ORGANIZATION_ID, {
71+ headers: { Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN }
72+ })
73+ .then(r => r.ok ? r.json() : [])
74+ .then(apps => {
75+ const list = Array.isArray(apps) ? apps : [];
76+ const match = list.find(a => a.title === 'CodingCat Ops & Revenue OS');
77+ if (match && match.id) console.log(match.id);
78+ })
79+ .catch(() => {});
80+ ")
81+ if [ -n "$EXISTING_APP_ID" ]; then
82+ echo "Found existing CodingCat Ops & Revenue OS app: $EXISTING_APP_ID"
83+ SANITY_OPS_APP_ID="$EXISTING_APP_ID" npx sanity deploy -y
84+ else
85+ echo "Creating and deploying new CodingCat Ops & Revenue OS app in organization $ORG_ID..."
86+ npx sanity deploy -y --create --title "CodingCat Ops & Revenue OS"
87+ fi
88+ env :
89+ SANITY_AUTH_TOKEN : ${{ secrets.SANITY_AUTH_TOKEN }}
90+
5491 - name : Promote Stack to Organization Scope
5592 run : |
5693 mkdir -p .sanity
@@ -59,11 +96,11 @@ jobs:
5996 env :
6097 SANITY_AUTH_TOKEN : ${{ secrets.SANITY_AUTH_TOKEN }}
6198
62- - name : Deploy Sanity Blueprints Functions
99+ - name : Deploy Sanity Blueprints & Poll Synchronously Until Completed
63100 run : |
64101 ORG_ID=$(node -e "
65102 fetch('https://api.sanity.io/v2021-06-07/projects/${{ vars.SANITY_STUDIO_PROJECT_ID }}', {
66- headers: { Authorization: 'Bearer ${{ secrets. SANITY_AUTH_TOKEN }}' }
103+ headers: { Authorization: 'Bearer ' + process.env. SANITY_AUTH_TOKEN }
67104 })
68105 .then(r => r.json())
69106 .then(d => {
@@ -75,7 +112,62 @@ jobs:
75112 echo "Resolved Organization ID: $ORG_ID"
76113 mkdir -p .sanity
77114 echo "{\"organizationId\":\"$ORG_ID\"}" > .sanity/blueprint.config.json
115+
116+ echo "=== Previous Stack Info & Logs ==="
117+ pnpm exec sanity blueprints info --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --verbose || true
118+ pnpm exec sanity blueprints logs --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} || true
119+
120+ echo "=== Starting Blueprint Deployment ==="
78121 pnpm exec sanity blueprints deploy --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --no-wait
122+
123+ echo "=== Synchronously Polling Blueprint Stack Operation Until Terminal State ==="
124+ ORG_ID="$ORG_ID" STACK_NAME="${{ vars.SANITY_STUDIO_DATASET }}" node -e "
125+ const token = process.env.SANITY_AUTH_TOKEN;
126+ const orgId = process.env.ORG_ID;
127+ const stackName = process.env.STACK_NAME;
128+ const headers = {
129+ Accept: 'application/json',
130+ Authorization: 'Bearer ' + token,
131+ 'X-Sanity-Scope-Type': 'organization',
132+ 'X-Sanity-Scope-Id': orgId,
133+ };
134+ (async () => {
135+ const stacksRes = await fetch('https://api.sanity.io/v2026-06-15/blueprints/stacks', { headers });
136+ if (!stacksRes.ok) throw new Error('Failed to list stacks: HTTP ' + stacksRes.status);
137+ const stacks = await stacksRes.json();
138+ const stack = stacks.find(s => s.name === stackName || s.id === stackName);
139+ if (!stack) throw new Error('Stack not found: ' + stackName);
140+ console.log('Polling Stack:', stack.name, '(' + stack.id + ')');
141+ const deadline = Date.now() + 5 * 60 * 1000;
142+ while (Date.now() < deadline) {
143+ const res = await fetch('https://api.sanity.io/v2026-06-15/blueprints/stacks/' + stack.id, { headers });
144+ if (!res.ok) throw new Error('Failed to fetch stack ' + stack.id + ': HTTP ' + res.status);
145+ const data = await res.json();
146+ const op = data.recentOperation;
147+ const status = op?.status || 'NONE';
148+ console.log('[' + new Date().toISOString() + '] Operation ' + (op?.id || 'n/a') + ' status: ' + status);
149+ if (status === 'COMPLETED' || status === 'NONE') {
150+ console.log('✔ Blueprint Stack operation settled with status:', status);
151+ process.exit(0);
152+ }
153+ if (status === 'FAILED' || status === 'CANCELLED') {
154+ console.error('✖ Blueprint Stack operation failed:', JSON.stringify(op, null, 2));
155+ const logsRes = await fetch('https://api.sanity.io/v2026-06-15/blueprints/logs?stackId=' + stack.id + (op?.id ? '&operationId=' + op.id : ''), { headers });
156+ if (logsRes.ok) {
157+ const logs = await logsRes.json();
158+ console.error('Operation logs:', JSON.stringify(logs, null, 2));
159+ }
160+ process.exit(1);
161+ }
162+ await new Promise(r => setTimeout(r, 4000));
163+ }
164+ console.error('✖ Timed out waiting 5 minutes for Blueprint Stack operation to complete');
165+ process.exit(1);
166+ })().catch(err => { console.error(err); process.exit(1); });
167+ "
168+
169+ echo "=== Verified Post-Deploy Stack Info ==="
170+ pnpm exec sanity blueprints info --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --verbose
79171 env :
80172 SANITY_AUTH_TOKEN : ${{ secrets.SANITY_AUTH_TOKEN }}
81173 SANITY_API_WRITE_TOKEN : ${{ secrets.SANITY_API_WRITE_TOKEN }}
@@ -125,6 +217,43 @@ jobs:
125217 SANITY_STUDIO_DATASET : ${{ vars.SANITY_STUDIO_DATASET }}
126218 SANITY_STUDIO_PROJECT_ID : ${{ vars.SANITY_STUDIO_PROJECT_ID }}
127219
220+ - name : Deploy Sanity Ops App (Sanity App SDK)
221+ working-directory : apps/ops
222+ run : |
223+ ORG_ID=$(node -e "
224+ fetch('https://api.sanity.io/v2021-06-07/projects/${{ vars.SANITY_STUDIO_PROJECT_ID }}', {
225+ headers: { Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN }
226+ })
227+ .then(r => r.json())
228+ .then(d => {
229+ if (!d.organizationId) throw new Error('No organizationId in project response');
230+ console.log(d.organizationId);
231+ })
232+ .catch(err => { console.error(err); process.exit(1); });
233+ ")
234+ export SANITY_ORGANIZATION_ID="$ORG_ID"
235+ EXISTING_APP_ID=$(node -e "
236+ fetch('https://api.sanity.io/v2024-08-01/user-applications?appType=coreApp&organizationId=' + process.env.SANITY_ORGANIZATION_ID, {
237+ headers: { Authorization: 'Bearer ' + process.env.SANITY_AUTH_TOKEN }
238+ })
239+ .then(r => r.ok ? r.json() : [])
240+ .then(apps => {
241+ const list = Array.isArray(apps) ? apps : [];
242+ const match = list.find(a => a.title === 'CodingCat Ops & Revenue OS');
243+ if (match && match.id) console.log(match.id);
244+ })
245+ .catch(() => {});
246+ ")
247+ if [ -n "$EXISTING_APP_ID" ]; then
248+ echo "Found existing CodingCat Ops & Revenue OS app: $EXISTING_APP_ID"
249+ SANITY_OPS_APP_ID="$EXISTING_APP_ID" npx sanity deploy -y
250+ else
251+ echo "Creating and deploying new CodingCat Ops & Revenue OS app in organization $ORG_ID..."
252+ npx sanity deploy -y --create --title "CodingCat Ops & Revenue OS"
253+ fi
254+ env :
255+ SANITY_AUTH_TOKEN : ${{ secrets.SANITY_AUTH_TOKEN }}
256+
128257 - name : Promote Stack to Organization Scope
129258 run : |
130259 mkdir -p .sanity
@@ -133,11 +262,11 @@ jobs:
133262 env :
134263 SANITY_AUTH_TOKEN : ${{ secrets.SANITY_AUTH_TOKEN }}
135264
136- - name : Deploy Sanity Blueprints Functions
265+ - name : Deploy Sanity Blueprints & Poll Synchronously Until Completed
137266 run : |
138267 ORG_ID=$(node -e "
139268 fetch('https://api.sanity.io/v2021-06-07/projects/${{ vars.SANITY_STUDIO_PROJECT_ID }}', {
140- headers: { Authorization: 'Bearer ${{ secrets. SANITY_AUTH_TOKEN }}' }
269+ headers: { Authorization: 'Bearer ' + process.env. SANITY_AUTH_TOKEN }
141270 })
142271 .then(r => r.json())
143272 .then(d => {
@@ -149,7 +278,62 @@ jobs:
149278 echo "Resolved Organization ID: $ORG_ID"
150279 mkdir -p .sanity
151280 echo "{\"organizationId\":\"$ORG_ID\"}" > .sanity/blueprint.config.json
281+
282+ echo "=== Previous Stack Info & Logs ==="
283+ pnpm exec sanity blueprints info --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --verbose || true
284+ pnpm exec sanity blueprints logs --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} || true
285+
286+ echo "=== Starting Blueprint Deployment ==="
152287 pnpm exec sanity blueprints deploy --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --no-wait
288+
289+ echo "=== Synchronously Polling Blueprint Stack Operation Until Terminal State ==="
290+ ORG_ID="$ORG_ID" STACK_NAME="${{ vars.SANITY_STUDIO_DATASET }}" node -e "
291+ const token = process.env.SANITY_AUTH_TOKEN;
292+ const orgId = process.env.ORG_ID;
293+ const stackName = process.env.STACK_NAME;
294+ const headers = {
295+ Accept: 'application/json',
296+ Authorization: 'Bearer ' + token,
297+ 'X-Sanity-Scope-Type': 'organization',
298+ 'X-Sanity-Scope-Id': orgId,
299+ };
300+ (async () => {
301+ const stacksRes = await fetch('https://api.sanity.io/v2026-06-15/blueprints/stacks', { headers });
302+ if (!stacksRes.ok) throw new Error('Failed to list stacks: HTTP ' + stacksRes.status);
303+ const stacks = await stacksRes.json();
304+ const stack = stacks.find(s => s.name === stackName || s.id === stackName);
305+ if (!stack) throw new Error('Stack not found: ' + stackName);
306+ console.log('Polling Stack:', stack.name, '(' + stack.id + ')');
307+ const deadline = Date.now() + 5 * 60 * 1000;
308+ while (Date.now() < deadline) {
309+ const res = await fetch('https://api.sanity.io/v2026-06-15/blueprints/stacks/' + stack.id, { headers });
310+ if (!res.ok) throw new Error('Failed to fetch stack ' + stack.id + ': HTTP ' + res.status);
311+ const data = await res.json();
312+ const op = data.recentOperation;
313+ const status = op?.status || 'NONE';
314+ console.log('[' + new Date().toISOString() + '] Operation ' + (op?.id || 'n/a') + ' status: ' + status);
315+ if (status === 'COMPLETED' || status === 'NONE') {
316+ console.log('✔ Blueprint Stack operation settled with status:', status);
317+ process.exit(0);
318+ }
319+ if (status === 'FAILED' || status === 'CANCELLED') {
320+ console.error('✖ Blueprint Stack operation failed:', JSON.stringify(op, null, 2));
321+ const logsRes = await fetch('https://api.sanity.io/v2026-06-15/blueprints/logs?stackId=' + stack.id + (op?.id ? '&operationId=' + op.id : ''), { headers });
322+ if (logsRes.ok) {
323+ const logs = await logsRes.json();
324+ console.error('Operation logs:', JSON.stringify(logs, null, 2));
325+ }
326+ process.exit(1);
327+ }
328+ await new Promise(r => setTimeout(r, 4000));
329+ }
330+ console.error('✖ Timed out waiting 5 minutes for Blueprint Stack operation to complete');
331+ process.exit(1);
332+ })().catch(err => { console.error(err); process.exit(1); });
333+ "
334+
335+ echo "=== Verified Post-Deploy Stack Info ==="
336+ pnpm exec sanity blueprints info --organization-id "$ORG_ID" --stack ${{ vars.SANITY_STUDIO_DATASET }} --verbose
153337 env :
154338 SANITY_AUTH_TOKEN : ${{ secrets.SANITY_AUTH_TOKEN }}
155339 SANITY_API_WRITE_TOKEN : ${{ secrets.SANITY_API_WRITE_TOKEN }}
0 commit comments