@google/clasp 2.5.0 → 3.0.1-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +251 -196
  2. package/build/src/auth/auth.js +176 -0
  3. package/build/src/auth/auth_code_flow.js +36 -0
  4. package/build/src/auth/credential_store.js +1 -0
  5. package/build/src/auth/file_credential_store.js +82 -0
  6. package/build/src/auth/localhost_auth_code_flow.js +63 -0
  7. package/build/src/auth/serverless_auth_code_flow.js +32 -0
  8. package/build/src/commands/clone-script.js +71 -0
  9. package/build/src/commands/create-deployment.js +33 -0
  10. package/build/src/commands/create-script.js +75 -0
  11. package/build/src/commands/create-version.js +31 -0
  12. package/build/src/commands/delete-deployment.js +71 -0
  13. package/build/src/commands/disable-api.js +19 -0
  14. package/build/src/commands/enable-api.js +31 -0
  15. package/build/src/commands/list-apis.js +23 -0
  16. package/build/src/commands/list-deployments.js +30 -0
  17. package/build/src/commands/list-scripts.js +28 -0
  18. package/build/src/commands/list-versions.js +29 -0
  19. package/build/src/commands/login.js +53 -54
  20. package/build/src/commands/logout.js +15 -15
  21. package/build/src/commands/open-apis.js +11 -0
  22. package/build/src/commands/open-container.js +15 -0
  23. package/build/src/commands/open-credentials.js +11 -0
  24. package/build/src/commands/open-logs.js +11 -0
  25. package/build/src/commands/open-script.js +18 -0
  26. package/build/src/commands/open-webapp.js +56 -0
  27. package/build/src/commands/program.js +108 -0
  28. package/build/src/commands/pull.js +19 -18
  29. package/build/src/commands/push.js +64 -74
  30. package/build/src/commands/run-function.js +61 -0
  31. package/build/src/commands/setup-logs.js +12 -0
  32. package/build/src/commands/show-authorized-user.js +18 -0
  33. package/build/src/commands/show-file-status.js +34 -0
  34. package/build/src/commands/tail-logs.js +92 -0
  35. package/build/src/commands/utils.js +82 -0
  36. package/build/src/constants.js +0 -3
  37. package/build/src/{apis.js → core/apis.js} +90 -92
  38. package/build/src/core/clasp.js +171 -0
  39. package/build/src/core/files.js +359 -0
  40. package/build/src/core/functions.js +51 -0
  41. package/build/src/core/logs.js +41 -0
  42. package/build/src/core/manifest.js +1 -0
  43. package/build/src/core/project.js +313 -0
  44. package/build/src/core/services.js +179 -0
  45. package/build/src/core/utils.js +121 -0
  46. package/build/src/index.js +16 -355
  47. package/build/src/intl.js +34 -0
  48. package/docs/README.md +0 -5
  49. package/docs/config-files.md +0 -1
  50. package/docs/run.md +2 -2
  51. package/package.json +86 -51
  52. package/CHANGELOG.md +0 -79
  53. package/build/src/apis.d.ts +0 -34
  54. package/build/src/apis.js.map +0 -1
  55. package/build/src/apiutils.d.ts +0 -16
  56. package/build/src/apiutils.js +0 -81
  57. package/build/src/apiutils.js.map +0 -1
  58. package/build/src/auth.d.ts +0 -37
  59. package/build/src/auth.js +0 -310
  60. package/build/src/auth.js.map +0 -1
  61. package/build/src/clasp-error.d.ts +0 -3
  62. package/build/src/clasp-error.js +0 -10
  63. package/build/src/clasp-error.js.map +0 -1
  64. package/build/src/commands/apis.d.ts +0 -10
  65. package/build/src/commands/apis.js +0 -90
  66. package/build/src/commands/apis.js.map +0 -1
  67. package/build/src/commands/clone.d.ts +0 -13
  68. package/build/src/commands/clone.js +0 -60
  69. package/build/src/commands/clone.js.map +0 -1
  70. package/build/src/commands/create.d.ts +0 -16
  71. package/build/src/commands/create.js +0 -81
  72. package/build/src/commands/create.js.map +0 -1
  73. package/build/src/commands/default.d.ts +0 -8
  74. package/build/src/commands/default.js +0 -10
  75. package/build/src/commands/default.js.map +0 -1
  76. package/build/src/commands/deploy.d.ts +0 -13
  77. package/build/src/commands/deploy.js +0 -51
  78. package/build/src/commands/deploy.js.map +0 -1
  79. package/build/src/commands/deployments.d.ts +0 -5
  80. package/build/src/commands/deployments.js +0 -29
  81. package/build/src/commands/deployments.js.map +0 -1
  82. package/build/src/commands/list.d.ts +0 -9
  83. package/build/src/commands/list.js +0 -34
  84. package/build/src/commands/list.js.map +0 -1
  85. package/build/src/commands/login.d.ts +0 -15
  86. package/build/src/commands/login.js.map +0 -1
  87. package/build/src/commands/logout.d.ts +0 -5
  88. package/build/src/commands/logout.js.map +0 -1
  89. package/build/src/commands/logs.d.ts +0 -17
  90. package/build/src/commands/logs.js +0 -181
  91. package/build/src/commands/logs.js.map +0 -1
  92. package/build/src/commands/open.d.ts +0 -15
  93. package/build/src/commands/open.js +0 -89
  94. package/build/src/commands/open.js.map +0 -1
  95. package/build/src/commands/pull.d.ts +0 -10
  96. package/build/src/commands/pull.js.map +0 -1
  97. package/build/src/commands/push.d.ts +0 -11
  98. package/build/src/commands/push.js.map +0 -1
  99. package/build/src/commands/run.d.ts +0 -14
  100. package/build/src/commands/run.js +0 -130
  101. package/build/src/commands/run.js.map +0 -1
  102. package/build/src/commands/setting.d.ts +0 -8
  103. package/build/src/commands/setting.js +0 -53
  104. package/build/src/commands/setting.js.map +0 -1
  105. package/build/src/commands/status.d.ts +0 -9
  106. package/build/src/commands/status.js +0 -25
  107. package/build/src/commands/status.js.map +0 -1
  108. package/build/src/commands/undeploy.d.ts +0 -9
  109. package/build/src/commands/undeploy.js +0 -55
  110. package/build/src/commands/undeploy.js.map +0 -1
  111. package/build/src/commands/version.d.ts +0 -5
  112. package/build/src/commands/version.js +0 -22
  113. package/build/src/commands/version.js.map +0 -1
  114. package/build/src/commands/versions.d.ts +0 -5
  115. package/build/src/commands/versions.js +0 -41
  116. package/build/src/commands/versions.js.map +0 -1
  117. package/build/src/conf.d.ts +0 -40
  118. package/build/src/conf.js +0 -100
  119. package/build/src/conf.js.map +0 -1
  120. package/build/src/constants.d.ts +0 -6
  121. package/build/src/constants.js.map +0 -1
  122. package/build/src/dotfile.d.ts +0 -50
  123. package/build/src/dotfile.js +0 -71
  124. package/build/src/dotfile.js.map +0 -1
  125. package/build/src/files.d.ts +0 -70
  126. package/build/src/files.js +0 -364
  127. package/build/src/files.js.map +0 -1
  128. package/build/src/index.d.ts +0 -18
  129. package/build/src/index.js.map +0 -1
  130. package/build/src/inquirer.d.ts +0 -82
  131. package/build/src/inquirer.js +0 -111
  132. package/build/src/inquirer.js.map +0 -1
  133. package/build/src/manifest.d.ts +0 -123
  134. package/build/src/manifest.js +0 -142
  135. package/build/src/manifest.js.map +0 -1
  136. package/build/src/messages.d.ts +0 -110
  137. package/build/src/messages.js +0 -161
  138. package/build/src/messages.js.map +0 -1
  139. package/build/src/urls.d.ts +0 -21
  140. package/build/src/urls.js +0 -33
  141. package/build/src/urls.js.map +0 -1
  142. package/build/src/utils.d.ts +0 -102
  143. package/build/src/utils.js +0 -232
  144. package/build/src/utils.js.map +0 -1
  145. package/docs/develop.md +0 -94
  146. package/docs/esmodules.md +0 -81
  147. package/docs/running-locally.md +0 -31
  148. package/docs/settings.md +0 -56
  149. package/docs/typescript.md +0 -354
@@ -0,0 +1,176 @@
1
+ import { readFileSync } from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { GoogleAuth, OAuth2Client } from 'google-auth-library';
5
+ import { google } from 'googleapis';
6
+ import { FileCredentialStore } from './file_credential_store.js';
7
+ import { LocalServerAuthorizationCodeFlow } from './localhost_auth_code_flow.js';
8
+ import { ServerlessAuthorizationCodeFlow } from './serverless_auth_code_flow.js';
9
+ export async function initAuth(options) {
10
+ var _a, _b, _c;
11
+ const authFilePath = (_a = options.authFilePath) !== null && _a !== void 0 ? _a : path.join(os.homedir(), '.clasprc.json');
12
+ const credentialStore = new FileCredentialStore(authFilePath);
13
+ if (options.useApplicationDefaultCredentials) {
14
+ const credentials = await createApplicationDefaultCredentials();
15
+ return {
16
+ credentials,
17
+ credentialStore,
18
+ user: (_b = options.userKey) !== null && _b !== void 0 ? _b : 'default',
19
+ };
20
+ }
21
+ const credentials = await getAuthorizedOAuth2Client(credentialStore, options.userKey);
22
+ return {
23
+ credentials,
24
+ credentialStore,
25
+ user: (_c = options.userKey) !== null && _c !== void 0 ? _c : 'default',
26
+ };
27
+ }
28
+ export async function getUserInfo(credentials) {
29
+ const api = google.oauth2('v2');
30
+ const res = await api.userinfo.get({ auth: credentials });
31
+ if (res.status !== 200) {
32
+ return undefined;
33
+ }
34
+ return {
35
+ email: res.data.email,
36
+ id: res.data.id,
37
+ };
38
+ }
39
+ /**
40
+ * Creates an an unauthorized oauth2 client given the client secret file. If no path is provided,
41
+ * teh default client is returned.
42
+ * @param clientSecretPath
43
+ * @returns
44
+ */
45
+ export function getUnauthorizedOuth2Client(clientSecretPath) {
46
+ if (clientSecretPath) {
47
+ return createOauthClient(clientSecretPath);
48
+ }
49
+ return createDefaultOAuthClient();
50
+ }
51
+ /**
52
+ * Create an authorized oauth2 client from saved credentials.
53
+ * @param userKey
54
+ * @returns
55
+ */
56
+ export async function getAuthorizedOAuth2Client(store, userKey) {
57
+ if (!userKey) {
58
+ userKey = 'default';
59
+ }
60
+ const savedCredentials = await store.load(userKey);
61
+ if (!savedCredentials) {
62
+ return undefined;
63
+ }
64
+ const client = new GoogleAuth().fromJSON(savedCredentials);
65
+ client.setCredentials(savedCredentials);
66
+ client.on('tokens', async (tokens) => {
67
+ const refreshedCredentials = {
68
+ ...savedCredentials,
69
+ expiry_date: tokens.expiry_date,
70
+ access_token: tokens.access_token,
71
+ id_token: tokens.access_token,
72
+ };
73
+ await store.save(userKey, refreshedCredentials);
74
+ });
75
+ return client;
76
+ }
77
+ /**
78
+ * Requests authorization to manage Apps Script projects.
79
+ * @param {boolean} useLocalhost Uses a local HTTP server if true. Manual entry o.w.
80
+ * @param {ClaspCredentials?} creds An optional credentials object.
81
+ * @param {string[]} [scopes=[]] List of OAuth scopes to authorize.
82
+ * @param {number?} redirectPort Optional custom port for the local HTTP server during the authorization process.
83
+ * If not specified, a random available port will be used.
84
+ */
85
+ export async function authorize(options) {
86
+ let flow;
87
+ if (options.noLocalServer) {
88
+ flow = new ServerlessAuthorizationCodeFlow(options.oauth2Client);
89
+ }
90
+ else {
91
+ flow = new LocalServerAuthorizationCodeFlow(options.oauth2Client);
92
+ }
93
+ const client = await flow.authorize(options.scopes);
94
+ await saveOauthClientCredentials(options.store, options.userKey, client);
95
+ return client;
96
+ }
97
+ async function saveOauthClientCredentials(store, userKey, oauth2Client) {
98
+ var _a, _b;
99
+ const savedCredentials = {
100
+ client_id: oauth2Client._clientId,
101
+ client_secret: oauth2Client._clientSecret,
102
+ type: 'authorized_user',
103
+ refresh_token: (_a = oauth2Client.credentials.refresh_token) !== null && _a !== void 0 ? _a : undefined,
104
+ access_token: (_b = oauth2Client.credentials.access_token) !== null && _b !== void 0 ? _b : undefined,
105
+ };
106
+ oauth2Client.on('tokens', async (tokens) => {
107
+ const refreshedCredentials = {
108
+ ...savedCredentials,
109
+ expiry_date: tokens.expiry_date,
110
+ access_token: tokens.access_token,
111
+ id_token: tokens.access_token,
112
+ };
113
+ await store.save(userKey, refreshedCredentials);
114
+ });
115
+ await store.save(userKey, savedCredentials);
116
+ }
117
+ /**
118
+ * Creates an aunthorized oauth2 client with the given credentials
119
+ * @param clientSecretPath
120
+ * @returns
121
+ */
122
+ function createOauthClient(clientSecretPath) {
123
+ if (!clientSecretPath) {
124
+ throw new Error('Invalid credentials');
125
+ }
126
+ const contents = readFileSync(clientSecretPath);
127
+ const keyFile = JSON.parse(contents.toString());
128
+ const keys = keyFile.installed || keyFile.web;
129
+ if (!keys.redirect_uris || keys.redirect_uris.length === 0) {
130
+ throw new Error('Invalid redirect URL');
131
+ }
132
+ const redirectUrl = keys.redirect_uris.find((uri) => new URL(uri).hostname === 'localhost');
133
+ if (!redirectUrl) {
134
+ throw new Error('No localhost redirect URL found');
135
+ }
136
+ // create an oAuth client to authorize the API call
137
+ return new OAuth2Client({
138
+ clientId: keys.client_id,
139
+ clientSecret: keys.client_secret,
140
+ redirectUri: redirectUrl,
141
+ });
142
+ }
143
+ /**
144
+ * Creates an aunthorized oauth2 client using the default id & secret.
145
+ * @param clientSecretPath
146
+ * @returns
147
+ */
148
+ function createDefaultOAuthClient() {
149
+ // Default client
150
+ return new OAuth2Client({
151
+ clientId: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com',
152
+ clientSecret: 'v6V3fKV_zWU7iw1DrpO1rknX',
153
+ redirectUri: 'http://localhost',
154
+ });
155
+ }
156
+ export async function createApplicationDefaultCredentials() {
157
+ const defaultCreds = await new GoogleAuth({
158
+ scopes: [
159
+ 'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments
160
+ 'https://www.googleapis.com/auth/script.projects', // Apps Script management
161
+ 'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps
162
+ 'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata
163
+ 'https://www.googleapis.com/auth/drive.file', // Create Drive files
164
+ 'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API
165
+ 'https://www.googleapis.com/auth/logging.read', // StackDriver logs
166
+ 'https://www.googleapis.com/auth/userinfo.email', // User email address
167
+ 'https://www.googleapis.com/auth/userinfo.profile',
168
+ 'https://www.googleapis.com/auth/cloud-platform',
169
+ ],
170
+ }).getClient();
171
+ // Remove this check after https://github.com/googleapis/google-auth-library-nodejs/issues/1677 fixed
172
+ if (defaultCreds instanceof OAuth2Client) {
173
+ return defaultCreds;
174
+ }
175
+ return undefined;
176
+ }
@@ -0,0 +1,36 @@
1
+ export class AuthorizationCodeFlow {
2
+ constructor(oauth2client) {
3
+ this.oauth2Client = oauth2client;
4
+ }
5
+ async authorize(scopes) {
6
+ const scope = Array.isArray(scopes) ? scopes.join(' ') : scopes;
7
+ const redirectUri = await this.getRedirectUri();
8
+ const authUrl = this.oauth2Client.generateAuthUrl({
9
+ redirect_uri: redirectUri,
10
+ access_type: 'offline',
11
+ scope: scope,
12
+ });
13
+ const code = await this.promptAndReturnCode(authUrl);
14
+ const tokens = await this.oauth2Client.getToken({
15
+ code,
16
+ redirect_uri: redirectUri,
17
+ });
18
+ this.oauth2Client.setCredentials(tokens.tokens);
19
+ return this.oauth2Client;
20
+ }
21
+ async getRedirectUri() {
22
+ throw new Error('Not implemented');
23
+ }
24
+ async promptAndReturnCode(_authorizationUrl) {
25
+ throw new Error('Not implemented');
26
+ }
27
+ }
28
+ export function parseAuthResponseUrl(url) {
29
+ const urlParts = new URL(url, 'http://localhost/').searchParams;
30
+ const code = urlParts.get('code');
31
+ const error = urlParts.get('error');
32
+ return {
33
+ code,
34
+ error,
35
+ };
36
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import fs from 'fs';
2
+ function hasLegacyLocalCredentials(store) {
3
+ return store.token && store.oauth2ClientSettings;
4
+ }
5
+ function hasLegacyGlobalCredentials(store) {
6
+ return !!store.access_token;
7
+ }
8
+ export class FileCredentialStore {
9
+ constructor(filePath) {
10
+ this.filePath = filePath;
11
+ }
12
+ async save(user, credentials) {
13
+ const store = this.readFile();
14
+ if (!store.tokens) {
15
+ store.tokens = {};
16
+ }
17
+ store.tokens[user] = credentials;
18
+ this.writeFile(store);
19
+ }
20
+ async delete(user) {
21
+ let store = this.readFile();
22
+ if (!store.tokens) {
23
+ store.tokens = {};
24
+ }
25
+ store.tokens[user] = undefined;
26
+ if (user === 'default') {
27
+ // Remove legacy keys if default user
28
+ store = {
29
+ tokens: store.tokens,
30
+ };
31
+ }
32
+ this.writeFile(store);
33
+ }
34
+ async deleteAll() {
35
+ await this.writeFile({
36
+ tokens: {},
37
+ });
38
+ }
39
+ async load(user) {
40
+ var _a, _b, _c;
41
+ const store = this.readFile();
42
+ const credentials = (_a = store.tokens) === null || _a === void 0 ? void 0 : _a[user];
43
+ if (credentials) {
44
+ return credentials;
45
+ }
46
+ if (user !== 'default') {
47
+ return null;
48
+ }
49
+ if (hasLegacyLocalCredentials(store)) {
50
+ // Support previous un
51
+ return {
52
+ type: 'authorized_user',
53
+ ...store.token,
54
+ client_id: (_b = store.oauth2ClientSettings) === null || _b === void 0 ? void 0 : _b.clientId,
55
+ client_secret: (_c = store.oauth2ClientSettings) === null || _c === void 0 ? void 0 : _c.clientSecret,
56
+ };
57
+ }
58
+ if (hasLegacyGlobalCredentials(store)) {
59
+ return {
60
+ type: 'authorized_user',
61
+ access_token: store.access_token,
62
+ refresh_token: store.refresh_token,
63
+ expiry_date: store.exprity_date,
64
+ token_type: store.token_type,
65
+ client_id: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com',
66
+ client_secret: 'v6V3fKV_zWU7iw1DrpO1rknX',
67
+ };
68
+ }
69
+ return null;
70
+ }
71
+ readFile() {
72
+ if (fs.existsSync(this.filePath)) {
73
+ // TODO - use promises
74
+ const content = fs.readFileSync(this.filePath, { encoding: 'utf8' });
75
+ return JSON.parse(content);
76
+ }
77
+ return {};
78
+ }
79
+ writeFile(store) {
80
+ fs.writeFileSync(this.filePath, JSON.stringify(store, null, 2));
81
+ }
82
+ }
@@ -0,0 +1,63 @@
1
+ import { createServer } from 'http';
2
+ import enableDestroy from 'server-destroy';
3
+ import { intl } from '../intl.js';
4
+ import { AuthorizationCodeFlow, parseAuthResponseUrl } from './auth_code_flow.js';
5
+ import open from 'open';
6
+ export class LocalServerAuthorizationCodeFlow extends AuthorizationCodeFlow {
7
+ constructor(oauth2client) {
8
+ super(oauth2client);
9
+ this.port = 0;
10
+ }
11
+ async getRedirectUri() {
12
+ this.server = await new Promise((resolve, reject) => {
13
+ const s = createServer();
14
+ enableDestroy(s);
15
+ s.listen(this.port, () => resolve(s)).on('error', (err) => {
16
+ if (err.code === 'EADDRINUSE') {
17
+ const msg = intl.formatMessage({ id: "smVcjx", defaultMessage: [{ type: 0, value: "Error: Port " }, { type: 1, value: "port" }, { type: 0, value: " is already in use. Please specify a different port with --redirect-port" }] }, {
18
+ port: this.port,
19
+ });
20
+ console.error(msg);
21
+ }
22
+ else {
23
+ const msg = intl.formatMessage({ id: "3X2J9l", defaultMessage: [{ type: 0, value: "Error: Unable to start the server on port " }, { type: 1, value: "port" }] }, {
24
+ port: this.port,
25
+ errorMessage: err.message,
26
+ });
27
+ console.error(msg, err.message);
28
+ }
29
+ reject(err);
30
+ });
31
+ });
32
+ const { port } = this.server.address();
33
+ return `http://localhost:${port}`;
34
+ }
35
+ async promptAndReturnCode(authorizationUrl) {
36
+ return await new Promise((resolve, reject) => {
37
+ if (!this.server) {
38
+ reject(new Error('Server not started'));
39
+ return;
40
+ }
41
+ this.server.on('request', (request, response) => {
42
+ if (!request.url) {
43
+ reject(new Error('Missing URL in request'));
44
+ return;
45
+ }
46
+ const { code, error } = parseAuthResponseUrl(request.url);
47
+ if (code) {
48
+ resolve(code);
49
+ }
50
+ else {
51
+ reject(error);
52
+ }
53
+ const msg = intl.formatMessage({ id: "ZT8LeG", defaultMessage: [{ type: 0, value: "Logged in! You may close this page." }] });
54
+ response.end(msg);
55
+ });
56
+ void open(authorizationUrl);
57
+ const msg = intl.formatMessage({ id: "NbCrKh", defaultMessage: [{ type: 0, value: "`\uD83D\uDD11 Authorize clasp by visiting this url: " }, { type: 1, value: "url" }] }, {
58
+ url: authorizationUrl,
59
+ });
60
+ console.log(msg);
61
+ }).finally(() => { var _a; return (_a = this.server) === null || _a === void 0 ? void 0 : _a.destroy(); });
62
+ }
63
+ }
@@ -0,0 +1,32 @@
1
+ import inquirer from 'inquirer';
2
+ import { intl } from '../intl.js';
3
+ import { AuthorizationCodeFlow, parseAuthResponseUrl } from './auth_code_flow.js';
4
+ export class ServerlessAuthorizationCodeFlow extends AuthorizationCodeFlow {
5
+ constructor(oauth2client) {
6
+ super(oauth2client);
7
+ }
8
+ async getRedirectUri() {
9
+ return 'http://localhost:8888';
10
+ }
11
+ async promptAndReturnCode(authorizationUrl) {
12
+ const prompt = intl.formatMessage({ id: "Tx67iE", defaultMessage: [{ type: 0, value: "Authorize clasp by visiting the following URL on another device: " }, { type: 1, value: "url" }, { type: 0, value: " After authorization, copy the URL in the browser. Enter the URL from your browser after completing authorization" }] }, {
13
+ url: authorizationUrl,
14
+ });
15
+ const answer = await inquirer.prompt([
16
+ {
17
+ message: prompt,
18
+ name: 'url',
19
+ type: 'input',
20
+ },
21
+ ]);
22
+ const { code, error } = parseAuthResponseUrl(answer.url);
23
+ if (error) {
24
+ throw new Error(error);
25
+ }
26
+ if (!code) {
27
+ const msg = intl.formatMessage({ id: "XvVmSR", defaultMessage: [{ type: 0, value: "Missing code in responde URL" }] });
28
+ throw new Error(msg);
29
+ }
30
+ return code;
31
+ }
32
+ }
@@ -0,0 +1,71 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import { intl } from '../intl.js';
4
+ import { isInteractive, withSpinner } from './utils.js';
5
+ export const command = new Command('clone-script')
6
+ .alias('clone')
7
+ .description('Clone an existing script')
8
+ .arguments('[scriptId] [versionNumber]')
9
+ .option('--rootDir <rootDir>', 'Local root directory in which clasp will store your project files.')
10
+ .action(async function (scriptId, versionNumber) {
11
+ var _a;
12
+ let clasp = this.opts().clasp;
13
+ if (clasp.project.exists()) {
14
+ const msg = intl.formatMessage({ id: "kk5+4G", defaultMessage: [{ type: 0, value: "Project file already exists." }] });
15
+ this.error(msg);
16
+ }
17
+ const rootDir = this.opts().rootDir;
18
+ clasp.withContentDir(rootDir !== null && rootDir !== void 0 ? rootDir : '.');
19
+ if (scriptId) {
20
+ const match = scriptId.match(/https:\/\/script\.google\.com\/d\/([^/]+)\/.*/);
21
+ if (match) {
22
+ scriptId = match[1];
23
+ }
24
+ else {
25
+ scriptId = scriptId.trim();
26
+ }
27
+ }
28
+ else if (isInteractive()) {
29
+ const projects = await clasp.project.listScripts();
30
+ const choices = projects.results.map(file => ({
31
+ name: `${file.name.padEnd(20)} - https://script.google.com/d/${file.id}/edit`,
32
+ value: file.id,
33
+ }));
34
+ const prompt = intl.formatMessage({ id: "3aQTl3", defaultMessage: [{ type: 0, value: "Clone which script?" }] });
35
+ const answer = await inquirer.prompt([
36
+ {
37
+ choices: choices,
38
+ message: prompt,
39
+ name: 'scriptId',
40
+ pageSize: 30,
41
+ type: 'list',
42
+ },
43
+ ]);
44
+ scriptId = answer.scriptId;
45
+ }
46
+ if (!scriptId) {
47
+ const msg = intl.formatMessage({ id: "DJWCmP", defaultMessage: [{ type: 0, value: "No script ID." }] });
48
+ this.error(msg);
49
+ }
50
+ try {
51
+ const cloningScriptMsg = intl.formatMessage({ id: "UTMHnH", defaultMessage: [{ type: 0, value: "Cloning script..." }] });
52
+ const files = await withSpinner(cloningScriptMsg, async () => {
53
+ clasp = clasp.withScriptId(scriptId);
54
+ const files = await clasp.files.pull(versionNumber);
55
+ clasp.project.updateSettings();
56
+ return files;
57
+ });
58
+ files.forEach(f => console.log(`└─ ${f.localPath}`));
59
+ const successMessage = intl.formatMessage({ id: "Hw9Gqn", defaultMessage: [{ type: 0, value: "Warning: files in subfolder are not accounted for unless you set a .claspignore file. Cloned " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
60
+ count: files.length,
61
+ });
62
+ console.log(successMessage);
63
+ }
64
+ catch (error) {
65
+ if (((_a = error.cause) === null || _a === void 0 ? void 0 : _a.code) === 'INVALID_ARGUMENT') {
66
+ const msg = intl.formatMessage({ id: "jRe7cT", defaultMessage: [{ type: 0, value: "Invalid script ID." }] });
67
+ this.error(msg);
68
+ }
69
+ throw error;
70
+ }
71
+ });
@@ -0,0 +1,33 @@
1
+ import { Command } from 'commander';
2
+ import { intl } from '../intl.js';
3
+ import { withSpinner } from './utils.js';
4
+ export const command = new Command('create-deployment')
5
+ .alias('deploy')
6
+ .description('Deploy a project')
7
+ .option('-V, --versionNumber <version>', 'The project version')
8
+ .option('-d, --description <description>', 'The deployment description')
9
+ .option('-i, --deploymentId <id>', 'The deployment ID to redeploy')
10
+ .action(async function (options) {
11
+ var _a, _b, _c;
12
+ const clasp = this.opts().clasp;
13
+ const deploymentId = options.deploymentId;
14
+ const description = (_a = options.description) !== null && _a !== void 0 ? _a : '';
15
+ const versionNumber = options.versionNumber ? Number(options.versionNumber) : undefined;
16
+ try {
17
+ const spinnerMsg = intl.formatMessage({ id: "oL8t7p", defaultMessage: [{ type: 0, value: "Deploying project..." }] });
18
+ const deployment = await withSpinner(spinnerMsg, async () => {
19
+ return await clasp.project.deploy(description, deploymentId, versionNumber);
20
+ });
21
+ const successMessage = intl.formatMessage({ id: "182gSV", defaultMessage: [{ type: 0, value: "Deployed " }, { type: 1, value: "deploymentId" }, { type: 0, value: " " }, { type: 5, value: "version", options: { undefined: { value: [{ type: 0, value: "@HEAD" }] }, other: { value: [{ type: 0, value: "@" }, { type: 1, value: "version" }] } } }] }, {
22
+ deploymentId: deployment.deploymentId,
23
+ version: (_b = deployment.deploymentConfig) === null || _b === void 0 ? void 0 : _b.versionNumber,
24
+ });
25
+ console.log(successMessage);
26
+ }
27
+ catch (error) {
28
+ if (((_c = error.cause) === null || _c === void 0 ? void 0 : _c.code) === 'INVALID_ARGUMENT') {
29
+ this.error(error.cause.message);
30
+ }
31
+ throw error;
32
+ }
33
+ });
@@ -0,0 +1,75 @@
1
+ import path from 'node:path';
2
+ import { Command } from 'commander';
3
+ import inflection from 'inflection';
4
+ import { intl } from '../intl.js';
5
+ import { withSpinner } from './utils.js';
6
+ // https://developers.google.com/drive/api/v3/mime-types
7
+ const DRIVE_FILE_MIMETYPES = {
8
+ docs: 'application/vnd.google-apps.document',
9
+ forms: 'application/vnd.google-apps.form',
10
+ sheets: 'application/vnd.google-apps.spreadsheet',
11
+ slides: 'application/vnd.google-apps.presentation',
12
+ };
13
+ export const command = new Command('create-script')
14
+ .command('create')
15
+ .description('Create a script')
16
+ .option('--type <type>', 'Creates a new Apps Script project attached to a new Document, Spreadsheet, Presentation, Form, or as a standalone script, web app, or API.', 'standalone')
17
+ .option('--title <title>', 'The project title.')
18
+ .option('--parentId <id>', 'A project parent Id.')
19
+ .option('--rootDir <rootDir>', 'Local root directory in which clasp will store your project files.')
20
+ .action(async function (options) {
21
+ var _a;
22
+ const clasp = this.opts().clasp;
23
+ if (clasp.project.exists()) {
24
+ const msg = intl.formatMessage({ id: "kk5+4G", defaultMessage: [{ type: 0, value: "Project file already exists." }] });
25
+ this.error(msg);
26
+ }
27
+ // Create defaults.
28
+ const parentId = options.parentId;
29
+ const name = options.title ? options.title : getDefaultProjectName(process.cwd());
30
+ const type = options.type ? options.type.toLowerCase() : 'standalone';
31
+ const rootDir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : '.';
32
+ clasp.withContentDir(rootDir);
33
+ if (type && type !== 'standalone') {
34
+ const mimeType = DRIVE_FILE_MIMETYPES[type];
35
+ if (!mimeType) {
36
+ const msg = intl.formatMessage({ id: "d2MBtN", defaultMessage: [{ type: 0, value: "Invalid container file type" }] });
37
+ this.error(msg);
38
+ }
39
+ const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
40
+ const { parentId } = await withSpinner(spinnerMsg, async () => await clasp.project.createWithContainer(name, mimeType));
41
+ const url = `https://drive.google.com/open?id=${parentId}`;
42
+ const successMessage = intl.formatMessage({ id: "11VZzp", defaultMessage: [{ type: 0, value: "Created new container: [url}" }] }, {
43
+ url,
44
+ });
45
+ console.log(successMessage);
46
+ }
47
+ else {
48
+ const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
49
+ const scriptId = await withSpinner(spinnerMsg, async () => await clasp.project.createScript(name, parentId));
50
+ const url = `https://script.google.com/d/${scriptId}/edit`;
51
+ const successMessage = intl.formatMessage({ id: "NUzw0t", defaultMessage: [{ type: 0, value: "Created new script: " }, { type: 1, value: "url" }] }, {
52
+ url,
53
+ });
54
+ console.log(successMessage);
55
+ }
56
+ const spinnerMsg = intl.formatMessage({ id: "UTMHnH", defaultMessage: [{ type: 0, value: "Cloning script..." }] });
57
+ const files = await withSpinner(spinnerMsg, async () => {
58
+ const files = await clasp.files.pull();
59
+ clasp.project.updateSettings();
60
+ return files;
61
+ });
62
+ files.forEach(f => console.log(`└─ ${f.localPath}`));
63
+ const successMessage = intl.formatMessage({ id: "Hw9Gqn", defaultMessage: [{ type: 0, value: "Warning: files in subfolder are not accounted for unless you set a .claspignore file. Cloned " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
64
+ count: files.length,
65
+ });
66
+ console.log(successMessage);
67
+ });
68
+ /**
69
+ * Gets default project name.
70
+ * @return {string} default project name.
71
+ */
72
+ export function getDefaultProjectName(dir) {
73
+ const dirName = path.basename(dir);
74
+ return inflection.humanize(dirName);
75
+ }
@@ -0,0 +1,31 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import { intl } from '../intl.js';
4
+ import { isInteractive, withSpinner } from './utils.js';
5
+ export const command = new Command('create-version')
6
+ .alias('version')
7
+ .arguments('[description]')
8
+ .description('Creates an immutable version of the script')
9
+ .action(async function (description) {
10
+ const clasp = this.opts().clasp;
11
+ if (!description && isInteractive()) {
12
+ const prompt = intl.formatMessage({ id: "6U9ksF", defaultMessage: [{ type: 0, value: "Give a description:" }] });
13
+ const answer = await inquirer.prompt([
14
+ {
15
+ default: '',
16
+ message: prompt,
17
+ name: 'description',
18
+ type: 'input',
19
+ },
20
+ ]);
21
+ description = answer.description;
22
+ }
23
+ const spinnerMsg = intl.formatMessage({ id: "HhBwsB", defaultMessage: [{ type: 0, value: "Creating a new version..." }] });
24
+ const versionNumber = await withSpinner(spinnerMsg, async () => {
25
+ return clasp.project.version(description);
26
+ });
27
+ const successMessage = intl.formatMessage({ id: "TVOEZz", defaultMessage: [{ type: 0, value: "Created version " }, { type: 2, value: "version", style: null }] }, {
28
+ version: versionNumber,
29
+ });
30
+ console.log(successMessage);
31
+ });