CI/CD Guide|Integrate Google Apps Script Web App with GitHub Actions for Free Packaging Platform
Developers facing complex CI/CD setups can streamline workflows by connecting Google Apps Script Web App with GitHub Actions, Slack, and Firebase APIs to build a free, collaborative packaging tool platform that enhances cross-team efficiency.
点击这里查看本文章简体中文版本。
點擊這裡查看本文章正體中文版本。
This post was translated with AI assistance — let me know if anything sounds off!
Table of Contents
CI/CD Practical Guide (4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform
GAS Web App Integrates GitHub, Slack, Firebase, or Asana/Jira APIs to Build a Middleware Platform for Cross-Team Shared Packaging Tools
Photo by Lee Campbell
Introduction
In the previous article, CI/CD Practical Guide (Part 3): Implementing App iOS CI and CD Workflows with GitHub Actions, we completed the foundational CI/CD setup for the iOS app project. Now, CI can automatically run tests and CD can handle packaging and deployment. However, in real-world product development, packaging and deployment are often done to deliver builds to other functional teams for QA (Quality Assurance) testing. At this point, the CD scenario extends beyond engineering and may involve QA, PM, design (Design QA), or even the boss wanting to try it out first.
The GitHub Actions workflow_dispatch manual form trigger event provides a simple form for users to run builds. However, it is not user-friendly for non-engineers who may not understand: what is a branch? Should fields be filled? How to know when the build is done? How to download it once ready? …etc.
Additionally, there is a permission control issue. To allow other team members to use GitHub Actions for building, their accounts must be added to the repo. This is insecure and unreasonable from a security perspective, as they get access to the entire source code just to operate the build form.
Unlike Jenkins, which has an independent web tool platform, GitHub Actions only offers this functionality.
Form Style of workflow_dispatch
Therefore, we need an intermediary packaging platform to serve other functional users, integrating Asana/Jira task tickets so users can package the app directly using the task ticket and view progress and download results on it.
GAS Web App as a Relay Station
Previous article focused on developing the core GitHub Actions CI/CD workflow on the right side; this article focuses on the left side—the end-user packaging tool platform and enhancing user experience.
Google Apps Script — Web App Packaging Tool Platform Result Screenshot
Packaging Form: Integrate project management tools to fetch ticket numbers and GitHub to retrieve open Pull Requests
Build Records: Displays build history, progress status of ongoing build tasks, and allows clicking to retrieve download links from Firebase App Distribution.
Runner Status: Displays the status of the Self-hosted Runner.
Slack build progress notification.
Mobile Support
Support restricting usage to accounts within the organization team
Main Responsibilities
0 state, 0 database, purely a relay exchange station, integrating and displaying data from various APIs (e.g. Asana/Jira/GitHub), forwarding form requests to GitHub Actions.
Operation Requirements: Support both mobile and desktop devices.
Permission Requirement: Access must be restricted to team organization members only.
Online Demo Web App
- For first-time use, please refer to the authorization in the image below (Only For Demo App):
Project source code: https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit
Technology Choices
The first article mentioned this; here is a more detailed summary.
Integration with Slack
We tried using Slack as the packaging platform by developing our own backend service hosted on GCP, integrating with Slack API and Asana API, then forwarding the forms submitted via Slack to the GitHub API to trigger subsequent GitHub Actions. The experience was very smooth and unified within the team’s collaboration tool, allowing painless usage. The downside was the high development and maintenance costs. Since the backend service was built with Ktor, app engineers had to handle backend tasks, manage Google service OAuth integration, and implement some features (e.g., submission for review) there, making it complex. If new team members didn’t properly take over this part later, it became almost impossible to maintain. Additionally, there was a monthly $15 USD GCP server cost.
Initially, we also tried using FaaS services to connect with the Slack API, such as Cloud Functions. However, the Slack API requires the service to respond within 3 seconds, or it considers the request failed. FaaS has a cold start problem, where the service goes to sleep after a period of inactivity and takes longer to respond (≥ 5 seconds) when called again. This caused the Slack packaging form to be very unstable, frequently resulting in Timeout Errors.
Integration into Internal Systems
This is obviously the optimal solution. If the team has web and backend developers, directly integrating with the existing system is the best and safest approach.
The premise of this article is: none, the app is self-reliant.
Google Apps Script — Web App
Google Apps Script has been our longtime partner. We have used it for many RPA projects to schedule and trigger tasks, such as: “Crashlytics + Google Analytics Automatic Query for App Crash-Free Users Rate” and “Using Google Apps Script to Automate Daily Data Report RPA”. At this point, I recalled it again; GAS has a feature to deploy as a Web (App) to serve directly as a web service.
Advantages of Google Apps Script:
✅ Functions as a Service means no need to set up or maintain your own server
✅ Permission control integrates with Google Workspace, allowing use only by Google accounts within the organization
✅ Seamless integration with Google ecosystem services (e.g., Firebase, GA, etc.) and data (no need to implement OAuth yourself)
✅ Programming language uses JavaScript, easy to learn (V8 Runtime supports ES6+)
✅ Fast writing, fast deployment, fast usage
✅ Stable and long-lasting service (launched for over 16 years)
✅ AI Can Help! Tested Using ChatGPT to Assist Development, Accuracy Can Reach 95%
Google Apps Script Disadvantages:
❌ Built-in version control is difficult to describe positively
❌ No built-in support for file storage, data storage, or key/certificate management
❌ Web App cannot fully achieve a 100% responsive web design (RWD) experience
❌ The project can only be linked to a personal account, not an organization
❌ Although Google continues development and maintenance, overall feature updates are slow
❌ Network request
UrlFetchAppdoes not support setting User-Agent❌ Web App
doGet/doPostdoes not support retrieving Headers information❌ FaaS Cold Start Issue
❌ Does not support multiple developers working simultaneously
However, this has little impact on the Web App; at most, you just wait a few extra seconds to access the page.
The above are the pros and cons of the GAS service itself, which have little impact on building a packaging tool web app; compared to a Slack-based solution, this approach is faster, lighter, and easier to hand over and get started with. The drawbacks are that the team needs to know the tool’s URL and how to use it, and due to GAS library limitations (e.g., no built-in encryption algorithms), it can basically only serve as a pure relay platform, such as submitting for review, which only forwards the review request to GitHub Actions.
Also only suitable for teams using Google Workspace; balancing resources and needs, Google Apps Script — Web App was chosen to implement the packaging tool platform.
UI Framework
We directly use the Bootstrap CDN; otherwise, managing CSS styles ourselves would be too troublesome. Asking AI how to combine and use Bootstrap is also more accurate and convenient.
Hands-On Practice
The entire platform architecture has been open-sourced here. Everyone can customize it based on their team’s needs using this version.
Open Source Sample Project
View the project directly on GAS:
GitHub Repo Backup:
File Structure
I wrote a very simple class-based MVC-like structure. If you need adjustments or don’t understand any feature, you can ask AI for accurate answers.
System
appsscript.json: Metadata configuration file for the GAS system
The key point is the “oauthScopes” variable, which declares the external permissions this Script will use.Entrypoint.gs: Define the doGet() entry point
Controller
- Controller_iOS.gs: iOS packaging tool page Controller, responsible for fetching data for the View to display
View
View_index.html: The entire packaging tool framework and homepage
View_iOS.html: iOS Packaging Tool Page Skeleton
View_iOS_Runs.html: iOS Packaging Tool — Packaging Record Content Page
View_iOS_Form.html: iOS Packaging Tool — Packaging Form Page
View_iOS_Runners.html: iOS Packaging Tool — Self-hosted Runner Status Page
Model(Lib)
Credentials.gs: Define key contents
(⚠️ Please note, using GCP IAM with GAS can be quite complex, so we directly define the keys here. Therefore, this GAS project contains sensitive information; do not share project view or edit permissions casually.)StubData.gs: Stub methods and data for the Online Demo.
Settings.gs: Some common settings and library initialization.
GitHub.gs: Wrapper for GitHub API operations.
Slack.gs: Wrapper for Slack API operations.
Firebase.gs: Firebase — Wrapper for App Distribution API operations.
Build Your Own Packaging Platform
- Create a Google Apps Script project and name it
Go to Project Settings → check “Show ‘appsscript.json’ manifest file in editor” to make the “appsscript.json” metadata file appear.
- Refer to my open source project files, create all files following the examples, and blindly copy the content over.
Stupid, but no choice.
Copy StubData.gs first; it can be used for the initial deployment test.
Another method is to use clasp (Google Apps Script CLI) to git clone the demo project and then push the code.
After copying, it will look exactly the same as the example project.
- First Deployment of the “Web App” and Viewing Results
In the top right corner of the project, click “Deploy” → “New deployment” → Type “Web app”:
Execution Identity:
I execute the script always using your account identity.
Users accessing the web app will
run the script as the currently logged-in Google account user.
Who can access:
Only me
All users within the same organization
Only users logged in with a Google account from the same organization can access.All users logged into a Google account
All logged-in Google account users can access.Everyone
does not need to log in with a Google account, and everyone can access it publicly.
If it is an internal tool: you can choose “Who has access: All users in the same organization as XXX” + “Execute as: User accessing the web app” for security control.
The URL of the “Web App” after deployment is your Web App packaging tool link, which you can share with your team members. (The URL may look ugly, so you can use a URL shortener service to tidy it up. Updating the deployment content will not change the URL.)
User must agree to authorization on first use
The first time you click the Web App URL, you need to authorize access.
Review Permission → Select the account identity to use this Web App
Unverified warning window, click “Advanced” to expand → click Proceed to “XXX” (unsafe)
- Click “Allow”
No need to reauthorize if the script permissions remain unchanged.
After completing the authorization consent, you will enter the packaging tool homepage:
Demo packaging tool deployed successfully 🎉🎉🎉
Note: The message “This app was created by a Google Apps Script user” cannot be automatically hidden.
Update Deployment
⚠️ All code changes require redeployment to take effect.
️️⚠️ All code changes require redeployment to take effect.
⚠️ All code changes require redeployment to take effect.
Note that saving code changes does not directly update the Web App, so if refreshing has no effect, this is the reason; you need to go to “Deploy” → “Manage Deployments” → “Edit” → create a new version → click “Deploy” → “Done”.
Refresh the page after updating the deployment to see the changes take effect.
Add test deployment for easier development
As mentioned earlier, all changes require redeployment to take effect; this is very inconvenient during development. Therefore, during development, we can use a “test deployment” to quickly verify if the changes are correct.
Go to “Deploy” → “Test deployments” → obtain the test “Web App” URL.
During development, we can save directly using this URL. After saving file changes, refresh the page at this development URL to see the results!
After all development is complete, update and deploy as described earlier, then release it for users to use.
Modify the Demo Sample Project to Connect with Real Data
Next is the key part: connecting real data. The GitHub Actions Workflow refers to the CI/CD process established in the previous article. You can also adjust the parameters according to your actual Actions Workflow.
⚠️ Please Note Before Making Changes
The Google Apps Script platform does not support multi-user or multiple windows development well. From my experience, if you accidentally open two editing windows, edit in window A first, then later edit in window B, the changes from A will be overwritten by the older version in B. Therefore, it is recommended that only one person edits the script in one window at a time.
GitHub Integration
Inject GitHub API Token:
GitHub -> Account -> Settings -> Developer Settings -> Fine-grained personal access tokens or Personal access tokens (classic).
It is recommended to use Fine-grained personal access tokens for better security (though they have an expiration).
The required permissions for Fine-grained personal access tokens are as follows:
Repo: Remember to select the Repo you want to operate on
Permissions:
Actions (read/write),Administration (read only)
If you don’t want to rely on someone’s personal account, it is recommended to create a dedicated team GitHub account and use its token.
Go to the GAS project → Credentials.gs → Insert the token into the githubToken variable.
Replace GithubStub with GitHub:
Go to the GAS project → Settings.gs → change:
1
const iOSGitHub = new GitHubStub(githubToken, iOSRepoPath);
Change to
1
const iOSGitHub = new GitHub(githubToken, iOSRepoPath);
Save the file.
Refresh the test “Web App” URL to check if the changes are correct:
Correctly displaying data means: GitHub webhook successfully connected with real data 🎉🎉🎉
You can also easily switch to the “Runner Status” to check whether the Self-hosted Runner is functioning properly:
Note: My Runner is off… so it’s offline.
Slack Integration
To integrate Slack notifications, we first need to go back to the Repo → GitHub Actions and add a notification wrapper Action for the package build Action Workflow.
CD-Deploy-Form.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# Workflow(Action) Name
name: CD-Deploy-Form
# Actions Log Title
run-name: "[CD-Deploy-Form] ${{ github.ref }}"
# Cancel running jobs in the same concurrency group if a new job starts
# For example, if the same branch's packaging job is triggered repeatedly, the previous job will be cancelled
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Trigger events
on:
# Manual form trigger
workflow_dispatch:
# Form input fields
inputs:
# App version number
VERSION_NUMBER:
description: 'Version Number of the app (e.g., 1.0.0). Auto-detect from the Xcode project if left blank.'
required: false
type: string
# App Build Number
BUILD_NUMBER:
description: 'Build number of the app (e.g., 1). Will use a timestamp if left blank.'
required: false
type: string
# App Release Note
RELEASE_NOTE:
description: 'Release notes of the deployment.'
required: false
type: string
# Triggering user's Slack User ID
SLACK_USER_ID:
description: 'Slack user id.'
required: true
type: string
# Triggering user's Email
AUTHOR:
description: 'Trigger author email.'
required: true
type: string
# Job tasks
jobs:
# Send Slack message when packaging starts
# Job ID
start-message:
# Run small jobs directly on GitHub Hosted Runner, usage is low
runs-on: ubuntu-latest
# Set maximum timeout to prevent endless waiting in abnormal situations
# Normally should not exceed 5 minutes
timeout-minutes: 5
# Job steps
steps:
- name: Post a Start Slack Message
id: slack
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: \\|
channel: ${{ inputs.SLACK_USER_ID }}
text: "Packaging request received.\nID: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\\|${{ github.run_id }}>\nBranch: ${{ github.ref_name }}\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
# Job Output for subsequent jobs
# ts = Slack message ID, used to reply in the same thread later
outputs:
ts: ${{ steps.slack.outputs.ts }}
deploy:
# Jobs run concurrently by default; needs ensures this waits for start-message to finish
# Execute packaging and deployment task
needs: start-message
uses: ./.github/workflows/CD-Deploy.yml
secrets: inherit
with:
VERSION_NUMBER: ${{ inputs.VERSION_NUMBER }}
BUILD_NUMBER: ${{ inputs.BUILD_NUMBER }}
RELEASE_NOTE: ${{ inputs.RELEASE_NOTE }}
AUTHOR: ${{ inputs.AUTHOR }}
# Success message for packaging and deployment
end-message-success:
needs: [start-message, deploy]
if: ${{ needs.deploy.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Post a Success Slack Message
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: \\|
channel: ${{ inputs.SLACK_USER_ID }}
thread_ts: "${{ needs.start-message.outputs.ts }}"
text: "✅ Packaging and deployment succeeded.\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
# Failure message for packaging and deployment
end-message-failure:
needs: [deploy, start-message]
if: ${{ needs.deploy.result == 'failure' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Post a Failure Slack Message
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: \\|
channel: ${{ inputs.SLACK_USER_ID }}
thread_ts: "${{ needs.start-message.outputs.ts }}"
text: "❌ Packaging and deployment failed. Please check the execution results or try again later.\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
# Cancelled message for packaging and deployment
end-message-cancelled:
needs: [deploy, start-message]
if: ${{ needs.deploy.result == 'cancelled' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Post a Cancelled Slack Message
uses: slackapi/slack-github-action@v2.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: \\|
channel: ${{ inputs.SLACK_USER_ID }}
thread_ts: "${{ needs.start-message.outputs.ts }}"
text: ":black_square_for_stop: Packaging and deployment cancelled.\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
Full code: CD-Deploy-Form.yml
This Action is just a container that connects Slack notifications, actually reusing the CD-Deploy.yml Action written in the previous article.
For setting up the Slack Bot App and message sending permissions, please refer to my previous article.
Remember to add the corresponding
SLACK_BOT_TOKENin Repo → Secrets and enter the Slack Bot App Token value.
Back to the GAS project → Credentials.gs → Assign the Token to the slackBotToken variable.
Go to the GAS project → Settings.gs → replace:
1
const slack = new SlackStub(slackBotToken);
Change to
1
const slack = new Slack(slackBotToken);
Save the file.
If you don’t have a ready Slack Bot App to send notifications and don’t want to create one, you can skip all the steps here and remove the Slack-related usage from the GAS project.
GitHub Integration — Build Form
Go to the GAS project → Controller_iOS.gs → modify the content of View_iOS_Form.html:
Remove the fake Asana Tasks integration method:
<? tasks.forEach(function(task) { ?>
<option value="<?=task.githubBranch?>">[<?=task.id?>] <?=task.title?></option>
<? }) ?>
You can also adjust the default branch here (this is main).
—
Go to the GAS project → Controller_iOS.gs → modify the content of iOSLoadForm():
Remove the line
template.tasks = Stubable.fetchStubAsanaTasks();which is a stub method for Asana integration.
If you want to integrate Asana/Jira, you can directly ask ChatGPT to help generate the integration method.template.prs = iOSGitHub.fetchOpenPRs();actually calls the GitHub API to get the list of opened PRs and can be kept as needed.
Post-submission Processing Inside iOSSubmitForm():
You can adjust according to the actual GitHub Actions Workflow file name and the workflow_dispatch inputs parameters:
1
2
3
4
5
6
7
8
iOSGitHub.dispatchWorkflow("CD-Deploy-Form.yml", branch, {
"BUILD_NUMBER": buildNumber,
"VERSION_NUMBER": versionNumber,
"VERSION_NUMBER": versionNumber,
"RELEASE_NOTE": releaseNote,
"AUTHOR": email,
"SLACK_USER_ID": slack.fetchUserID(email)
});
You can also add your own required field validations. Here, it only checks that the branch must be filled; otherwise, an error message will appear.
If you think this is not secure enough, you can add password verification or restrict access to specific accounts.
The last line Slack notification requires proper Slack setup. If you don’t have a Slack Bot App or don’t want to integrate Slack, you can directly use
iOSGitHub.dispatchWorkflow("CD-Deploy.yml")in the Demo Actions Repo and remove theSLACK_USER_IDparameter.
Refresh the test “Web App” URL to check if the changes are correct:
You can see that only the Opened PR List remains in the packaging form.
Fill in the information and click “Submit Request” to test the packaging form:
If the prompt submission is successful, it means there is no problem. You can also see the task started in the packaging records 🎉 :
Repeated packaging records can update progress.
Common Submission Errors:
Required input ‘SLACK_USER_ID’ not provided: This SLACK_USER_ID field in GitHub Actions is required but not provided. It may be due to Slack configuration failure or the current User Email not matching any Slack UID.
Workflow does not have ‘workflow_dispatch’ trigger,Branch is outdated, please update the xxx branch: The selected branch cannot find the corresponding Action Workflow file (the file specified by iOSGitHub.dispatchWorkflow).
No ref found for,Branch not found: The branch could not be found.
Firebase App Distribution — Getting Download Link Integration
The last small feature is integrating Firebase App Distribution to directly get download info and links, making it easy to open the packaging platform tool on mobile and click to download and install.
Previously introduced “Google Apps Script x Google APIs Quick Integration Method“—GAS can quickly and easily integrate with Firebase.
Integration Principle
Before integration, let’s first explain the integration principle of this “Tricky” method.
Our build platform has no database and only serves as an API relay; so in practice, when we run the build in GitHub Actions CD-Deploy.yml, we pass the Job Run ID into the Release Note (it can also be passed into the Build Number):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ID="${{ github.run_id }}" // Job Run ID
COMMIT_SHA="${{ github.sha }}"
BRANCH_NAME="${{ github.ref_name }}"
AUTHOR="${{ env.AUTHOR }}"
# Compose Release Note
RELEASE_NOTE="${{ env.RELEASE_NOTE }}
ID: ${ID}
Commit SHA: ${COMMIT_SHA}
Branch: ${BRANCH_NAME}
Author: ${AUTHOR}
"
# Run Fastlane build & deploy lane
bundle exec fastlane beta release_notes:"${RELEASE_NOTE}" version_number:"${VERSION_NUMBER}" build_number:"${BUILD_NUMBER}"
This way, the Firebase App Distribution Release Notes will include the Job Run ID.
The GAS Web App packaging tool platform connects to the GitHub API to get GitHub Actions run records. We directly use the Job Run ID from the API to query the Firebase App Distribution API. If the Release Notes contain a version with *ID: XXX*, we can find the corresponding packaging record.
Mapping between two tool platforms without using any database.
Project Integration Settings
Go to GAS → Project Settings → Google Cloud Platform (GCP) Project → Change Project:
Enter the Firebase project ID you want to connect.
Initial setup may show errors “To change the project, please configure the OAuth consent screen. Details on setting up the OAuth consent screen.” If not applicable, you can skip the following steps.
Click the “OAuth consent screen details” link → Click “Configure consent screen”:
Click “Start”:
Application Information:
Application Name:
Enter Your Tool NameUser Support Email:
Select Email
Target Audience:
Internal: For use by organization members only
External: Available to all Google account users after authorization consent
Contact Information:
- Email for receiving notifications
Check to agree to the 《 Google API Services: User Data Policy 》。
Finally, click “Create”.
—
Back to GAS → Project Settings → Google Cloud Platform (GCP) Project → Change Project:
Re-enter the Firebase project number and click “Change Project”.
If no errors appear, the binding is complete.
—
If you choose “External,” you may also need to complete the following settings:
Click “Project Number” → expand the left sidebar → “APIs & Services” → “OAuth consent screen”
Select “Target Audience” → Test → Click “Deploy as Web App” → Done.
Users can start using it after completing the authorization following the “First-time user authorization” steps mentioned above!
If the above steps are not set, users will encounter the following error:
Access to “XXX” blocked due to incomplete Google verification process
— — —
Project Integration
Back to the integration, Firebase directly uses ScriptApp.getOAuthToken() to dynamically obtain the token based on the execution identity, so no token setup is needed.
Just go to the GAS project → Settings.gs → and change:
1
const iOSFirebase = new FirebaseStub(iOSFirebaseProject);
Change to
1
const iOSFirebase = new Firebase(iOSFirebaseProject);
Done.
Refresh the test “Web App” URL to the build records → Find a record and click “Get Download Link”:
If the corresponding Job Run Id build is found in Firebase App Distribution Release Notes, the download information will be displayed directly, and clicking the download button will take you straight to the download page.
Done! 🎉🎉🎉
Results
By now, you have replaced the examples with your actual usable packaging tools. Remaining custom features, additional third-party API integrations, and more forms can be extended on your own (discuss with ChatGPT).
Finally, don’t forget that after development and testing are complete, you need to follow the previous steps — updating the deployment for the changes to take effect!
Integration Extensions
Continuing the spirit of our “middleman” role, here are a few quick integration CheatSheets:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function asanaAPI(endPoint, method = "GET", data = null) {
var options = {
"method" : method,
"headers": {
"Authorization": "Bearer "+asanaToken
},
"payload" : data
};
var url = "https://app.asana.com/api/1.0"+endPoint;
var res = UrlFetchApp.fetch(url, options);
var data = JSON.parse(res.getContentText());
return data;
}
asanaAPI("/projects/{project_gid}/tasks")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// jql = filter condition
function jiraTickets(jql) {
const url = `https://xxx.atlassian.net/rest/api/3/search`;
const maxResults = 100;
let allIssues = [];
let startAt = 0;
let total = 0;
do {
const queryParams = {
jql: jql,
startAt: startAt,
maxResults: maxResults,
fields: "assignee,summary,status"
};
const queryString = Object.keys(queryParams)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
.join("&");
const options = {
method: "get",
headers: {
Authorization: "Basic " + jiraToken,
"Content-Type": "application/json",
},
muteHttpExceptions: true,
};
const response = UrlFetchApp.fetch(`${url}?${queryString}`, options);
const json = JSON.parse(response.getContentText());
if (response.getResponseCode() != 200) {
throw new Error("Failed to fetch Jira issues.");
}
if (json.issues && json.issues.length > 0) {
allIssues = allIssues.concat(json.issues);
total = json.total;
startAt += json.issues.length;
} else {
break;
}
} while (startAt < total);
var groupIssues = {};
for(var i = 0; i < allIssues.length; i++) {
const issue = allIssues[i];
if (groupIssues[issue.fields.status.name] == null) {
groupIssues[issue.fields.status.name] = [];
}
groupIssues[issue.fields.status.name].push(issue);
}
return groupIssues;
}
jiraTickets(`project IN(App)`);
If a database is really needed, Google Sheets can be used as a substitute:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Saveable {
constructor(type) {
// https://docs.google.com/spreadsheets/d/Sheet-ID/edit
const spreadsheet = SpreadsheetApp.openById("Sheet-ID");
this.sheet = spreadsheet.getSheetByName("Data"); // Sheet Name
this.type = type;
}
write(key, value) {
this.sheet.appendRow([
this.type,
key,
JSON.stringify(value)
]);
}
read(key) {
const data = this.sheet.getDataRange().getValues();
const row = data.find(r => r[0] === this.type && r[1] === key);
if (row) {
return JSON.parse(row[2]);
}
return null;
}
}
let saveable = Saveable("user");
// Write
saveable.write("birthday_zhgchgli", "0718");
// Read
saveable.read("birthday_zhgchgli"); // -> 0718
Slack API & Sending Message Method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function slackSendMessage(channel, text = "", blocks = null) {
const content = {
channel: channel,
unfurl_links: false,
unfurl_media: false,
text: text,
blocks: blocks
};
try {
const response = slackRequest("chat.postMessage", content);
return response;
} catch (error) {
throw new Error(`Failed to send Slack message: ${error}`);
}
}
function slackRequest(path, content) {
const options = {
method: "post",
contentType: "application/json",
headers: {
Authorization: `Bearer ${slackBotToken}`,
'X-Slack-No-Retry': 1
},
payload: JSON.stringify(content)
};
try {
const response = UrlFetchApp.fetch("https://slack.com/api/"+path, options);
const responseData = JSON.parse(response.getContentText());
if (responseData.ok) {
return responseData
} else {
throw new Error(`Slack: ${responseData.error}`);
}
} catch (error) {
throw error;
}
}
More Google Apps Script Examples:
Quick Integration Method for Google Apps Script x Google APIs
Simple 3 Steps — Create a Free GA4 Automated Data Notification Bot
Automate Daily Data Reports with RPA Using Google Apps Script
Create a Free GitHub Repo Star Notifier in Three Steps Using Google Apps Script
Crashlytics + Google Analytics Automatic Query for App Crash-Free Users Rate
Crashlytics + Big Query to Build a More Real-Time and Convenient Crash Tracking Tool
Summary
Thank you for your patience and participation. The CI/CD From 0 to 1 series ends here; I hope it helps you and your team build a complete CI/CD workflow to improve efficiency and product stability. Feel free to leave comments if you have any implementation questions. These four articles took about 14+ days to write. If you found them helpful, please follow me on Medium and share with your friends and colleagues.
You’re welcome.
Buy me a coffee
Series Articles:
If you have any questions or suggestions, feel free to contact me.
This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.





















































{:target="_blank"}](/assets/4273e57e7148/1*znvPmqsaivk3KhsE26sFwA.webp)
{:target="_blank"}](/assets/4273e57e7148/1*QJj54G9gOjtQS-rbHVT1SQ.webp)