zapier-platform-core 17.5.0 → 17.7.0

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.
package/index.js CHANGED
@@ -2,4 +2,5 @@ const zapier = require('./src');
2
2
  zapier.version = require('./package.json').version;
3
3
  zapier.tools = require('./src/tools/exported');
4
4
  zapier.errors = require('./src/errors');
5
+ zapier.console = require('./src/tools/console-singleton').consoleProxy;
5
6
  module.exports = zapier;
package/index.mjs CHANGED
@@ -2,9 +2,11 @@ import zapier from './src/index.js';
2
2
  import packageJson from './package.json' with { type: 'json' };
3
3
  import _tools from './src/tools/exported.js';
4
4
  import _errors from './src/errors.js';
5
+ import { consoleProxy } from './src/tools/console-singleton.js';
5
6
  zapier.version = packageJson.version;
6
7
  zapier.tools = _tools;
7
8
  zapier.errors = _errors;
9
+ zapier.console = consoleProxy;
8
10
  // Allows `import { ... } from 'zapier-platform-core'`
9
11
  export const {
10
12
  createAppHandler,
@@ -16,6 +18,7 @@ export const {
16
18
  defineSearch,
17
19
  defineTrigger,
18
20
  integrationTestHandler,
21
+ console,
19
22
  tools,
20
23
  version,
21
24
  errors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-core",
3
- "version": "17.5.0",
3
+ "version": "17.7.0",
4
4
  "description": "The core SDK for CLI apps in the Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -63,7 +63,7 @@
63
63
  "node-fetch": "2.7.0",
64
64
  "oauth-sign": "0.9.0",
65
65
  "semver": "7.7.1",
66
- "zapier-platform-schema": "17.5.0"
66
+ "zapier-platform-schema": "17.7.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node-fetch": "^2.6.11",
@@ -10,7 +10,7 @@ const createJSONtool = require('../../tools/create-json-tool');
10
10
  const createStoreKeyTool = require('../../tools/create-storekey-tool');
11
11
  const createCallbackHigherOrderFunction = require('../../tools/create-callback-wrapper');
12
12
  const createLegacyScriptingRunner = require('../../tools/create-legacy-scripting-runner');
13
- const createLoggerConsole = require('../../tools/create-logger-console');
13
+ const { initialize } = require('../../tools/console-singleton');
14
14
  const errors = require('../../errors');
15
15
  const hashing = require('../../tools/hashing');
16
16
 
@@ -19,9 +19,10 @@ const hashing = require('../../tools/hashing');
19
19
  */
20
20
  const injectZObject = (input) => {
21
21
  const bundle = _.get(input, '_zapier.event.bundle', {});
22
+
22
23
  const zRoot = {
23
24
  cache: createCache(input),
24
- console: createLoggerConsole(input),
25
+ console: initialize(input),
25
26
  cursor: createStoreKeyTool(input),
26
27
  dehydrate: createDehydrator(input, 'method'),
27
28
  dehydrateFile: createDehydrator(input, 'file'),
@@ -1,7 +1,7 @@
1
1
  module.exports = {
2
2
  createIsObject: require('./create-is-object'),
3
3
 
4
- searchIsArray: require('./search-is-array'),
4
+ searchIsArrayOrEnvelope: require('./search-is-array-or-envelope'),
5
5
 
6
6
  triggerIsArray: require('./trigger-is-array'),
7
7
  triggerIsObject: require('./trigger-is-object'),
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const { simpleTruncate } = require('../tools/data');
5
+
6
+ const isSearch = require('./is-search');
7
+
8
+ const hasCanPaginate = (searchKey, compiledApp) => {
9
+ const canPaginate =
10
+ compiledApp?.searches?.[searchKey]?.operation?.canPaginate;
11
+ return canPaginate;
12
+ };
13
+
14
+ /*
15
+ Searches should return an array of objects,
16
+ or a response envelope like { results: [...], paging_token: '...' }
17
+ when canPaginate is true.
18
+ */
19
+ const searchIsArrayOrEnvelope = {
20
+ name: 'searchIsArrayOrEnvelope',
21
+ shouldRun: isSearch,
22
+ run: (method, results, compiledApp) => {
23
+ const searchKey = method.split('.', 2)[1];
24
+ const truncatedResults = simpleTruncate(JSON.stringify(results), 50);
25
+
26
+ if (hasCanPaginate(searchKey, compiledApp)) {
27
+ // if paging is supported and results is an object (indicating pagination), it must have results and paging_token
28
+ if (_.isPlainObject(results)) {
29
+ if (!_.has(results, 'results') || !_.has(results, 'paging_token')) {
30
+ return [
31
+ `Paginated search results must be an object containing results and paging_token, got: ${truncatedResults}`,
32
+ ];
33
+ }
34
+ if (
35
+ !_.isString(results.paging_token) &&
36
+ !_.isNull(results.paging_token)
37
+ ) {
38
+ return [
39
+ `"paging_token" must be a string or null, got: ${typeof results.paging_token}`,
40
+ ];
41
+ }
42
+ // pass to array check below
43
+ results = results.results;
44
+ } else {
45
+ return [
46
+ `Paginated search results must be an object, got: ${typeof results}, (${truncatedResults})`,
47
+ ];
48
+ }
49
+ }
50
+
51
+ if (!_.isArray(results)) {
52
+ return [
53
+ `Search results must be an array, got: ${typeof results}, (${truncatedResults})`,
54
+ ];
55
+ }
56
+ return [];
57
+ },
58
+ };
59
+
60
+ module.exports = searchIsArrayOrEnvelope;
package/src/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const createLambdaHandler = require('./tools/create-lambda-handler');
4
4
  const createAppTester = require('./tools/create-app-tester');
5
+ const { consoleProxy } = require('./tools/console-singleton');
5
6
 
6
7
  let _integrationTestHandler;
7
8
  const integrationTestHandler = (event, context, callback) => {
@@ -15,5 +16,6 @@ module.exports = {
15
16
  createAppHandler: createLambdaHandler,
16
17
  createAppTester,
17
18
  integrationTestHandler,
19
+ console: consoleProxy,
18
20
  ...require('./typeHelpers'),
19
21
  };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const createLoggerConsole = require('./create-logger-console');
4
+
5
+ /**
6
+ * Shared console instance that can be used standalone or as z.console.
7
+ * Uses createLoggerConsole to create the single instance shared between both.
8
+ */
9
+
10
+ // Shared console instance - will be initialized by middleware
11
+ let loggerConsole = null;
12
+
13
+ /**
14
+ * Initialize the console with the input context from middleware.
15
+ * This is called by the z-object middleware and uses createLoggerConsole.
16
+ * Creates a new logger console for each Lambda invocation to ensure proper isolation.
17
+ * Returns the shared console instance for z.console to use.
18
+ */
19
+ const initialize = (input) => {
20
+ // Always create a new logger console for each invocation to ensure
21
+ // log context isolation between Lambda invocations
22
+ loggerConsole = createLoggerConsole(input);
23
+ return loggerConsole;
24
+ };
25
+
26
+ /**
27
+ * Reset the singleton for testing purposes
28
+ */
29
+ const reset = () => {
30
+ loggerConsole = null;
31
+ };
32
+
33
+ /**
34
+ * Proxy that behaves like a console object.
35
+ * Forwards calls to loggerConsole if initialized, otherwise to global console.
36
+ */
37
+ const consoleProxy = new Proxy(console, {
38
+ get(target, prop, receiver) {
39
+ // Use loggerConsole if initialized, otherwise fall back to global console
40
+ const actualConsole = loggerConsole || target;
41
+ return Reflect.get(actualConsole, prop, actualConsole);
42
+ },
43
+ });
44
+
45
+ module.exports = {
46
+ consoleProxy,
47
+ initialize,
48
+ reset,
49
+ };
@@ -32,16 +32,68 @@ const LOG_STREAM_BYTES_LIMIT = 15 * 1024 * 1024;
32
32
 
33
33
  const DEFAULT_LOGGER_TIMEOUT = 200;
34
34
 
35
+ // Will be initialized lazily
36
+ const SAFE_AUTH_DATA_KEYS = new Set();
37
+
35
38
  const sleep = promisify(setTimeout);
36
39
 
37
- const isUrl = (url) => {
38
- try {
39
- // eslint-disable-next-line no-new
40
- new URL(url);
41
- return true;
42
- } catch (e) {
40
+ const initSafeAuthDataKeys = () => {
41
+ // An authData key in (safePrefixes x safeSuffixes) is considered safe to log
42
+ // uncensored
43
+ const safePrefixes = [
44
+ 'account',
45
+ 'bot_user',
46
+ 'cloud',
47
+ 'cloud_site',
48
+ 'company',
49
+ 'domain',
50
+ 'email',
51
+ 'environment',
52
+ 'location',
53
+ 'org',
54
+ 'organization',
55
+ 'project',
56
+ 'region',
57
+ 'scope',
58
+ 'scopes',
59
+ 'site',
60
+ 'subdomain',
61
+ 'team',
62
+ 'token_type',
63
+ 'user',
64
+ 'workspace',
65
+ ];
66
+ const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
67
+ for (const prefix of safePrefixes) {
68
+ for (const suffix of safeSuffixes) {
69
+ SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
70
+ }
71
+ }
72
+ };
73
+
74
+ const isUrl = (value) => {
75
+ if (!value || typeof value !== 'string') {
43
76
  return false;
44
77
  }
78
+ const commonProtocols = [
79
+ 'https://',
80
+ 'http://',
81
+ 'ftp://',
82
+ 'ftps://',
83
+ 'file://',
84
+ ];
85
+ for (const protocol of commonProtocols) {
86
+ if (value.startsWith(protocol)) {
87
+ try {
88
+ // eslint-disable-next-line no-new
89
+ new URL(value);
90
+ return true;
91
+ } catch (e) {
92
+ return false;
93
+ }
94
+ }
95
+ }
96
+ return false;
45
97
  };
46
98
 
47
99
  const MAX_LENGTH = 3500;
@@ -136,6 +188,13 @@ const attemptFindSecretsInStr = (s, isGettingNewSecret) => {
136
188
  return findSensitiveValues(parsedRespContent);
137
189
  };
138
190
 
191
+ const isSafeAuthDataKey = (key) => {
192
+ if (SAFE_AUTH_DATA_KEYS.size === 0) {
193
+ initSafeAuthDataKeys();
194
+ }
195
+ return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
196
+ };
197
+
139
198
  const buildSensitiveValues = (event, data) => {
140
199
  const bundle = event.bundle || {};
141
200
  const authData = bundle.authData || {};
@@ -147,7 +206,9 @@ const buildSensitiveValues = (event, data) => {
147
206
  if (isSensitiveKey(key)) {
148
207
  return true;
149
208
  }
150
-
209
+ if (isSafeAuthDataKey(key)) {
210
+ return false;
211
+ }
151
212
  if (isUrl(value) && !isUrlWithSecrets(value)) {
152
213
  return false;
153
214
  }
@@ -0,0 +1,24 @@
1
+ import { expectType } from 'tsd';
2
+ import { console } from '..';
3
+
4
+ // Test that console is exported and has the right type
5
+ expectType<Console>(console);
6
+
7
+ // Test that console has expected methods with proper signatures
8
+ expectType<{
9
+ (...data: any[]): void;
10
+ (message?: any, ...optionalParams: any[]): void;
11
+ }>(console.log);
12
+ expectType<{
13
+ (...data: any[]): void;
14
+ (message?: any, ...optionalParams: any[]): void;
15
+ }>(console.error);
16
+ expectType<{
17
+ (...data: any[]): void;
18
+ (message?: any, ...optionalParams: any[]): void;
19
+ }>(console.warn);
20
+
21
+ // Test that console can be used for logging
22
+ console.log('test message');
23
+ console.error('test error');
24
+ console.warn('test warning');
package/types/custom.d.ts CHANGED
@@ -10,13 +10,10 @@ import { Headers } from 'node-fetch';
10
10
 
11
11
  // The EXPORTED OBJECT
12
12
  export const version: string;
13
- export const tools: {
14
- env: { inject: (filename?: string) => void };
15
- };
13
+ export const tools: { env: { inject: (filename?: string) => void } };
14
+ export const console: Console;
16
15
  export const errors: ErrorsModule;
17
16
 
18
-
19
-
20
17
  // see: https://github.com/zapier/zapier-platform-cli/issues/339#issue-336888249
21
18
  export const createAppTester: (
22
19
  appRaw: object,
@@ -118,9 +118,17 @@ export type CreatePerformGet<
118
118
  > = (z: ZObject, bundle: Bundle<$InputData>) => $Return | Promise<$Return>;
119
119
 
120
120
  /**
121
- * Search for objects on a partner API.
121
+ * Helper type for search results that can optionally include pagination.
122
+ * Returns either:
123
+ * - an array of objects, matching the search query.
124
+ * - an object with a `results` array of objects and a `paging_token` string.
122
125
  *
123
- * Returns an 1-item array of objects, matching the search query.
126
+ * When `canPaginate` is true for the search, the object shape is required.
127
+ */
128
+ type SearchResult<T> = T[] | { results: T[], paging_token: string };
129
+
130
+ /**
131
+ * Search for objects on a partner API.
124
132
  *
125
133
  * @remarks
126
134
  * This type requires a one-item array. Multiple items *can* be
@@ -130,7 +138,7 @@ export type CreatePerformGet<
130
138
  export type SearchPerform<
131
139
  $InputData extends DefaultInputData = DefaultInputData,
132
140
  $Return extends {} = {},
133
- > = (z: ZObject, bundle: Bundle<$InputData>) => $Return[] | Promise<$Return[]>;
141
+ > = (z: ZObject, bundle: Bundle<$InputData>) => SearchResult<$Return> | Promise<SearchResult<$Return>>;
134
142
 
135
143
  /**
136
144
  * Follow up a search's perform with additional data.
@@ -4,7 +4,7 @@
4
4
  * files, and/or the schema-to-ts tool and run its CLI to regenerate
5
5
  * these typings.
6
6
  *
7
- * zapier-platform-schema version: 17.4.0
7
+ * zapier-platform-schema version: 17.6.0
8
8
  * schema-to-ts compiler version: 0.1.0
9
9
  */
10
10
  import type {
@@ -1193,6 +1193,9 @@ export interface BasicSearchOperation<
1193
1193
  */
1194
1194
  performGet?: Request | SearchPerformGet<InferInputData<$InputFields>>;
1195
1195
 
1196
+ /** Does this search support pagination? */
1197
+ canPaginate?: boolean;
1198
+
1196
1199
  /** What should the form a user sees and configures look like? */
1197
1200
  inputFields?: $InputFields;
1198
1201
 
@@ -1,23 +0,0 @@
1
- 'use strict';
2
-
3
- const _ = require('lodash');
4
- const { simpleTruncate } = require('../tools/data');
5
-
6
- const isSearch = require('./is-search');
7
-
8
- /*
9
- Searches should always return an array of objects.
10
- */
11
- const searchIsArray = {
12
- name: 'triggerIsArray',
13
- shouldRun: isSearch,
14
- run: (method, results) => {
15
- if (!_.isArray(results)) {
16
- const repr = simpleTruncate(JSON.stringify(results), 50);
17
- return [`Results must be an array, got: ${typeof results}, (${repr})`];
18
- }
19
- return [];
20
- },
21
- };
22
-
23
- module.exports = searchIsArray;