diff --git a/docs/config-files.md b/docs/config-files.md new file mode 100644 index 00000000..d4e15dc4 --- /dev/null +++ b/docs/config-files.md @@ -0,0 +1,56 @@ +# Clasp configuration files + +A Clasp project uses the following configuration files: + +File | Default | Description +--- | --- | --- +Clasp project file | `./.clasp.json` | Define which Apps Script project to interact with (and how) +Clasp ignore file | `.claspignore` in the same directory as the Clasp project file | Specifies files to ignore by `push` and `watch` Clasp commands +Google Auth file | `~/.clasprc.json` (global) or `.clasprc.json` in the same directory as the Clasp project file (local) | OAuth 2.0 authentication and authorization to access Google APIs. Authentication is global by default. +Apps Script project manifest | `/appsscript.json` | Specifies basic project information (cf. [Manifests](https://developers.google.com/apps-script/concepts/manifests)) +Typescript configuration file | `/tsconfig.json` | Used for user specific compiling options. Limited support. + +## Environment variables + +Environment variables can be set in order to set the following default files: + +File | Environment varaiable | Comment +--- | --- | --- +Clasp project file | `clasp_config_project` | The filename must start with a dot '.' +Clasp ignore file | `clasp_config_ignore` | +Google Auth file | `clasp_config_auth` | The filename must start with a dot '.' + +## Command line options + +Command line options can be used in order to set the following default files: + +File | Environment varaiable | Comment +--- | --- | --- +Clasp project file | `-P ` or `--project ` | The filename must start with a dot '.' +Clasp ignore file | `-I ` or `--ignore ` | +Google Auth file | `-A ` or `--auth ` | The filename must start with a dot '.' + +> Note: command line options have precedence over environment variables + +## Usage of configuration files per command + +Command | Project file | ignore file | Auth file | Manifest file +--- | --- | --- | --- | --- +login | | | Write (`--creds` option) | +logout | | | Delete | +create | Write | | Read | Write +clone | Write | | Read | Write +pull | Read | | Read | Write +push | Read | Read | Read | Read +status | Read | Read | | Read +open | Read | | Read | ??? +deployments | Read | | Read | ??? +deploy | Read | | Read | Read +undeploy | Read | | Read | ??? +version | Read | | Read | ??? +versions | Read | | Read | ??? +list | | | Read | +logs | ??? | | Read | ??? +run | Read | | Read | ??? +apis | ??? | | Read | ??? +setting | Read | | ??? | ??? diff --git a/package-lock.json b/package-lock.json index 0fe92ab1..4efca528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -136,9 +136,9 @@ } }, "@types/chai": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", - "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-zw8UvoBEImn392tLjxoavuonblX/4Yb9ha4KBU10FirCfwgzhKO0dvyJSF9ByxV1xK1r2AgnAi/tvQaLgxQqxA==", "dev": true }, "@types/cli-spinner": { @@ -150,12 +150,6 @@ "@types/node": "*" } }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, "@types/fs-extra": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz", @@ -165,23 +159,6 @@ "@types/node": "*" } }, - "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz", - "integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==" - } - } - }, "@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -213,9 +190,9 @@ "dev": true }, "@types/node": { - "version": "10.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.14.tgz", - "integrity": "sha512-xXD08vZsvpv4xptQXj1+ky22f7ZoKu5ZNI/4l+/BXG3X+XaeZsmaFbbTKuhSE3NjjvRuZFxFf9sQBMXIcZNFMQ==", + "version": "10.14.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.16.tgz", + "integrity": "sha512-/opXIbfn0P+VLt+N8DE4l8Mn8rbhiJgabU96ZJ0p9mxOkIks5gh6RUnpHak7Yh0SFkyjO/ODbxsQQPV2bpMmyA==", "dev": true }, "@types/pluralize": { @@ -308,7 +285,8 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -570,11 +548,11 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, "cli-spinner": { @@ -646,9 +624,9 @@ } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.0.tgz", + "integrity": "sha512-pl3QrGOBa9RZaslQiqnnKX2J068wcQw7j9AIaBQ9/JEp5RY6je4jKTImg0Bd+rpoONSe7GUFSgkxLeo17m3Pow==" }, "commondir": { "version": "1.0.1", @@ -700,7 +678,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -1045,19 +1023,6 @@ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, - "fast-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz", - "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==", - "requires": { - "@nodelib/fs.stat": "^2.0.1", - "@nodelib/fs.walk": "^1.2.1", - "glob-parent": "^5.0.0", - "is-glob": "^4.0.1", - "merge2": "^1.2.3", - "micromatch": "^4.0.2" - } - }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -1342,32 +1307,17 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, - "globby": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", - "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - } - }, "google-auth-library": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.2.6.tgz", - "integrity": "sha512-oJ6tCA9rbsYeIVY+mcLPFHa2hatz3XO6idYIrlI/KhhlMxZrO3tKyU8O2Pxu5KnSBBP7Wj4HtbM1LLKngNFaFw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.2.0.tgz", + "integrity": "sha512-I2726rgOedQ06HgTvoNvBeRCzy5iFe6z3khwj6ugfRd1b0VHwnTYKl/3t2ytOTo7kKc6KivYIBsCIdZf2ep67g==", "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "fast-text-encoding": "^1.0.0", "gaxios": "^2.0.0", "gcp-metadata": "^2.0.0", - "gtoken": "^3.0.0", + "gtoken": "^4.0.0", "jws": "^3.1.5", "lru-cache": "^5.0.0" } @@ -1381,22 +1331,22 @@ } }, "googleapis": { - "version": "41.0.1", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-41.0.1.tgz", - "integrity": "sha512-4o3seDyEB4VPukCYwry0/S7Bz1Fz6TLTjjeUnYfssGjcWTpbR3w+4dyhPQ2Kf+0E6srQExi+0h68qqQNezuhlg==", + "version": "42.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-42.0.0.tgz", + "integrity": "sha512-nQiKPDmzmMusnU8UOibmlC6hsgkm70SjqmLxSlBBb7i0z7/J6UPilSzo9tAMoHA8u3BUw3OXn13+p9YLmBH6Gg==", "requires": { - "google-auth-library": "^4.0.0", - "googleapis-common": "^2.0.2" + "google-auth-library": "^5.1.0", + "googleapis-common": "^3.0.0" } }, "googleapis-common": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-2.0.4.tgz", - "integrity": "sha512-8RRkxr24v1jIKCC1onFWA8RGnwFV55m3Qpil9DLX1yLc9e5qvOJsRoDOhhD2e7jFRONYEhT/BzT8vJZANqSr9w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-3.1.0.tgz", + "integrity": "sha512-abnogPoWqv0cU6O/EFpkerCVsaUsKEG6XpCm4P1YgK44PxIRcGtalDwijqUErkcivM1Xy5MNyf1PDkKTLEjOZA==", "requires": { "extend": "^3.0.2", "gaxios": "^2.0.1", - "google-auth-library": "^4.2.5", + "google-auth-library": "^5.2.0", "qs": "^6.7.0", "url-template": "^2.0.8", "uuid": "^3.3.2" @@ -1432,9 +1382,9 @@ "dev": true }, "gtoken": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-3.0.2.tgz", - "integrity": "sha512-BOBi6Zz31JfxhSHRZBIDdbwIbOPyux10WxJHdx8wz/FMP1zyN1xFrsAWsgcLe5ww5v/OZu/MePUEZAjgJXSauA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.0.0.tgz", + "integrity": "sha512-XaRCfHJxhj06LmnWNBzVTAr85NfAErq0W1oabkdqwbq3uL/QTB1kyvGog361Uu2FMG/8e3115sIy/97Rnd4GjQ==", "requires": { "gaxios": "^2.0.0", "google-p12-pem": "^2.0.0", @@ -1612,23 +1562,66 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", + "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", + "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", + "requires": { + "type-fest": "^0.5.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "figures": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", + "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", + "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^5.2.0" + } + }, + "type-fest": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", + "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" + } } }, "inquirer-autocomplete-prompt": { @@ -1687,7 +1680,8 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "is-ip": { "version": "3.1.0", @@ -1708,27 +1702,6 @@ "public-ip": "^3.0.0" } }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "requires": { - "is-path-inside": "^2.1.0" - } - }, - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "requires": { - "path-is-inside": "^1.0.2" - } - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -2164,20 +2137,6 @@ } } }, - "merge2": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.4.tgz", - "integrity": "sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==" - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", @@ -2199,9 +2158,9 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mimic-response": { "version": "1.0.1", @@ -2218,12 +2177,12 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -2343,9 +2302,9 @@ } }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "neo-async": { "version": "2.6.1", @@ -2533,11 +2492,11 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" } }, "open": { @@ -2560,7 +2519,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { @@ -2690,15 +2649,6 @@ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" }, - "path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", - "requires": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2799,11 +2749,6 @@ "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", "dev": true }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -2842,9 +2787,9 @@ "dev": true }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.8.0.tgz", + "integrity": "sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w==" }, "read-pkg": { "version": "3.0.0", @@ -3050,11 +2995,11 @@ } }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, @@ -3245,6 +3190,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -3254,6 +3200,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -3414,9 +3361,9 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.19.0.tgz", + "integrity": "sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -3432,6 +3379,14 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" + }, + "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + } } }, "tsutils": { @@ -3485,6 +3440,13 @@ "source-map": "~0.6.1" }, "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3521,21 +3483,6 @@ "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -3573,7 +3520,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } diff --git a/package.json b/package.json index 54017d54..575b9ab6 100644 --- a/package.json +++ b/package.json @@ -58,23 +58,22 @@ "dependencies": { "chalk": "^2.4.2", "cli-spinner": "^0.2.10", - "commander": "^2.20.0", + "commander": "^3.0.0", "dotf": "^1.2.0", "ellipsize": "^0.1.0", "find-up": "^4.1.0", "fs-extra": "^8.1.0", "fuzzy": "^0.1.3", "gaxios": "^2.0.1", - "google-auth-library": "^4.2.5", - "googleapis": "^41.0.1", - "inquirer": "^6.5.0", + "google-auth-library": "^5.2.0", + "googleapis": "^42.0.0", + "inquirer": "^6.5.1", "inquirer-autocomplete-prompt": "1.0.1", "is-online": "^8.2.0", "mkdirp": "^0.5.1", "multimatch": "^4.0.0", "normalize-newline": "3.0.0", "open": "^6.4.0", - "path": "^0.12.7", "pluralize": "^8.0.0", "recursive-readdir": "^2.2.2", "split-lines": "^2.0.0", @@ -85,26 +84,25 @@ "watch": "^1.0.2" }, "devDependencies": { - "@types/chai": "^4.1.7", + "@types/chai": "^4.2.0", "@types/cli-spinner": "^0.2.0", - "@types/events": "^3.0.0", "@types/fs-extra": "^8.0.0", - "@types/inquirer": "^6.0.3", + "@types/inquirer": "^6.5.0", "@types/mkdirp": "^0.5.2", "@types/mocha": "^5.2.7", - "@types/node": "^10.14.13", + "@types/node": "^10.14.16", "@types/pluralize": "^0.0.29", "@types/recursive-readdir": "^2.2.0", "@types/tmp": "^0.1.0", "@types/watch": "^1.0.1", "axios": "^0.19.0", "chai": "^4.2.0", - "coveralls": "^3.0.5", + "coveralls": "^3.0.6", "mocha": "^6.2.0", "nyc": "^14.1.1", "prettier": "^1.18.2", "tmp": "^0.1.0", "ts-node": "^8.3.0", - "tslint": "^5.18.0" + "tslint": "^5.19.0" } } diff --git a/src/auth.ts b/src/auth.ts index 5a4e40f3..0406ed3d 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -9,7 +9,7 @@ import url from 'url'; import { Credentials, GenerateAuthUrlOpts, OAuth2Client, OAuth2ClientOptions } from 'google-auth-library'; import { google, script_v1 } from 'googleapis'; import open from 'open'; -import { ClaspToken, DOTFILE, Dotfile } from './dotfile'; +import { ClaspToken, DOTFILE } from './dotfile'; import { oauthScopesPrompt } from './inquirer'; import { readManifest } from './manifest'; import { ClaspCredentials, ERROR, LOG, checkIfOnline, getOAuthSettings, logError } from './utils'; @@ -159,9 +159,8 @@ export async function authorize(options: { // Save the token and own creds together. let claspToken: ClaspToken; - let dotfile: Dotfile; + // TODO: deprecate `--creds` option if (options.creds) { - dotfile = DOTFILE.RC_LOCAL(); // Save local ClaspCredentials. claspToken = { token, @@ -173,7 +172,6 @@ export async function authorize(options: { isLocalCreds: true, }; } else { - dotfile = DOTFILE.RC; // Save global ClaspCredentials. claspToken = { token, @@ -181,7 +179,7 @@ export async function authorize(options: { isLocalCreds: false, }; } - await dotfile.write(claspToken); + await DOTFILE.AUTH().write(claspToken); console.log(LOG.SAVED_CREDS(!!options.creds)); } catch (err) { logError(null, ERROR.ACCESS_TOKEN + err); @@ -318,7 +316,7 @@ async function setOauthClientCredentials(rc: ClaspToken) { await refreshCredentials(globalOAuth2Client); // Save the credentials. - await (rc.isLocalCreds ? DOTFILE.RC_LOCAL() : DOTFILE.RC).write(rc); + await DOTFILE.AUTH().write(rc); } catch (err) { logError(null, ERROR.ACCESS_TOKEN + err); } diff --git a/src/commands/apis.ts b/src/commands/apis.ts index 327802da..476d42cc 100644 --- a/src/commands/apis.ts +++ b/src/commands/apis.ts @@ -7,6 +7,7 @@ import { URL } from '../urls'; import { enableOrDisableAPI } from '../apiutils'; import open from 'open'; import { serviceusage_v1 } from 'googleapis'; +// TODO: drop padEnd polyfill with with NodeJs >= 8.2.1 const padEnd = require('string.prototype.padend'); /** diff --git a/src/commands/clone.ts b/src/commands/clone.ts index 9df0f599..fa07e9b2 100644 --- a/src/commands/clone.ts +++ b/src/commands/clone.ts @@ -1,11 +1,10 @@ -import { async } from 'rxjs/internal/scheduler/async'; import { drive, loadAPICredentials } from '../auth'; import { fetchProject, hasProject, writeProjectFiles } from '../files'; import { ScriptIdPrompt, scriptIdPrompt } from '../inquirer'; import { extractScriptId } from '../urls'; import { ERROR, LOG, checkIfOnline, logError, saveProject, spinner } from '../utils'; import status from './status'; - +// TODO: drop padEnd polyfill with with NodeJs >= 8.2.1 const padEnd = require('string.prototype.padend'); /** diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index 91fad75e..e2a7b247 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -1,13 +1,6 @@ import { loadAPICredentials, script } from '../auth'; -import { - ERROR, - LOG, - PROJECT_MANIFEST_BASENAME, - checkIfOnline, - getProjectSettings, - logError, - spinner, -} from '../utils'; +import { PROJECT_MANIFEST_BASENAME } from '../conf'; +import { ERROR, LOG, checkIfOnline, getProjectSettings, logError, spinner } from '../utils'; /** * Deploys an Apps Script project. diff --git a/src/commands/list.ts b/src/commands/list.ts index 998d157d..aefe42a2 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -9,6 +9,7 @@ interface EllipizeOptions { truncate?: boolean | 'middle'; } const ellipsize: (str?: string, max?: number, opts?: EllipizeOptions) => string = require('ellipsize'); +// TODO: drop padEnd polyfill with with NodeJs >= 8.2.1 const padEnd = require('string.prototype.padend'); /** diff --git a/src/commands/login.ts b/src/commands/login.ts index b3822d73..d089f202 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -7,7 +7,6 @@ import { authorize, getLoggedInEmail } from '../auth'; import { FS_OPTIONS } from '../files'; import { readManifest } from '../manifest'; import { ERROR, LOG, checkIfOnline, hasOauthClientSettings, safeIsOnline } from '../utils'; -import { google } from 'googleapis'; /** * Logs the user in. Saves the client credentials to an either local or global rc file. @@ -16,10 +15,10 @@ import { google } from 'googleapis'; * @param {string?} options.creds The location of credentials file. * @param {boolean?} options.status If true, prints who is logged in instead of doing login. */ -export default async (options: { localhost?: boolean; creds?: string, status?: boolean }) => { +export default async (options: { localhost?: boolean; creds?: string; status?: boolean }) => { if (options.status) { if (hasOauthClientSettings()) { - const email = (await safeIsOnline()) ? (await getLoggedInEmail()) : undefined; + const email = (await safeIsOnline()) ? await getLoggedInEmail() : undefined; if (!!email) { console.log(LOG.LOGGED_IN_AS(email)); @@ -77,22 +76,22 @@ export default async (options: { localhost?: boolean; creds?: string, status?: b await authorize({ useLocalhost, scopes: [ - // Use the default scopes needed for clasp. - 'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments - 'https://www.googleapis.com/auth/script.projects', // Apps Script management - 'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps - 'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata - 'https://www.googleapis.com/auth/drive.file', // Create Drive files - 'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API - 'https://www.googleapis.com/auth/logging.read', // StackDriver logs - 'https://www.googleapis.com/auth/userinfo.email', // User email address - 'https://www.googleapis.com/auth/userinfo.profile', + // Use the default scopes needed for clasp. + 'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments + 'https://www.googleapis.com/auth/script.projects', // Apps Script management + 'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps + 'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata + 'https://www.googleapis.com/auth/drive.file', // Create Drive files + 'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API + 'https://www.googleapis.com/auth/logging.read', // StackDriver logs + 'https://www.googleapis.com/auth/userinfo.email', // User email address + 'https://www.googleapis.com/auth/userinfo.profile', - // Extra scope since service.management doesn't work alone - 'https://www.googleapis.com/auth/cloud-platform', - ], - }); - } - process.exit(0); // gracefully exit after successful login + // Extra scope since service.management doesn't work alone + 'https://www.googleapis.com/auth/cloud-platform', + ], + }); + } + process.exit(0); // gracefully exit after successful login } }; diff --git a/src/commands/logout.ts b/src/commands/logout.ts index 7ff546d7..e148eb4c 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -1,11 +1,39 @@ -import { DOT } from '../dotfile'; -import fs from 'fs-extra'; +import { Conf } from '../conf'; +import { DOTFILE } from '../dotfile'; import { hasOauthClientSettings } from '../utils'; +const auth = Conf.get().auth; + /** * Logs out the user by deleting credentials. */ -export default async () => { - if (hasOauthClientSettings(true)) fs.unlinkSync(DOT.RC.ABSOLUTE_LOCAL_PATH); - if (hasOauthClientSettings()) fs.unlinkSync(DOT.RC.ABSOLUTE_PATH); +export default async () => { let previousPath: string | undefined = undefined; + if (hasOauthClientSettings(true)) { + if (auth.isDefault()) { + // if no local auth defined, try current directory + previousPath = auth.path; + auth.path = '.'; + } + + await DOTFILE.AUTH().delete(); + + if (previousPath) { + auth.path = previousPath; + } + } + + if (hasOauthClientSettings()) { + if (!auth.isDefault()) { + // if local auth defined, try with default (global) + previousPath = auth.path; + auth.path = ''; + } + + await DOTFILE.AUTH().delete(); + + if (previousPath) { + auth.path = previousPath; + } + } + }; diff --git a/src/commands/logs.ts b/src/commands/logs.ts index 21a85db7..793e761d 100644 --- a/src/commands/logs.ts +++ b/src/commands/logs.ts @@ -7,7 +7,7 @@ import { DOTFILE, ProjectSettings } from '../dotfile'; import { projectIdPrompt } from '../inquirer'; import { URL } from '../urls'; import { ERROR, LOG, checkIfOnline, getProjectSettings, isValidProjectId, logError, spinner } from '../utils'; - +// TODO: drop padEnd polyfill with with NodeJs >= 8.2.1 const padEnd = require('string.prototype.padend'); /** diff --git a/src/commands/openCmd.ts b/src/commands/openCmd.ts index 9f710286..d36c0930 100644 --- a/src/commands/openCmd.ts +++ b/src/commands/openCmd.ts @@ -2,7 +2,7 @@ import open from 'open'; import { loadAPICredentials, script } from '../auth'; import { deploymentIdPrompt } from '../inquirer'; import { URL } from '../urls'; -import { ERROR, LOG, checkIfOnline, getProjectSettings, getWebApplicationURL, logError } from '../utils'; +import { ERROR, LOG, getProjectSettings, getWebApplicationURL, logError } from '../utils'; interface EllipizeOptions { ellipse?: string; @@ -10,6 +10,7 @@ interface EllipizeOptions { truncate?: boolean | 'middle'; } const ellipsize: (str?: string, max?: number, opts?: EllipizeOptions) => string = require('ellipsize'); +// TODO: drop padEnd polyfill with with NodeJs >= 8.2.1 const padEnd = require('string.prototype.padend'); /** diff --git a/src/commands/push.ts b/src/commands/push.ts index 0eee02a9..532ff412 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -3,18 +3,14 @@ import { readFileSync } from 'fs-extra'; import multimatch from 'multimatch'; import { watchTree } from 'watch'; import { loadAPICredentials } from '../auth'; -import { DOT, DOTFILE } from '../dotfile'; +import { Conf, PROJECT_MANIFEST_BASENAME, PROJECT_MANIFEST_FILENAME } from '../conf'; +import { DOTFILE } from '../dotfile'; import { FS_OPTIONS, fetchProject, pushFiles } from '../files'; import { overwritePrompt } from '../inquirer'; import { isValidManifest } from '../manifest'; -import { - LOG, - PROJECT_MANIFEST_BASENAME, - PROJECT_MANIFEST_FILENAME, - checkIfOnline, - getProjectSettings, - spinner, -} from '../utils'; +import { LOG, checkIfOnline, getProjectSettings, spinner } from '../utils'; + +const project = Conf.get().project; const normalizeNewline = require('normalize-newline'); @@ -77,7 +73,7 @@ const confirmManifestUpdate = async (): Promise => { */ const manifestHasChanges = async (): Promise => { const { scriptId, rootDir } = await getProjectSettings(); - const localManifestPath = path.join(rootDir || DOT.PROJECT.DIR, PROJECT_MANIFEST_FILENAME); + const localManifestPath = path.join(rootDir || project.resolvedDir, PROJECT_MANIFEST_FILENAME); const localManifest = readFileSync(localManifestPath, FS_OPTIONS); const remoteFiles = await fetchProject(scriptId, undefined, true); const remoteManifest = remoteFiles.find(file => file.name === PROJECT_MANIFEST_BASENAME); diff --git a/src/commands/run.ts b/src/commands/run.ts index 33a6b74c..8f068dfc 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -50,6 +50,7 @@ export default async (functionName: string, cmd: { nondev: boolean; params: stri */ async function runFunction(functionName: string, params: string[], scriptId: string, devMode: boolean) { try { + // TODO: @grant what are the issues/risks of not using local auth? // Load local credentials. await loadAPICredentials(true); const localScript = await getLocalScript(); diff --git a/src/conf.ts b/src/conf.ts new file mode 100644 index 00000000..d1d65199 --- /dev/null +++ b/src/conf.ts @@ -0,0 +1,128 @@ +import os from 'os'; +import path from 'path'; +import { PathProxy } from './pathproxy'; + +/** + * supported environment variables + */ +enum ENV { + DOT_CLASP_AUTH = 'clasp_config_auth', + DOT_CLASP_IGNORE = 'clasp_config_ignore', + DOT_CLASP_PROJECT = 'clasp_config_project', + // MANIFEST = 'clasp_config_manifest', + // TSCONFIG = 'clasp_config_tsconfig', +} + +// Names / Paths +/** Constant `clasp` */ +export const PROJECT_NAME = 'clasp'; +/** Constant `appsscript` */ +export const PROJECT_MANIFEST_BASENAME = 'appsscript'; +/** Constant `appsscript.json` */ +export const PROJECT_MANIFEST_FILENAME = `${PROJECT_MANIFEST_BASENAME}.json`; + +/** + * A Singleton class to hold configuration related objects. + * Use the `get()` method to access the unique singleton instance. + */ +export class Conf { + private static _instance: Conf; + /** + * This dotfile saves clasp project information, local to project directory. + */ + readonly project: PathProxy; + /** + * This dotfile stores information about ignoring files on `push`. Like .gitignore. + */ + readonly ignore: IgnoreFile; + /** + * This dotfile saves auth information. Should never be committed. + * There are 2 types: personal & global: + * - Global: In the $HOME directory. + * - Personal: In the local directory. + * @see {ClaspToken} + */ + readonly auth: AuthFile; + // readonly manifest: PathProxy; + + /** + * Private to prevent direct construction calls with the `new` operator. + */ + private constructor() { + + /** + * Helper to set the PathProxy path if an environment variables is set. + * + * *Note: Empty values (i.e. '') are not accounted for.* + */ + const setPathWithEnvVar = (varName: string, file: PathProxy) => { + const envVar = process.env[varName]; + if (envVar) { + file.path = envVar; + } + }; + + // default `project` path is `./.clasp.json` + this.project = new PathProxy({ dir: '.', base: `.${PROJECT_NAME}.json` }); + + // default `ignore` path is `~/.claspignore` + // IgnoreFile class implements custom `.resolve()` rules + this.ignore = new IgnoreFile({ dir: os.homedir(), base: `.${PROJECT_NAME}ignore` }); + + // default `auth` path is `~/.clasprc.json` + // Default Auth is global. Any other implies local auth + this.auth = new AuthFile({ dir: os.homedir(), base: `.${PROJECT_NAME}rc.json` }); + + // resolve environment variables + setPathWithEnvVar(ENV.DOT_CLASP_PROJECT, this.project); + setPathWithEnvVar(ENV.DOT_CLASP_IGNORE, this.ignore); + setPathWithEnvVar(ENV.DOT_CLASP_AUTH, this.auth); + } + + /** + * The static method that controls the access to the Conf singleton instance. + * + * @returns {Conf} + */ + static get(): Conf { + if (!Conf._instance) { + Conf._instance = new Conf(); + } + + return Conf._instance; + } +} + +class AuthFile extends PathProxy { + /** + * Rules to resolves path: + * + * - if default path, use as is + * - otherwise use super.resolve() + * + * @returns {string} + */ + resolve(): string { + return this.isDefault() + ? path.join(this._default.dir, this._default.base) + : super.resolve(); + } +} + +class IgnoreFile extends PathProxy { + /** + * Rules to resolves path: + * + * - if default, use the **project** directory and the default base filename + * - otherwise use super.resolve() + * + * @returns {string} + */ + resolve(): string { + return this.isDefault() + ? path.join(Conf.get().project.resolvedDir, this._default.base) + : super.resolve(); + } +} + +// TODO: add more subclasses if necessary diff --git a/src/dotfile.ts b/src/dotfile.ts index a37c8019..cb7d6655 100644 --- a/src/dotfile.ts +++ b/src/dotfile.ts @@ -12,13 +12,17 @@ * This should be the only file that uses DOTFILE. */ -import os from 'os'; import path from 'path'; -import findUp from 'find-up'; import fs from 'fs-extra'; import { Credentials } from 'google-auth-library'; import { OAuth2ClientOptions } from 'google-auth-library/build/src/auth/oauth2client'; -import stripBom = require('strip-bom'); +import stripBom from 'strip-bom'; +import { Conf } from './conf'; + +const conf = Conf.get(); +const auth = conf.auth; +const ignore = conf.ignore; +const project = conf.project; // Getting ready to switch to `dotf` embedded types // import { default as dotf } from 'dotf'; @@ -37,10 +41,6 @@ const dotf: Dotf = require('dotf'); const splitLines: (str: string, options?: { preserveNewLines?: boolean }) => string[] = require('split-lines'); -// TEMP CIRCULAR DEPS, TODO REMOVE -// import { PROJECT_NAME } from './utils'; -const PROJECT_NAME = 'clasp'; - // TODO: workaround the circular dependency with `files.ts` // @see https://nodejs.org/api/fs.html#fs_fs_readfilesync_path_options const FS_OPTIONS = { encoding: 'utf8' }; @@ -53,57 +53,21 @@ export interface ProjectSettings { fileExtension?: string; filePushOrder?: string[]; } + // Dotfile names -export const DOT = { - /** - * This dotfile stores information about ignoring files on `push`. Like .gitignore. - */ - IGNORE: { - DIR: '~', - NAME: `${PROJECT_NAME}ignore`, - PATH: `.${PROJECT_NAME}ignore`, - }, - /** - * This dotfile saves clasp project information, local to project directory. - */ - PROJECT: { - DIR: path.join('.', '/'), // Relative to where the command is run. See DOTFILE.PROJECT() - NAME: `${PROJECT_NAME}.json`, - PATH: `.${PROJECT_NAME}.json`, - }, - /** - * This dotfile saves auth information. Should never be committed. - * There are 2 types: personal & global: - * - Global: In the $HOME directory. - * - Personal: In the local directory. - * @see {ClaspToken} - */ - RC: { - DIR: '~', - LOCAL_DIR: './', - NAME: `${PROJECT_NAME}rc.json`, - LOCAL_PATH: `.${PROJECT_NAME}rc.json`, - PATH: path.join('~', `.${PROJECT_NAME}rc.json`), - ABSOLUTE_PATH: path.join(os.homedir(), `.${PROJECT_NAME}rc.json`), - ABSOLUTE_LOCAL_PATH: path.join('.', `.${PROJECT_NAME}rc.json`), - }, -}; +// @grant the `DOT` object has been replaced with the `Conf` singleton // Methods for retrieving dotfiles. export const DOTFILE = { /** - * Reads DOT.IGNORE.PATH to get a glob pattern of ignored paths. + * Reads ignore.resolve() to get a glob pattern of ignored paths. * @return {Promise} A list of file glob patterns */ IGNORE: () => { - const projectPath = findUp.sync(DOT.PROJECT.PATH); - const ignoreDirectory = path.join(projectPath ? path.dirname(projectPath) : DOT.PROJECT.DIR); + const ignorePath = ignore.resolve(); return new Promise((resolve, reject) => { - if ( - fs.existsSync(ignoreDirectory) - && fs.existsSync(DOT.IGNORE.PATH) - ) { - const buffer = stripBom(fs.readFileSync(DOT.IGNORE.PATH, FS_OPTIONS)); + if (fs.existsSync(ignorePath)) { + const buffer = stripBom(fs.readFileSync(ignorePath, FS_OPTIONS)); resolve(splitLines(buffer).filter((name: string) => name)); } else { resolve(['**/**', '!appsscript.json', '!*.gs', '!*.js', '!*.ts', '!*.html']); @@ -116,15 +80,21 @@ export const DOTFILE = { * @return {Dotf} A dotf with that dotfile. Null if there is no file */ PROJECT: () => { - const projectPath = findUp.sync(DOT.PROJECT.PATH); - return dotf(projectPath ? path.dirname(projectPath) : DOT.PROJECT.DIR, DOT.PROJECT.NAME); + // ! TODO: currently limited if filename doesn't start with a dot '.' + const { dir, base } = path.parse(project.resolve()); + if (base[0] === '.') { + return dotf(dir || '.', base.slice(1)); + } + throw new Error('Project file must start with a dot (i.e. .clasp.json)'); }, // Stores {ClaspCredentials} - RC: dotf(DOT.RC.DIR, DOT.RC.NAME), - // Stores {ClaspCredentials} - RC_LOCAL: () => { - const localPath = findUp.sync(DOT.PROJECT.PATH); - return dotf(localPath ? path.dirname(localPath) : DOT.RC.LOCAL_DIR, DOT.RC.NAME); + AUTH: () => { + // ! TODO: currently limited if filename doesn't start with a dot '.' + const { dir, base } = path.parse(auth.resolve()); + if (base[0] === '.') { + return dotf(dir || '.', base.slice(1)); + } + throw new Error('Auth file must start with a dot (i.e. .clasp.json)'); }, }; diff --git a/src/files.ts b/src/files.ts index 6b57e5b7..1fcaefc1 100644 --- a/src/files.ts +++ b/src/files.ts @@ -1,18 +1,16 @@ import path from 'path'; -import findUp from 'find-up'; import fs from 'fs-extra'; import mkdirp from 'mkdirp'; import multimatch from 'multimatch'; import recursive from 'recursive-readdir'; -import { async } from 'rxjs/internal/scheduler/async'; import ts2gas from 'ts2gas'; import ts from 'typescript'; import { loadAPICredentials, script } from './auth'; -import { DOT, DOTFILE } from './dotfile'; +import { Conf, PROJECT_MANIFEST_FILENAME } from './conf'; +import { DOTFILE } from './dotfile'; import { ERROR, LOG, - PROJECT_MANIFEST_FILENAME, checkIfOnline, getAPIFileType, getProjectSettings, @@ -20,6 +18,8 @@ import { spinner, } from './utils'; +const project = Conf.get().project; + // @see https://nodejs.org/api/fs.html#fs_fs_readfilesync_path_options export const FS_OPTIONS = { encoding: 'utf8' }; @@ -49,7 +49,7 @@ export function getFileType(type: string, fileExtension?: string): string { * @returns {boolean} If .clasp.json exists. */ export function hasProject(): boolean { - return fs.existsSync(DOT.PROJECT.PATH); + return fs.existsSync(project.resolve()); } /** @@ -58,8 +58,8 @@ export function hasProject(): boolean { */ // TODO: unnecessary export export function getTranspileOptions(): ts.TranspileOptions { - const projectPath = findUp.sync(DOT.PROJECT.PATH); - const tsconfigPath = path.join(projectPath ? path.dirname(projectPath) : DOT.PROJECT.DIR, 'tsconfig.json'); + const projectPath = project.resolvedDir; + const tsconfigPath = path.join(projectPath, 'tsconfig.json'); if(fs.existsSync(tsconfigPath)) { const tsconfigContent = fs.readFileSync(tsconfigPath, FS_OPTIONS); const parsedConfigResult = ts.parseConfigFileTextToJson(tsconfigPath, tsconfigContent); @@ -121,7 +121,7 @@ export async function getProjectFiles(rootDir: string = path.join('.', '/'), cal // Can't rename, conflicting files abortPush = true; // only print error once (for .gs) - if (path.extname(name) === '.gs') { + if (path.extname(name) === '.gs') { logError(null, ERROR.CONFLICTING_FILE_EXTENSION(fileNameWithoutExt)); } } diff --git a/src/index.ts b/src/index.ts index c94f5b9e..495c33b7 100755 --- a/src/index.ts +++ b/src/index.ts @@ -21,8 +21,6 @@ * clasp – The Apps Script CLI */ -import { PROJECT_NAME, handleError } from './utils'; - import apis from './commands/apis'; import clone from './commands/clone'; import commander from 'commander'; @@ -44,6 +42,11 @@ import status from './commands/status'; import undeploy from './commands/undeploy'; import version from './commands/version'; import versions from './commands/versions'; +import { Conf, PROJECT_NAME } from './conf'; +import { handleError } from './utils'; + +// instantiate the config singleton (and loads environment variables as a side effect) +const config = Conf.get(); // CLI @@ -55,6 +58,46 @@ commander .usage(` [options]`) .description(`${PROJECT_NAME} - The Apps Script CLI`); +/** + * Path to an auth file, or to a folder with a '.clasprc.json' file. + */ +commander.option( + '-A, --auth ', + `path to an auth file or a folder with a '.clasprc.json' file.`, +).on('option:auth', () => { + config.auth.path = commander['auth']; +}); + +/** + * Path to an ignore file, or to a folder with a '.claspignore'. + */ +commander.option( + '-I, --ignore ', + `path to an ignore file or a folder with a '.claspignore' file.`, +).on('option:ignore', () => { + config.ignore.path = commander['ignore']; +}); + +/** + * Path to an manifest file, or to a folder with a 'appsscript.json' file. + */ +// commander.option( +// '-M, --manifest ', +// `path to an manifest file or a folder with a 'appsscript.json' file.`, +// ).on('option:manifest', () => { +// config.manifest.path = commander['manifest']; +// }); + +/** + * Path to a project file, or to a folder with a '.clasp.json'. + */ +commander.option( + '-P, --project ', + `path to a project file or to a folder with a '.clasp.json' file.`, +).on('option:project', () => { + config.project.path = commander['project']; +}); + /** * Logs the user in. Saves the client credentials to an rc file. * @name login @@ -343,7 +386,7 @@ commander * @example random */ commander - .command('*', undefined, { isDefault: true }) + .command('*', { isDefault: true }) .description('Any other command is not supported') .action(handleError(defaultCmd)); @@ -354,6 +397,23 @@ commander.option('-v, --version').on('option:version', () => { console.log(require('../package.json').version); }); +/** + * TEMPORARY FOR DEBUG + * Displays clasp version + */ +commander + .command('paths') + .description('List current config files path') + .action(() => { + const conf = Conf.get(); + const project = conf.project; + const ignore = conf.ignore; + const auth = conf.auth; + console.log('project', project.path, project.isDefault(), project.resolve()); + console.log('ignore', ignore.path, ignore.isDefault(), ignore.resolve()); + console.log('auth', auth.path, auth.isDefault(), auth.resolve()); + }); + // defaults to help if commands are not provided if (!process.argv.slice(2).length) { commander.outputHelp(); diff --git a/src/manifest.ts b/src/manifest.ts index 25d69b52..b93a1885 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -2,15 +2,20 @@ import path from 'path'; import fs from 'fs-extra'; import { PUBLIC_ADVANCED_SERVICES } from './apis'; import { enableOrDisableAPI, isEnabled } from './apiutils'; -import { DOT } from './dotfile'; +import { Conf, PROJECT_MANIFEST_FILENAME } from './conf'; import { FS_OPTIONS } from './files'; -import { ERROR, PROJECT_MANIFEST_FILENAME, getProjectSettings, getValidJSON, logError } from './utils'; +import { ERROR, getProjectSettings, getValidJSON, logError } from './utils'; + +const project = Conf.get().project; /** * Checks if the rootDir appears to be a valid project. + * + * @param {string} rootDir dir to check. + * * @return {boolean} True if valid project, false otherwise */ -export const manifestExists = (rootDir: string = DOT.PROJECT.DIR): boolean => +export const manifestExists = (rootDir: string = project.resolvedDir): boolean => fs.existsSync(path.join(rootDir, PROJECT_MANIFEST_FILENAME)); /** @@ -20,7 +25,7 @@ export const manifestExists = (rootDir: string = DOT.PROJECT.DIR): boolean => */ export async function readManifest(): Promise { let { rootDir } = await getProjectSettings(); - if (typeof rootDir === 'undefined') rootDir = DOT.PROJECT.DIR; + if (typeof rootDir === 'undefined') rootDir = project.resolvedDir; const manifest = path.join(rootDir, PROJECT_MANIFEST_FILENAME); try { return fs.readJsonSync(manifest, FS_OPTIONS); @@ -37,7 +42,7 @@ export async function readManifest(): Promise { // TODO: unnecessary export export async function writeManifest(manifest: Manifest) { let { rootDir } = await getProjectSettings(); - if (typeof rootDir === 'undefined') rootDir = DOT.PROJECT.DIR; + if (typeof rootDir === 'undefined') rootDir = project.resolvedDir; const manifestFilePath = path.join(rootDir, PROJECT_MANIFEST_FILENAME); try { fs.writeJsonSync(manifestFilePath, manifest, { encoding: 'utf8', spaces: 2 }); @@ -78,7 +83,7 @@ export async function isValidRunManifest(): Promise { */ export async function getManifest(): Promise { let { rootDir } = await getProjectSettings(); - if (typeof rootDir === 'undefined') rootDir = DOT.PROJECT.DIR; + if (typeof rootDir === 'undefined') rootDir = project.resolvedDir; const manifestString = fs.readFileSync(path.join(rootDir, PROJECT_MANIFEST_FILENAME), FS_OPTIONS); return getValidJSON(manifestString); } diff --git a/src/pathproxy.ts b/src/pathproxy.ts new file mode 100644 index 00000000..d4903c49 --- /dev/null +++ b/src/pathproxy.ts @@ -0,0 +1,126 @@ +import path from 'path'; +import fs from 'fs-extra'; + +/** A path broken down into a `dir`ectory and a `base` filename */ +export type PathParts = Pick; + +export class PathProxy { + protected _default: PathParts; + protected _userDefined: string | undefined; + + /** + * Handles a path to a file. + * + * - Constructor requires a default path (directory and filename) + * - Path can be overridden with the `path` accessor. + * - The `resolve()` method implements specific rules to define the effective path to the proxied file. + * + * @param {PathParts} defaultPath default path + */ + constructor(defaultPath: PathParts) { + this._default = defaultPath; + } + + /** + * Returns the current (raw and unresolved) defined path to the proxied file. + * + * *Note: for most uses, prefer the `resolve()` method in order to retreive a file's path* + * + * @returns {string} + */ + get path(): string { + return this._userDefined || path.join(this._default.dir, this._default.base); + } + + /** + * Sets the current (raw and unresolved) path to the proxied file. + * + * *Note: passing an empty string restores the default path* + */ + set path(userDefined: string ) { + this._userDefined = userDefined === path.join(this._default.dir, this._default.base) + ? undefined + : userDefined; + } + + /** + * Returns true if current path is the default. + * + * @returns {boolean} + */ + isDefault(): boolean { + return !this._userDefined + || this._userDefined === path.join(this._default.dir, this._default.base); + } + + /** + * Returns the resolved directory to the proxied file. + * + * *Note: for most uses, prefer the `.resolve()` method in order to retreive a file's path* + * + * @returns {string} + */ + get resolvedDir(): string { + return path.dirname(this.resolve()); + } + + /** + * Resolves the current active path + * + * @returns {string} + */ + resolve(): string { + return this._userDefined + ? resolvePath(this._userDefined, this._default.base) + : path.join(this._default.dir, this._default.base); + } +} + +/** + * Attempts to resolve a path with the following rules: + * + * - if path exists and points to a file: use it as is + * - if path exists and points to a directory: append the default base filename to the path + * - if path partially resolves to an existing directory but base filename does not exists: use it as is + * - otherwise throw an error + * + * @param {string} pathToResolve the path to resolve + * @param {string} baseFilename the default base filename + * + * @returns {string} + */ +function resolvePath(pathToResolve: string, baseFilename: string) { + if (fs.existsSync(pathToResolve)) { + return appendBaseIfDirectory(pathToResolve, baseFilename); + } + const parsedPath = path.parse(pathToResolve); + if (parsedPath.dir === '' || fs.lstatSync(parsedPath.dir).isDirectory()) { + return pathToResolve; // assume fullpath to missing file + } + // TODO: improve support for unresolved paths + throw new Error(`Unrecognized path ${pathToResolve}`); +} + +/** + * Attempts to resolve an **existing** path using the following rules: + * + * - if path exists and points to a file: use it as is + * - if path exists and points to a directory: append the default base filename to the path + * - otherwise throw an error + * + * @param {string} somePath the path to resolve + * @param {string} baseFilename the default base filename + * + * @returns {string} + */ +function appendBaseIfDirectory(somePath: string, baseFilename: string ): string { + const stats = fs.lstatSync(somePath); + if (stats.isFile()) { + return somePath; + } + if (stats.isDirectory()) { + return path.join(somePath, baseFilename); + } + // TODO: improve support for other stats types (stats.isSymbolicLink() ? ) + throw new Error(`Unrecognized path ${somePath}`); +} diff --git a/src/utils.ts b/src/utils.ts index 015880eb..79d6d310 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,18 +4,19 @@ import { Spinner } from 'cli-spinner'; import fs from 'fs-extra'; import { script_v1 } from 'googleapis'; import pluralize from 'pluralize'; -import { ClaspToken, DOT, DOTFILE, ProjectSettings } from './dotfile'; +import { Conf, PROJECT_MANIFEST_FILENAME, PROJECT_NAME } from './conf'; +import { ClaspToken, DOTFILE, ProjectSettings } from './dotfile'; import { projectIdPrompt } from './inquirer'; import { URL } from './urls'; +const conf = Conf.get(); +const auth = conf.auth; +const ignore = conf.ignore; +const project = conf.project; + const ucfirst = (str: string) => str && `${str[0].toUpperCase()}${str.slice(1)}`; const isOnline: (options?: { timeout?: number; version?: 'v4'|'v6'; }) => boolean = require('is-online'); -// Names / Paths -export const PROJECT_NAME = 'clasp'; -export const PROJECT_MANIFEST_BASENAME = 'appsscript'; -export const PROJECT_MANIFEST_FILENAME = PROJECT_MANIFEST_BASENAME + '.json'; - /** * The installed credentials. This is a file downloaded from console.developers.google.com * Credentials > OAuth 2.0 client IDs > Type:Other > Download @@ -40,8 +41,26 @@ export interface ClaspCredentials { * @param {boolean} local check ./clasprc.json instead of ~/.clasprc.json * @return {boolean} */ -export const hasOauthClientSettings = (local = false): boolean => - local ? fs.existsSync(DOT.RC.ABSOLUTE_LOCAL_PATH) : fs.existsSync(DOT.RC.ABSOLUTE_PATH); +// export const hasOauthClientSettings = (local = false): boolean => +// local ? fs.existsSync(DOT.RC.ABSOLUTE_LOCAL_PATH) : fs.existsSync(DOT.RC.ABSOLUTE_PATH); +export const hasOauthClientSettings = (local = false): boolean => { + let result: boolean; + let previousPath: string | undefined = undefined; + + if (local && auth.isDefault()) { + // if no local auth defined, try current directory + previousPath = auth.path; + auth.path = '.'; + } + + result = (local ? !auth.isDefault() : auth.isDefault()) && fs.existsSync(auth.resolve()); + + if (previousPath) { + auth.path = previousPath; + } + + return result; +}; /** * Gets the OAuth client settings from rc file. @@ -50,10 +69,24 @@ export const hasOauthClientSettings = (local = false): boolean => * @returns {Promise} A promise to get the rc file as object. */ export function getOAuthSettings(local: boolean): Promise { - const RC = (local) ? DOTFILE.RC_LOCAL() : DOTFILE.RC; - return RC + let result; + let previousPath: string | undefined = undefined; + + if (local && auth.isDefault()) { + // if no local auth defined, try current directory + previousPath = auth.path; + auth.path = '.'; + } + + result = DOTFILE.AUTH() .read() .catch((err: Error) => logError(err, ERROR.NO_CREDENTIALS(local))); + + if (previousPath) { + auth.path = previousPath; + } + + return result; } // Error messages (some errors take required params) @@ -72,8 +105,7 @@ Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`, DEPLOYMENT_COUNT: `Unable to deploy; Scripts may only have up to 20 versioned deployments at a time.`, DRIVE: `Something went wrong with the Google Drive API`, EXECUTE_ENTITY_NOT_FOUND: `Script API executable not published/deployed.`, - // FOLDER_EXISTS: `Project file already exists.`, // TEMP! - FOLDER_EXISTS: `Project file (${DOT.PROJECT.PATH}) already exists.`, + FOLDER_EXISTS: `Project file (${project.resolve()}) already exists.`, FS_DIR_WRITE: 'Could not create directory.', FS_FILE_WRITE: 'Could not write file.', INVALID_JSON: `Input params not Valid JSON string. Please fix and try again`, @@ -87,7 +119,7 @@ Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`, NO_CREDENTIALS: (local: boolean) => `Could not read API credentials. ` + `Are you logged in ${local ? 'locall' : 'globall'}y?`, NO_FUNCTION_NAME: 'N/A', - NO_GCLOUD_PROJECT: `No projectId found in your ${DOT.PROJECT.PATH} file.`, + NO_GCLOUD_PROJECT: `No projectId found in your ${project.resolve()} file.`, NO_LOCAL_CREDENTIALS: `Requires local crendetials:\n\n ${PROJECT_NAME} login --creds `, NO_MANIFEST: (filename: string) => `Manifest: ${filename} invalid. \`create\` or \`clone\` a project first.`, @@ -105,14 +137,14 @@ Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`, RATE_LIMIT: 'Rate limit exceeded. Check quota.', RUN_NODATA: 'Script execution API returned no data.', READ_ONLY_DELETE: 'Unable to delete read-only deployment.', - SCRIPT_ID_DNE: `No scriptId found in your ${DOT.PROJECT.PATH} file.`, + SCRIPT_ID_DNE: `No scriptId found in your ${project.resolve()} file.`, SCRIPT_ID_INCORRECT: (scriptId: string) => `The scriptId "${scriptId}" looks incorrect. Did you provide the correct scriptId?`, SCRIPT_ID: `Could not find script. Did you provide the correct scriptId? Are you logged in to the correct account with the script?`, SETTINGS_DNE: ` -No valid ${DOT.PROJECT.PATH} project file. You may need to \`create\` or \`clone\` a project first.`, +No valid ${project.resolve()} project file. You may need to \`create\` or \`clone\` a project first.`, UNAUTHENTICATED_LOCAL: `Error: Local client credentials unauthenticated. Check scopes/authorization.`, UNAUTHENTICATED: 'Error: Unauthenticated request: Please try again.', UNKNOWN_KEY: (key: string) => `Unknown key "${key}"`, @@ -132,7 +164,7 @@ export const LOG = { AUTH_SUCCESSFUL: `Authorization successful.`, AUTHORIZE: (authUrl: string) => `🔑 Authorize ${PROJECT_NAME} by visiting this url:\n${authUrl}\n`, CLONE_SUCCESS: (fileNum: number) => `Warning: files in subfolder are not accounted for unless you set a '${ - DOT.IGNORE.PATH + ignore.resolve() }' file. Cloned ${fileNum} ${pluralize('files', fileNum)}.`, CLONING: 'Cloning files...', @@ -159,7 +191,7 @@ Cloned ${fileNum} ${pluralize('files', fileNum)}.`, GET_PROJECT_ID_INSTRUCTIONS: `Go to *Resource > Cloud Platform Project...* and copy your projectId (including "project-id-")`, GIVE_DESCRIPTION: 'Give a description: ', - LOCAL_CREDS: `Using local credentials: ${DOT.RC.LOCAL_DIR}${DOT.RC.NAME} 🔐 `, + LOCAL_CREDS: `Using local credentials: ${auth.resolve()} 🔐 `, LOGIN: (isLocal: boolean) => `Logging in ${isLocal ? 'locally' : 'globally'}...`, LOGS_SETUP: 'Finished setting up logs.\n', NO_GCLOUD_PROJECT: `No projectId found. Running ${PROJECT_NAME} logs --setup.`, @@ -176,10 +208,11 @@ Cloned ${fileNum} ${pluralize('files', fileNum)}.`, PUSHING: 'Pushing files...', SAVED_CREDS: (isLocalCreds: boolean) => isLocalCreds - ? `Local credentials saved to: ${DOT.RC.LOCAL_DIR}${DOT.RC.ABSOLUTE_LOCAL_PATH}.\n` + - `*Be sure to never commit this file!* It's basically a password.` - : `Default credentials saved to: ${DOT.RC.PATH} (${DOT.RC.ABSOLUTE_PATH}).`, -SCRIPT_LINK: (scriptId: string) => `https://script.google.com/d/${scriptId}/edit`, + ? `Local credentials saved to: ${auth.resolve()}. +*Be sure to never commit this file!* It's basically a password.` + : `Default credentials saved to: ${auth.resolve()}.`, + SCRIPT_LINK: (scriptId: string) => `https://script.google.com/d/${scriptId}/edit`, + // TODO: `SCRIPT_RUN` is never used SCRIPT_RUN: (functionName: string) => `Executing: ${functionName}`, STACKDRIVER_SETUP: 'Setting up StackDriver Logging.', STATUS_IGNORE: 'Ignored files:', @@ -192,6 +225,7 @@ SCRIPT_LINK: (scriptId: string) => `https://script.google.com/d/${scriptId}/edit VERSION_DESCRIPTION: ({ versionNumber, description }: script_v1.Schema$Version) => `${versionNumber} - ` + (description || '(no description)'), VERSION_NUM: (numVersions: number) => `~ ${numVersions} ${pluralize('Version', numVersions)} ~`, + // TODO: `SETUP_LOCAL_OAUTH` is never used SETUP_LOCAL_OAUTH: (projectId: string) => `1. Create a client ID and secret: Open this link: ${chalk.blue(URL.CREDS(projectId))} Click ${chalk.cyan('Create credentials')}, then select ${chalk.yellow('OAuth client ID')}. @@ -257,9 +291,9 @@ export function getWebApplicationURL(deployment: script_v1.Schema$Deployment) { const entryPoints = deployment.entryPoints || []; const webEntryPoint = entryPoints.find((entryPoint: script_v1.Schema$EntryPoint) => entryPoint.entryPointType === 'WEB_APP'); - if (webEntryPoint) return webEntryPoint.webApp && webEntryPoint.webApp.url; - logError(null, ERROR.NO_WEBAPP(deployment.deploymentId || '')); -} + if (webEntryPoint) return webEntryPoint.webApp && webEntryPoint.webApp.url; + logError(null, ERROR.NO_WEBAPP(deployment.deploymentId || '')); + } /** * Gets default project name.