├── .claspignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── README.ru.md ├── clients └── src │ ├── Code.js │ └── appsscript.json ├── gulpfile.js ├── package.json ├── settings ├── dev │ └── .clasp.json └── prod │ └── .clasp.json └── src ├── .eslintrc ├── Code.js └── appsscript.json /.claspignore: -------------------------------------------------------------------------------- 1 | **/** 2 | !appsscript.json 3 | !**/*.gs 4 | !**/*.js 5 | !**/*.ts 6 | !**/*.html -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "google", "plugin:prettier/recommended"], 4 | "plugins": ["prettier"], 5 | "env": { 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": 2020, 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "prettier/prettier": "error" 14 | }, 15 | "globals": {} 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.clasp.json 2 | node_modules 3 | build 4 | dist 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "bracketSameLine": true, 9 | "printWidth": 120 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hedgehog's burrows 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An empty Google Apps Script project 2 | -------------------------------------------------------------------------------- /README.ru.md: -------------------------------------------------------------------------------- 1 | # Пустой проект Google Apps Script 2 | 3 | Начальный набор для запуска и разработки приложения на google Apps Script локально. 4 | 5 | Основной особенностью является возможность обратного получения кода с сервера, конечно, при условии, что вы не использовали транспиллеры и обфускаторы. Другой важной особенностью является возможность обновления и использования срузу нескольких файлов проектов Google Apps Script, как для разработки и тестирования, так и для промышленного запуска. 6 | 7 | ## Как использовать 8 | 9 | Для начала скопируйте текущий репозиторий для себя локально, например, в папку `myproject` 10 | 11 | ```shell 12 | $> git clone --depth 1 https://github.com/hedgehogsburrows/empty-google-apps-script-project.git ./myproject 13 | ``` 14 | 15 | Удалите ссылку на git. 16 | 17 | ```shell 18 | $> rm -rf ./myproject/.git 19 | ``` 20 | -------------------------------------------------------------------------------- /clients/src/Code.js: -------------------------------------------------------------------------------- 1 | function onOpen(e) { 2 | Library.onOpen(e); 3 | } 4 | -------------------------------------------------------------------------------- /clients/src/appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "Europe/Moscow", 3 | "dependencies": { 4 | "enabledAdvancedServices": [ 5 | { 6 | "userSymbol": "Sheets", 7 | "serviceId": "sheets", 8 | "version": "v4" 9 | } 10 | ], 11 | "libraries": [ 12 | { 13 | "userSymbol": "Library", 14 | "libraryId": "", 15 | "version": "", 16 | "developmentMode": false 17 | } 18 | ] 19 | }, 20 | "exceptionLogging": "STACKDRIVER", 21 | "runtimeVersion": "V8" 22 | } 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import { spawn } from 'child_process'; 3 | import * as fs from 'fs'; 4 | import { createRequire } from 'module'; 5 | import { deleteAsync } from 'del'; 6 | const require = createRequire(import.meta.url); 7 | const packageJson = require('./package.json'); 8 | 9 | const watchDelay = (packageJson.devSettings ? packageJson.devSettings.watchDelay : undefined) || 3000; 10 | 11 | const argv = { 12 | buildpart: 'build', 13 | }; 14 | 15 | const version = [packageJson.version, '⎇']; 16 | /** 17 | * Cleans build 18 | */ 19 | gulp.task('clean', function () { 20 | return deleteAsync(`${argv.buildpart}`); 21 | }); 22 | 23 | /** 24 | * Cleans build 25 | */ 26 | gulp.task('clean-clients', function () { 27 | return deleteAsync('clients/build'); 28 | }); 29 | /** 30 | * Preparation of the configuration file .clasp.json 31 | */ 32 | gulp.task('preparation-clasp-json', function preparationClaspJson() { 33 | return gulp.src(`./settings/${argv.part}/.clasp.json`).pipe(gulp.dest('./')); 34 | }); 35 | 36 | /** 37 | * Preparation of assets files 38 | */ 39 | gulp.task('preparation-assets', function preparationAssets() { 40 | return gulp 41 | .src(`./settings/${argv.part}/assets/**/*.{ts,js,gs,json,html}`, { 42 | base: `./settings/${argv.part}/assets`, 43 | }) 44 | .pipe(gulp.dest(`${argv.buildpart}/_assets`)); 45 | }); 46 | 47 | /** 48 | * The prebuild action 49 | */ 50 | gulp.task('pre-build', function preBuild() { 51 | return gulp.src('./src/**/*.{ts,js,gs,json,html}').pipe(gulp.dest(`./${argv.buildpart}`)); 52 | }); 53 | 54 | /** 55 | * Runs clasp 56 | */ 57 | gulp.task('clasp', function (cb) { 58 | cb = cb || console.log; 59 | const cmd = spawn('./node_modules/.bin/clasp', ['push'], { 60 | stdio: 'inherit', 61 | }); 62 | cmd.on('close', function (code) { 63 | console.log('clasp exited with code ' + code); 64 | cb(code); 65 | }); 66 | }); 67 | 68 | gulp.task('preparation-update-client-code', function (cb) { 69 | return gulp.src('./clients/src/**/*.{ts,js,gs,json,html}').pipe(gulp.dest('./clients/build')); 70 | }); 71 | 72 | gulp.task('preparation-update-clients', function (cb) { 73 | let version = ''; 74 | const cmd = spawn('./node_modules/.bin/clasp', ['versions'], { 75 | stdio: 'pipe', 76 | }); 77 | cmd.stdout.on('data', function (data) { 78 | if (version === '') { 79 | const match = data.toString().match(/^(\d+).+?-/); 80 | if (match && match.length) version = match[1]; 81 | } 82 | }); 83 | cmd.on('close', function (code) { 84 | console.log(`clasp exit code: ${code}.`, `Version detected: ${version}`); 85 | const appscript = JSON.parse(fs.readFileSync('./clients/src/appsscript.json')); 86 | const claspjson = JSON.parse(fs.readFileSync('./.clasp.json')); 87 | const index = appscript.dependencies.libraries.findIndex((lib) => lib.userSymbol === 'Library'); 88 | // console.log(`Lib is ${!~index ? 'NOT ' : ''}DETECTED`); 89 | appscript.dependencies.libraries[index].version = version; 90 | appscript.dependencies.libraries[index].libraryId = claspjson.scriptId; 91 | appscript.dependencies.libraries[index].developmentMode = Object.prototype.hasOwnProperty.call( 92 | argv, 93 | 'developmentMode', 94 | ); 95 | 96 | // fs.mkdirSync('./clients/build', { recursive: true }); 97 | fs.writeFileSync('./clients/build/appsscript.json', JSON.stringify(appscript, null, ' '), { flag: 'w' }); 98 | cb(code); 99 | }); 100 | }); 101 | 102 | gulp.task('update-clients-bulk', function (cb) { 103 | const projects = JSON.parse( 104 | /start[\s\S]*?`([\s\S]+?)`[\s\S]*?end/.exec(fs.readFileSync(`./settings/${argv.part}/assets/index.js`))[1], 105 | ); 106 | // cb(projects); 107 | const clientsList = argv['clients-list'] ? JSON.parse(`[${argv['clients-list']}]`) : undefined; 108 | const tasks = projects.projects 109 | .map((project) => (_cb_) => { 110 | // _cb_ = _cb_ || console.log; 111 | 112 | const claspjson = { 113 | scriptId: project.projectId, 114 | }; 115 | fs.writeFileSync('./clients/build/.clasp.json', JSON.stringify(claspjson)); 116 | console.log(project); 117 | // cb(0); 118 | const cmd = spawn('./../../node_modules/.bin/clasp', ['push', '--force'], { 119 | stdio: 'inherit', 120 | cwd: './clients/build', 121 | }); 122 | cmd.on('close', function (code) { 123 | console.log('clasp exited with code ' + code); 124 | _cb_(code); 125 | }); 126 | }) 127 | .filter( 128 | (_, i) => 129 | !clientsList || 130 | clientsList.includes(projects.projects[i].stage) || 131 | clientsList.includes(projects.projects[i].container) || 132 | clientsList.includes(projects.projects[i].projectId), 133 | ); 134 | console.log(tasks); 135 | return gulp.series(...tasks)(cb); 136 | }); 137 | 138 | /** 139 | * Attention! Changes global `version` object 140 | */ 141 | gulp.task('update-version', async function (cb) { 142 | version.splice(2); 143 | const abbrevRef = spawn('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { 144 | stdio: 'pipe', 145 | }); 146 | 147 | const short = spawn('git', ['rev-parse', '--short', 'HEAD'], { 148 | stdio: 'pipe', 149 | }); 150 | 151 | for await (const data of abbrevRef.stdout) version.push(data.toString().replace(/[\r\n]/g, '')); 152 | for await (const data of short.stdout) version.push(data.toString().replace(/[\r\n]/g, '')); 153 | return cb(0); 154 | }); 155 | 156 | gulp.task('push-version', function (cb) { 157 | const namedVersion = argv['push-version'] ? `${argv['push-version']}` : ''; 158 | const versionStr = `${version.join(' ')} ${namedVersion}`; 159 | 160 | spawn('clasp', ['version', versionStr], { 161 | stdio: 'pipe', 162 | }) 163 | .stdout.on('data', (data) => console.info(data.toString().replace(/[\r\n]/g, ''))) 164 | .on('close', () => { 165 | console.info(`${versionStr} is pushed.`); 166 | cb(0); 167 | }); 168 | }); 169 | 170 | // ====================================================================== 171 | 172 | gulp.task('bulk', function (cb) { 173 | const projects = JSON.parse(fs.readFileSync('./scripts/projects.json')); 174 | const tasks = projects.projects.map((project) => (_cb_) => { 175 | _cb_ = _cb_ || console.log; 176 | const claspjson = { 177 | scriptId: project.projectId, 178 | }; 179 | fs.writeFileSync('./scripts/src/.clasp.json', JSON.stringify(claspjson)); 180 | console.log(project.projectId); 181 | const cmd = spawn('./../../node_modules/.bin/clasp', ['push'], { 182 | stdio: 'inherit', 183 | cwd: './scripts/src', 184 | }); 185 | cmd.on('close', function (code) { 186 | console.log('clasp exited with code ' + code); 187 | _cb_(code); 188 | }); 189 | }); 190 | console.log(tasks); 191 | return gulp.series(...tasks)(cb); 192 | }); 193 | 194 | gulp.task('readversion', function (cb) { 195 | let version = ''; 196 | cb = cb || console.log; 197 | const cmd = spawn('./node_modules/.bin/clasp', ['versions'], { 198 | stdio: 'pipe', 199 | }); 200 | cmd.stdout.on('data', function (data) { 201 | if (version === '') { 202 | const match = data.toString().match(/^(\d+).+?-/); 203 | if (match && match.length) version = match[1]; 204 | } 205 | }); 206 | cmd.on('close', function (code) { 207 | console.log('clasp exited with code ' + code, version); 208 | const appscript = JSON.parse(fs.readFileSync('./scripts/src/appsscript.json')); 209 | const index = appscript.dependencies.libraries.findIndex((lib) => lib.userSymbol === 'Luxcom'); 210 | console.log(`Lib is ${!~index ? 'NOT ' : ''}DETECTED`); 211 | appscript.dependencies.libraries[index].version = version; 212 | fs.writeFileSync('./scripts/src/appsscript.json', JSON.stringify(appscript, null, ' ')); 213 | cb(code); 214 | }); 215 | }); 216 | 217 | gulp.task('update-clients', gulp.series('readversion', 'bulk')); 218 | 219 | gulp.task('readversion-prod', function (cb) { 220 | let version = ''; 221 | cb = cb || console.log; 222 | const cmd = spawn('./node_modules/.bin/clasp', ['versions'], { 223 | stdio: 'pipe', 224 | }); 225 | 226 | cmd.stdout.on('data', function (data) { 227 | if (version === '') { 228 | const match = data.toString().match(/^(\d+).+?-/); 229 | if (match && match.length) version = match[1]; 230 | } 231 | }); 232 | cmd.on('close', function (code) { 233 | console.log('clasp exited with code ' + code, version); 234 | const appscript = JSON.parse(fs.readFileSync('./scripts/src/appsscript.json')); 235 | console.info(appscript); 236 | const index = appscript.dependencies.libraries.findIndex((lib) => lib.userSymbol === 'Luxcom'); 237 | appscript.dependencies.libraries[index].version = version; 238 | appscript.dependencies.libraries[index].libraryId = '1fRodYEJFBC3jTabfNabvAUglQo2FxtkZLkQL7I4ulgi1OMZpJLJV8NBu'; 239 | fs.writeFileSync('./scripts/src/appsscript.json', JSON.stringify(appscript, null, ' ')); 240 | cb(code); 241 | }); 242 | }); 243 | 244 | gulp.task('start', function (done) { 245 | process.argv.forEach((arg) => { 246 | const match = /^-+(.+?)(=.+?)?$/.exec(arg); 247 | if (match) argv[match[1]] = match[2] ? match[2].slice(1) : undefined; 248 | }); 249 | console.log('Received parameters', argv); 250 | if (!argv.part) done('"part" arg is requeried'); 251 | const seriesList = ['clean', 'preparation-clasp-json', 'preparation-assets', 'pre-build']; 252 | if (Object.prototype.hasOwnProperty.call(argv, 'push-version')) 253 | seriesList.push('clasp', 'update-version', 'push-version'); 254 | 255 | if (Object.prototype.hasOwnProperty.call(argv, 'update-clients')) { 256 | seriesList.push( 257 | 'clean-clients', 258 | 'preparation-update-client-code', 259 | 'preparation-update-clients', 260 | 'update-clients-bulk', 261 | ); 262 | 263 | const updateClientsvSeries = () => gulp.series(seriesList); 264 | console.log('update-clients'); 265 | return updateClientsvSeries()(done); 266 | } 267 | 268 | if (!seriesList.includes('clasp')) seriesList.push('clasp'); 269 | const buildSeries = () => gulp.series(seriesList); 270 | return Object.prototype.hasOwnProperty.call(argv, 'watch_mode') 271 | ? gulp.watch( 272 | ['./src/**/*.{ts,js,gs,json,html}', './settings/**/*.{ts,js,gs,json,html}'], 273 | { delay: watchDelay }, 274 | buildSeries(), 275 | ) 276 | : buildSeries()(done); 277 | }); 278 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-google-apps-script-project", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "gulp start --part=prod --push-version --update-clients", 7 | "dev": "gulp start --part=dev --watch_mode", 8 | "prod": "gulp start --part=prod", 9 | "dev-version": "gulp start --part=dev --push-version", 10 | "pull-dev": "cp ./settings/dev/.clasp.json ./ && json -I -f .clasp.json -e \"this.rootDir='src'\" && clasp pull && rm -rf ./settings/dev/assets && mv ./src/_assets ./settings/dev/assets", 11 | "pull-prod": "cp ./settings/prod/.clasp.json ./ && json -I -f .clasp.json -e \"this.rootDir='src'\" && clasp pull && rm -rf ./settings/prod/assets && mv ./src/_assets ./settings/prod/assets", 12 | "lnsett": "cd ./settings && rm -f .eslintrc && ln -s ./../src/.eslintrc .eslintrc", 13 | "pretty": "prettier --write ." 14 | }, 15 | "type": "module", 16 | "keywords": [], 17 | "author": "", 18 | "license": "", 19 | "devDependencies": { 20 | "@google/clasp": "*", 21 | "@types/google-apps-script": "*", 22 | "clasp-types": "*", 23 | "del": "*", 24 | "eslint": "*", 25 | "eslint-config-google": "*", 26 | "eslint-config-prettier": "*", 27 | "eslint-plugin-googleappsscript": "*", 28 | "eslint-plugin-prettier": "*", 29 | "gulp": "*", 30 | "json": "*", 31 | "prettier": "*" 32 | }, 33 | "devSettings": { 34 | "watchDelay": 900 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /settings/dev/.clasp.json: -------------------------------------------------------------------------------- 1 | { "scriptId": "", "rootDir": "build" } 2 | -------------------------------------------------------------------------------- /settings/prod/.clasp.json: -------------------------------------------------------------------------------- 1 | { "scriptId": "", "rootDir": "build" } 2 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "google", "plugin:prettier/recommended"], 4 | "plugins": ["prettier", "googleappsscript"], 5 | "env": { 6 | "googleappsscript/googleappsscript": true, 7 | "es6": true 8 | }, 9 | "rules": { 10 | "prettier/prettier": "error", 11 | "no-var": "off", 12 | "valid-jsdoc": "off", 13 | "require-jsdoc": "off", 14 | "guard-for-in": "off" 15 | }, 16 | "parserOptions": { "ecmaVersion": "latest" } 17 | } 18 | -------------------------------------------------------------------------------- /src/Code.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hedgehogsburrows/empty-google-apps-script-project/bc0ae2fee97112bcc169176f54bc45f6cc8cf111/src/Code.js -------------------------------------------------------------------------------- /src/appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "Europe/Moscow", 3 | "dependencies": {}, 4 | "exceptionLogging": "STACKDRIVER", 5 | "runtimeVersion": "V8" 6 | } 7 | --------------------------------------------------------------------------------