├── .prettierignore ├── src ├── GasProject1 │ ├── .claspignore │ ├── Code │ │ ├── more-stuff.ts │ │ ├── Code.ts │ │ └── lost │ │ │ └── found.ts │ ├── .clasp.json │ ├── appsscript.json │ ├── tsconfig.json │ ├── index.ts │ └── README.md ├── GasProject2 │ ├── .claspignore │ ├── Code │ │ ├── more-stuff.ts │ │ ├── Code.ts │ │ └── lost │ │ │ └── found.ts │ ├── .clasp.json │ ├── appsscript.json │ ├── FileA.ts │ ├── Code.ts │ ├── tsconfig.json │ └── README.md ├── common-components │ ├── README.md │ └── lib1 │ │ ├── tsconfig.json │ │ ├── README.md │ │ ├── sub │ │ └── sub.ts │ │ └── index.ts ├── tsconfig.json ├── README.md ├── tsconfig-node.json └── tsconfig-gas.json ├── .gitignore ├── .editorconfig ├── TODOs.md ├── .prettierrc.json ├── LICENSE ├── README.md └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /src/GasProject1/.claspignore: -------------------------------------------------------------------------------- 1 | **/** 2 | !**/appsscript.json 3 | !**/*.js 4 | -------------------------------------------------------------------------------- /src/GasProject2/.claspignore: -------------------------------------------------------------------------------- 1 | **/** 2 | !**/appsscript.json 3 | !**/*.js 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #build 2 | node_modules 3 | 4 | *.tsbuildinfo 5 | 6 | \#*\# 7 | .\#* 8 | -------------------------------------------------------------------------------- /src/GasProject1/Code/more-stuff.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN more-stuff.ts */ 2 | 3 | function more() { 4 | return 'stuff'; 5 | } 6 | 7 | /** END more-stuff.ts */ 8 | -------------------------------------------------------------------------------- /src/GasProject2/Code/more-stuff.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN Code/more-stuff.ts */ 2 | 3 | function more() { 4 | return 'stuff'; 5 | } 6 | 7 | /** END Code/more-stuff.ts */ 8 | -------------------------------------------------------------------------------- /src/GasProject1/.clasp.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptId": "1TLCgakcLgxDMfRqwxwAobr6kg5ae9OgnOLV-MjIji-BhfTZP8vZ9ylDs", 3 | "rootDir": "../../build/gas/project1", 4 | "filePushOrder": [] 5 | } 6 | -------------------------------------------------------------------------------- /src/GasProject2/.clasp.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptId": "1oN_vpdtZHm3DKhulKTbNxIClqhRuL3J3wXxZtLEBBMkLve9-Ax_tiBjj", 3 | "rootDir": "../../build/gas/GasProject2", 4 | "filePushOrder": [] 5 | } 6 | -------------------------------------------------------------------------------- /src/GasProject1/appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "libraries": [] 4 | }, 5 | "exceptionLogging": "STACKDRIVER", 6 | "runtimeVersion": "V8", 7 | "timeZone": "Etc/GMT" 8 | } 9 | -------------------------------------------------------------------------------- /src/GasProject2/appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "libraries": [] 4 | }, 5 | "exceptionLogging": "STACKDRIVER", 6 | "runtimeVersion": "V8", 7 | "timeZone": "Etc/GMT" 8 | } 9 | -------------------------------------------------------------------------------- /src/common-components/README.md: -------------------------------------------------------------------------------- 1 | # The `common-components` folder 2 | 3 | This is where standalone are, reusable components are defined 4 | 5 | - `lib1` subfolder holds as sample component project 6 | -------------------------------------------------------------------------------- /src/GasProject1/Code/Code.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN Code.ts */ 2 | 3 | function main() { 4 | console.log('Code.ts: main()'); 5 | console.log(lib1.publicFunction()); 6 | } 7 | 8 | main(); 9 | 10 | /** END Code.ts */ 11 | -------------------------------------------------------------------------------- /src/GasProject2/Code/Code.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN Code/Code.ts */ 2 | 3 | function main() { 4 | console.log('Code.ts: main()'); 5 | console.log(lib1.publicFunction()); 6 | } 7 | 8 | main(); 9 | 10 | /** END Code/Code.ts */ 11 | -------------------------------------------------------------------------------- /src/GasProject2/FileA.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN FileA.ts */ 2 | 3 | /// 4 | 5 | if (lib1) { 6 | more(); 7 | 8 | lib1.publicFunction(); 9 | } else { 10 | lost(); 11 | } 12 | 13 | /** END FileA */ 14 | -------------------------------------------------------------------------------- /src/GasProject1/Code/lost/found.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN lost/found.ts */ 2 | 3 | // even though not referenced in index.ts, this file gets added to the output 4 | 5 | function lost() { 6 | return 'found'; 7 | } 8 | 9 | /** END lost/found.ts */ 10 | -------------------------------------------------------------------------------- /src/GasProject2/Code/lost/found.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN lost/found.ts */ 2 | 3 | // even though not referenced in index.ts, this file gets added to the output 4 | 5 | function lost() { 6 | return 'found'; 7 | } 8 | 9 | /** END lost/found.ts */ 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | end_of_line = lf 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /src/common-components/lib1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-gas", 3 | "compilerOptions": { 4 | "outFile": "../../../build/common-components/lib1/lib1.js" 5 | }, 6 | 7 | "references": [], 8 | 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /TODOs.md: -------------------------------------------------------------------------------- 1 | # TODOs 2 | 3 | - better approach to manage configuration files (.clasp.json, .claspignore, appscript.json) from src to build 4 | - requires new Clasp release 5 | - better approach to handle project2 and lib1 dependency in build 6 | - requires new Clasp release 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": false, 4 | "endOfLine": "lf", 5 | "printWidth": 120, 6 | "quoteProps": "as-needed", 7 | "semi": true, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "es5", 11 | "useTabs": false 12 | } 13 | -------------------------------------------------------------------------------- /src/GasProject2/Code.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN Code.ts */ 2 | 3 | /** 4 | * Here the triple slash directives are meaningless since the tsconfig.json does not define an `outFile` 5 | */ 6 | 7 | /// 8 | /// 9 | 10 | /** END Code.ts */ 11 | -------------------------------------------------------------------------------- /src/GasProject1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-gas", 3 | "compilerOptions": { 4 | "outFile": "../../build/gas/project1/Code.js" 5 | }, 6 | "files": ["index.ts"], 7 | "include": ["**/*.ts"], 8 | 9 | "references": [{ "path": "../common-components/lib1", "prepend": true }] 10 | } 11 | -------------------------------------------------------------------------------- /src/GasProject2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-gas", 3 | "compilerOptions": { 4 | "outDir": "../../build/gas" 5 | }, 6 | "include": [ 7 | "**/*.ts" 8 | ], 9 | "references": [ 10 | { 11 | "path": "../common-components/lib1", 12 | "prepend": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [ 5 | // Sample project 1 6 | { 7 | "path": "./GasProject1" 8 | }, 9 | // Sample project 2 10 | { 11 | "path": "./GasProject2" 12 | }, 13 | // Sample common component 14 | { 15 | "path": "./common-components/lib1" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/GasProject1/index.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN index.ts */ 2 | 3 | /** 4 | * Here the triple slash directives allow to specify order 5 | * in which files get added to the output 6 | */ 7 | 8 | /// 9 | /// 10 | 11 | // other files in tsconfig scope (`files` and `include`) will be added past this point 12 | 13 | /** END index.ts */ 14 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # The `src` folder 2 | 3 | - default settings for compiling for GAS `tsconfig-gas.json` 4 | - (also makes `google-apps-script` types available) 5 | - default build definition `tsconfig.json` with references to the GAS project and common components 6 | - `GasProject1` subfolder holds our first GAS project 7 | - `GasProject2` subfolder holds our second GAS project 8 | - `common-components` subfolder where reusable components are defined 9 | -------------------------------------------------------------------------------- /src/common-components/lib1/README.md: -------------------------------------------------------------------------------- 1 | # The `lib1` component 2 | 3 | A reusable component which can be added to any GAS project. 4 | 5 | Code isolation is defined through namespaces. Exposed parts (using `export`) are available with the `lib1.` property accessor: 6 | 7 | ```ts 8 | lib1.publicFunction(); 9 | ``` 10 | 11 | ## tsconfig.json 12 | 13 | - use the default compile options `src/tsconfig-gas.json` 14 | - includes every *.ts files as source (no specific file ordering) 15 | - output a single `build/common-components/lib1.js` 16 | -------------------------------------------------------------------------------- /src/GasProject1/README.md: -------------------------------------------------------------------------------- 1 | # The `GasProject1` folder 2 | 3 | A sample project using the `lib1` reusable component. It is setup to output a single *.js (including the `lib1` component) 4 | 5 | ## tsconfig.json 6 | 7 | - use the default compile options `src/tsconfig-gas.json` 8 | - reference `common-components/lib1` as available 9 | - declare that `common-components/lib1` is to be prepended 10 | - includes every *.ts files as source with `index.ts` first (though after any prepended reference) 11 | - output a single `build/gas/project1/Code.js` 12 | -------------------------------------------------------------------------------- /src/common-components/lib1/sub/sub.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN sub.ts */ 2 | 3 | /** 4 | * Content of this component lives in a namescape 5 | * 6 | * Its exported parts are accessed using the `lib1.` property accessor 7 | */ 8 | namespace lib1 { 9 | /** 10 | * This function is not exposed to outside the namespace. 11 | * It is visible only within this namespace block (i.e. namespace `lib1` in index.ts does not know about it) 12 | */ 13 | const subInnerFunction = () => 'innerFunction'; 14 | 15 | /** 16 | * This function is avaialble globally as `lib1.subPublicFunction()` 17 | */ 18 | export const subPublicFunction = () => `subPublicFunction called ${subInnerFunction()}`; 19 | } 20 | 21 | /** END sub.ts */ 22 | -------------------------------------------------------------------------------- /src/GasProject2/README.md: -------------------------------------------------------------------------------- 1 | # The `GasProject2` folder 2 | 3 | A sample project using the `lib1` reusable component. It is setup to output the project structure (instead of a single file like GasProject1). 4 | 5 | ## tsconfig.json 6 | 7 | - use the default compile options `src/tsconfig-gas.json` 8 | - reference `common-components/lib1` as available (but it will not be prepended) 9 | - includes every *.ts files as source (no specific file ordering) 10 | - output a single `build/gas/project1/Code.js` 11 | 12 | ## lib1 component dependancy 13 | 14 | The compiled lib1 component copied from build/common-components to build/gas/GasProject2 after a successfull `npm run build`* command (cf. the 'postbuild' script in package.json) 15 | -------------------------------------------------------------------------------- /src/common-components/lib1/index.ts: -------------------------------------------------------------------------------- 1 | /** BEGIN lib1/index.ts */ 2 | 3 | /** 4 | * Content of this component lives in a namescape 5 | * 6 | * Its exported parts are accessed using the `lib1.` property accessor 7 | */ 8 | namespace lib1 { 9 | /** 10 | * This function is not exposed to outside the namespace. 11 | * It is visible only within this namespace block (i.e. namespace `lib1` in sub/sub.ts does not know about it) 12 | */ 13 | const innerFunction = () => 'innerFunction'; 14 | 15 | /** 16 | * This function is avaialble globally as `lib1.publicFunction()` 17 | */ 18 | export const publicFunction = () => { 19 | // subInnerFunction is unreachable from here 20 | // console.log(`publicFunction called ${subInnerFunction()}`); 21 | 22 | console.log(`publicFunction called ${subPublicFunction()}`); 23 | 24 | return `publicFunction called ${innerFunction()}`; 25 | }; 26 | } 27 | 28 | /** END lib1/index.ts */ 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Guillaume Contesso a.k.a. PopGoesTheWza 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 | -------------------------------------------------------------------------------- /src/tsconfig-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "rootDir": ".", 5 | 6 | "lib": ["es2017"], 7 | // "module": "commonjs", 8 | "moduleResolution": "node", 9 | // "noLib": false, 10 | "target": "es2017", // fully supported with node ^8.10.0 (cf. https://node.green/) 11 | "types": [], 12 | 13 | "strict": true, 14 | // "alwaysStrict": true, 15 | // "noImplicitAny": true, 16 | // "noImplicitReturns": true, 17 | // "strictBindCallApply": true, 18 | // "strictNullChecks": true, 19 | // "strictFunctionTypes": true, 20 | // "strictPropertyInitialization": true, 21 | 22 | "noEmitOnError": true, 23 | // "noErrorTruncation": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noImplicitThis": true, 26 | "noUnusedLocals": true, 27 | "noUnusedParameters": true, 28 | // "preserveConstEnums": true, 29 | // "removeComments": true, 30 | // "skipLibCheck": true, 31 | "strictNullChecks": true, 32 | // "suppressExcessPropertyErrors": true, 33 | // "suppressImplicitAnyIndexErrors": true, 34 | 35 | "declaration": true, 36 | // "declarationDir": "../types", 37 | // "declarationMap": true, 38 | "newLine": "lf", 39 | "pretty": true, 40 | "sourceMap": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typescript - Google Apps Script project starter [![clasp](https://img.shields.io/badge/built%20with-clasp-4285f4.svg)](https://github.com/google/clasp) 2 | 3 | This repository aims at defining how one or more Google Apps Script (GAS) projects can be organized while satisfying the goals below. 4 | 5 | - Modern javascript features (Typescript) 6 | - Optional static and infered typing (Typescript) 7 | - Code isolation and reuse (by using Typescript `namespace` statements and project references) 8 | - Incremental builds (Typescript 3.4) 9 | - Fine control on how source `*.ts` files get compiled into target `*.js` files (i.e. overcome the `ts2gas` library limitations) 10 | - Code pretty printing and linting (Prettier and Tslint) 11 | 12 | ## Installation 13 | 14 | 1. clone this repository: `git clone https://github.com/PopGoesTheWza/ts-gas-project-starter.git` 15 | 1. install (globally) the **Clasp** CLI: `npm install --global @google/clasp` 16 | 1. install local dependencies: `npm install` 17 | 18 | ## NPM scripts 19 | 20 | Several commands are available as NPM scripts: `npm run `. The most commonly used are: 21 | 22 | - `build` and `build-clean` to compile all projects 23 | - `format` and `lint` to normalise code and check its correcteness 24 | - `push-all`, `push-project1` and `push-project2` to publish the GAS projects 25 | -------------------------------------------------------------------------------- /src/tsconfig-gas.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "incremental": true, 5 | 6 | // Enforce strict coding style 7 | "strict": true, 8 | "alwaysStrict": true, 9 | // "allowUnreachableCode": false, 10 | // "allowUnusedLabels": false, 11 | // "downlevelIteration": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitAny": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "strictBindCallApply": true, 20 | "strictNullChecks": true, 21 | "strictFunctionTypes": true, 22 | "strictPropertyInitialization": true, 23 | // "suppressExcessPropertyErrors": false, 24 | // "suppressImplicitAnyIndexErrors": false, 25 | 26 | // Enable latest Javascriptt syntax and target V8 engine 27 | "experimentalDecorators": true, 28 | "lib": ["ESNext"], 29 | "target": "ES2018", 30 | 31 | // Limit use of `export` statements 32 | "module": "None", 33 | "moduleResolution": "node", 34 | // "allowSyntheticDefaultImports": false, 35 | // "esModuleInterop": false, 36 | // "importHelpers": false, 37 | 38 | // Define files produced by the compiler 39 | "newLine": "lf", 40 | "noEmitOnError": true, 41 | // "declaration": false, 42 | // "declarationMap": false, 43 | // "noResolve": false, 44 | // "preserveConstEnums": false, 45 | // "removeComments": false, 46 | "rootDir": ".", 47 | // "sourceMap": false, 48 | // "stripInternal": false, 49 | 50 | // Make Google Apps Script type definitions ambient 51 | // "noLib": false, 52 | // "skipLibCheck": false, 53 | "types": ["google-apps-script"], 54 | // "typeRoots": ["src/types", "node_modules/@types"], 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "ts-gas-project-starter", 4 | "version": "1.1.0", 5 | "description": "Sample repository for Typescript based Google Apps Script projects", 6 | "author": "Guillaume Contesso a.k.a. PopGoesTheWza", 7 | "license": "MIT", 8 | "keywords": [ 9 | "gas", 10 | "google-apps-script", 11 | "ts", 12 | "typescript", 13 | "v8" 14 | ], 15 | "scripts": { 16 | "build-clean": "rimraf build && npm run config-all && npm run build && npm run postbuild", 17 | "build": "tsc --build src", 18 | "postbuild": "npm run postbuild-project2", 19 | "build-project1": "tsc --build src/GasProject1", 20 | "build-project2": "tsc --build src/GasProject2", 21 | "postbuild-project2": "copyfiles -u 3 build/common-components/lib1/lib1.js build/gas/GasProject2", 22 | "format": "prettier --write **.{ts,json}", 23 | "lint": "xo --quiet", 24 | "config-project1": "copyfiles -u 2 src/GasProject1/appsscript.json build/gas/project1", 25 | "config-project2": "copyfiles -u 2 src/GasProject2/appsscript.json build/gas/GasProject2", 26 | "config-all": "npm run config-project1 && npm run config-project2", 27 | "push-project1": "cd src/GasProject1 && clasp push", 28 | "push-project2": "cd src/GasProject2 && clasp push", 29 | "push-all": "npm run config-all && npm run push-project1 && npm run push-project2" 30 | }, 31 | "repository": "https://github.com/PopGoesTheWza/ts-gas-project-starter.git", 32 | "devDependencies": { 33 | "@types/google-apps-script": "^1.0.33", 34 | "copyfiles": "^2.4.1", 35 | "prettier": "^2.3.0", 36 | "rimraf": "^3.0.2", 37 | "typescript": "^4.2.4", 38 | "xo": "^0.39.1" 39 | }, 40 | "xo": { 41 | "ignores": [ 42 | "build", 43 | "**.js" 44 | ], 45 | "space": 2, 46 | "rules": { 47 | "capitalized-comments": "warn", 48 | "no-inner-declarations": "warn", 49 | "no-undef": "off", 50 | "@typescript-eslint/no-unsafe-argument": "warn", 51 | "@typescript-eslint/no-unused-vars": "off", 52 | "@typescript-eslint/restrict-template-expressions": [ 53 | "warn", 54 | { 55 | "allowNumber": true, 56 | "allowBoolean": false, 57 | "allowAny": false, 58 | "allowNullish": false 59 | } 60 | ], 61 | "@typescript-eslint/triple-slash-reference": "off", 62 | "unicorn/filename-case": "off" 63 | }, 64 | "prettier": true 65 | } 66 | } 67 | --------------------------------------------------------------------------------