zapier-platform-cli 17.2.0 → 17.3.1
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/oclif.manifest.json +88 -90
- package/package.json +3 -5
- package/src/constants.js +3 -10
- package/src/oclif/commands/build.js +13 -5
- package/src/oclif/commands/deprecate.js +3 -3
- package/src/oclif/commands/push.js +9 -1
- package/src/utils/build.js +525 -295
- package/src/utils/files.js +41 -7
- package/src/utils/ignore.js +29 -9
- package/src/utils/itertools.js +39 -0
- package/src/utils/misc.js +5 -4
- package/src/utils/zapierwrapper.js +1 -1
package/src/utils/build.js
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
const crypto = require('node:crypto');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const {
|
|
6
|
+
constants: { Z_BEST_COMPRESSION },
|
|
7
|
+
} = require('node:zlib');
|
|
4
8
|
|
|
5
9
|
const _ = require('lodash');
|
|
6
10
|
const archiver = require('archiver');
|
|
11
|
+
const colors = require('colors/safe');
|
|
7
12
|
const esbuild = require('esbuild');
|
|
8
|
-
const fs = require('fs');
|
|
9
13
|
const fse = require('fs-extra');
|
|
10
|
-
const klaw = require('klaw');
|
|
11
14
|
const updateNotifier = require('update-notifier');
|
|
12
|
-
const
|
|
13
|
-
const semver = require('semver');
|
|
14
|
-
const { minimatch } = require('minimatch');
|
|
15
|
+
const decompress = require('decompress');
|
|
15
16
|
|
|
16
17
|
const {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
BUILD_DIR,
|
|
19
|
+
BUILD_PATH,
|
|
20
|
+
PLATFORM_PACKAGE,
|
|
21
|
+
SOURCE_PATH,
|
|
22
|
+
UPDATE_NOTIFICATION_INTERVAL,
|
|
23
|
+
} = require('../constants');
|
|
24
|
+
const { copyDir, walkDir, walkDirLimitedLevels } = require('./files');
|
|
25
|
+
const { iterfilter, itermap } = require('./itertools');
|
|
23
26
|
|
|
24
27
|
const {
|
|
25
|
-
prettyJSONstringify,
|
|
26
|
-
startSpinner,
|
|
27
28
|
endSpinner,
|
|
28
29
|
flattenCheckResult,
|
|
30
|
+
prettyJSONstringify,
|
|
31
|
+
startSpinner,
|
|
29
32
|
} = require('./display');
|
|
30
33
|
|
|
31
34
|
const {
|
|
@@ -35,33 +38,23 @@ const {
|
|
|
35
38
|
validateApp,
|
|
36
39
|
} = require('./api');
|
|
37
40
|
|
|
38
|
-
const { copyZapierWrapper } = require('./zapierwrapper');
|
|
41
|
+
const { copyZapierWrapper, deleteZapierWrapper } = require('./zapierwrapper');
|
|
39
42
|
|
|
40
43
|
const checkMissingAppInfo = require('./check-missing-app-info');
|
|
41
44
|
|
|
42
|
-
const {
|
|
43
|
-
const { respectGitIgnore } = require('./ignore');
|
|
45
|
+
const { findCorePackageDir, isWindows, runCommand } = require('./misc');
|
|
46
|
+
const { isBlocklisted, respectGitIgnore } = require('./ignore');
|
|
44
47
|
const { localAppCommand } = require('./local');
|
|
45
48
|
|
|
46
49
|
const debug = require('debug')('zapier:build');
|
|
47
50
|
|
|
48
|
-
const stripPath = (cwd, filePath) => filePath.split(cwd).pop();
|
|
51
|
+
// const stripPath = (cwd, filePath) => filePath.split(cwd).pop();
|
|
49
52
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const appPackageJson = require(path.join(cwd, 'package.json'));
|
|
53
|
+
// Given entry points in a directory, return an array of file paths that are
|
|
54
|
+
// required for the build. The returned paths are relative to workingDir.
|
|
55
|
+
const findRequiredFiles = async (workingDir, entryPoints) => {
|
|
56
|
+
const appPackageJson = require(path.join(workingDir, 'package.json'));
|
|
57
57
|
const isESM = appPackageJson.type === 'module';
|
|
58
|
-
// Only include 'module' condition if the app is an ESM app (PDE-6187)
|
|
59
|
-
// otherwise exclude 'module' since it breaks the build for hybrid packages (like uuid)
|
|
60
|
-
// in CJS apps by only including ESM version of packages.
|
|
61
|
-
// An empty list is necessary because otherwise if `platform: node` is specified,
|
|
62
|
-
// the 'module' condition is included by default.
|
|
63
|
-
// https://esbuild.github.io/api/#conditions
|
|
64
|
-
const conditions = isESM ? ['module'] : [];
|
|
65
58
|
const format = isESM ? 'esm' : 'cjs';
|
|
66
59
|
|
|
67
60
|
const result = await esbuild.build({
|
|
@@ -71,111 +64,245 @@ const requiredFiles = async ({ cwd, entryPoints }) => {
|
|
|
71
64
|
platform: 'node',
|
|
72
65
|
metafile: true,
|
|
73
66
|
logLevel: 'warning',
|
|
67
|
+
logOverride: {
|
|
68
|
+
'require-resolve-not-external': 'silent',
|
|
69
|
+
},
|
|
74
70
|
external: ['../test/userapp'],
|
|
75
71
|
format,
|
|
76
|
-
conditions,
|
|
72
|
+
// Setting conditions to an empty array to exclude 'module' condition,
|
|
73
|
+
// which Node.js doesn't use. https://esbuild.github.io/api/#conditions
|
|
74
|
+
conditions: [],
|
|
77
75
|
write: false, // no need to write outfile
|
|
78
|
-
absWorkingDir:
|
|
76
|
+
absWorkingDir: workingDir,
|
|
79
77
|
tsconfigRaw: '{}',
|
|
80
78
|
});
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
let relPaths = Object.keys(result.metafile.inputs);
|
|
81
|
+
if (path.sep === '\\') {
|
|
82
|
+
// The paths in result.metafile.inputs use forward slashes even on Windows,
|
|
83
|
+
// path.normalize() will convert them to backslashes.
|
|
84
|
+
relPaths = relPaths.map((x) => path.normalize(x));
|
|
85
|
+
}
|
|
86
|
+
return relPaths;
|
|
85
87
|
};
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
// From a file path relative to workingDir, traverse up the directory tree until
|
|
90
|
+
// it finds a directory that looks like a package directory, which either
|
|
91
|
+
// contains a package.json file or whose path matches a pattern like
|
|
92
|
+
// 'node_modules/(@scope/)package-name'.
|
|
93
|
+
// Returns null if no package directory is found.
|
|
94
|
+
const getPackageDir = (workingDir, relPath) => {
|
|
95
|
+
const nm = `node_modules${path.sep}`;
|
|
96
|
+
let i = relPath.lastIndexOf(nm);
|
|
97
|
+
if (i < 0) {
|
|
98
|
+
let dir = path.dirname(relPath);
|
|
99
|
+
for (let j = 0; j < 100; j++) {
|
|
100
|
+
const packageJsonPath = path.resolve(workingDir, dir, 'package.json');
|
|
101
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
102
|
+
return dir;
|
|
103
|
+
}
|
|
104
|
+
const nextDir = path.dirname(dir);
|
|
105
|
+
if (nextDir === dir) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
dir = nextDir;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
109
|
-
});
|
|
113
|
+
i += nm.length;
|
|
114
|
+
if (relPath[i] === '@') {
|
|
115
|
+
// For scoped package, e.g. node_modules/@zapier/package-name
|
|
116
|
+
const j = relPath.indexOf(path.sep, i + 1);
|
|
117
|
+
if (j < 0) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
i = j + 1; // skip the next path.sep
|
|
121
|
+
}
|
|
122
|
+
const j = relPath.indexOf(path.sep, i);
|
|
123
|
+
if (j < 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return relPath.substring(0, j);
|
|
110
127
|
};
|
|
111
128
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
129
|
+
function expandRequiredFiles(workingDir, relPaths) {
|
|
130
|
+
const expandedPaths = new Set(relPaths);
|
|
131
|
+
for (const relPath of relPaths) {
|
|
132
|
+
const packageDir = getPackageDir(workingDir, relPath);
|
|
133
|
+
if (packageDir) {
|
|
134
|
+
expandedPaths.add(path.join(packageDir, 'package.json'));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return expandedPaths;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Yields files and symlinks (as fs.Direnv objects) from a directory
|
|
141
|
+
// recursively, excluding names that are typically not needed in the build,
|
|
142
|
+
// such as .git, .env, build, etc.
|
|
143
|
+
function* walkDirWithPresetBlocklist(dir) {
|
|
144
|
+
const shouldInclude = (entry) => {
|
|
145
|
+
const relPath = path.relative(dir, path.join(entry.parentPath, entry.name));
|
|
146
|
+
return !isBlocklisted(relPath);
|
|
147
|
+
};
|
|
148
|
+
yield* iterfilter(shouldInclude, walkDir(dir));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Yields files and symlinks (as fs.Direnv objects) from a directory recursively
|
|
152
|
+
// that match any of the given or preset regex patterns.
|
|
153
|
+
function* walkDirWithPatterns(dir, patterns) {
|
|
154
|
+
const sep = path.sep.replaceAll('\\', '\\\\'); // escape backslash for regex
|
|
155
|
+
const presetPatterns = [
|
|
156
|
+
`${sep}definition\\.json$`,
|
|
157
|
+
`${sep}package\\.json$`,
|
|
158
|
+
`${sep}aws-sdk${sep}apis${sep}.*\\.json$`,
|
|
159
|
+
];
|
|
160
|
+
patterns = [...presetPatterns, ...(patterns || [])].map(
|
|
161
|
+
(x) => new RegExp(x, 'i'),
|
|
162
|
+
);
|
|
163
|
+
const shouldInclude = (entry) => {
|
|
164
|
+
const relPath = path.join(entry.parentPath, entry.name);
|
|
165
|
+
if (isBlocklisted(relPath)) {
|
|
118
166
|
return false;
|
|
119
167
|
}
|
|
120
|
-
|
|
168
|
+
for (const pattern of patterns) {
|
|
169
|
+
if (pattern.test(relPath)) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
};
|
|
175
|
+
yield* iterfilter(shouldInclude, walkDir(dir));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Opens a zip file for writing. Returns an Archiver object.
|
|
179
|
+
const openZip = (outputPath) => {
|
|
180
|
+
const output = fs.createWriteStream(outputPath);
|
|
181
|
+
const zip = archiver('zip', {
|
|
182
|
+
zlib: { level: Z_BEST_COMPRESSION }, // Sets the compression level.
|
|
121
183
|
});
|
|
122
184
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
185
|
+
const streamCompletePromise = new Promise((resolve, reject) => {
|
|
186
|
+
output.on('close', resolve);
|
|
187
|
+
zip.on('error', reject);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
zip.finish = async () => {
|
|
191
|
+
// zip.finalize() doesn't return a promise, so here we create a
|
|
192
|
+
// zip.finish() function so the caller can await it.
|
|
193
|
+
// So callers: Use `await zip.finish()` and avoid zip.finalize().
|
|
194
|
+
zip.finalize();
|
|
195
|
+
await streamCompletePromise;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (path.sep === '\\') {
|
|
199
|
+
// On Windows, patch zip.file() and zip.symlink() so they normalize the path
|
|
200
|
+
// separator to '/' because we're supposed to use '/' in a zip file
|
|
201
|
+
// regardless of the OS platform. Those are the only two methods we're
|
|
202
|
+
// currently using. If you wanted to call other zip.xxx methods, you should
|
|
203
|
+
// patch them here as well.
|
|
204
|
+
const origFileMethod = zip.file;
|
|
205
|
+
zip.file = (filepath, data) => {
|
|
206
|
+
filepath = path.normalize(filepath);
|
|
207
|
+
return origFileMethod.call(zip, filepath, data);
|
|
208
|
+
};
|
|
209
|
+
const origSymlinkMethod = zip.symlink;
|
|
210
|
+
zip.symlink = (name, target, mode) => {
|
|
211
|
+
name = path.normalize(name);
|
|
212
|
+
target = path.normalize(target);
|
|
213
|
+
return origSymlinkMethod.call(zip, name, target, mode);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
zip.pipe(output);
|
|
218
|
+
return zip;
|
|
141
219
|
};
|
|
142
220
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
zlib: { level: Z_BEST_COMPRESSION },
|
|
148
|
-
});
|
|
221
|
+
const looksLikeWorkspaceRoot = async (dir) => {
|
|
222
|
+
if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
149
225
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
226
|
+
if (fs.existsSync(path.join(dir, 'lerna.json'))) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
154
229
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
230
|
+
const packageJsonPath = path.join(dir, 'package.json');
|
|
231
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
158
234
|
|
|
159
|
-
|
|
160
|
-
|
|
235
|
+
let packageJson;
|
|
236
|
+
try {
|
|
237
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
238
|
+
} catch (err) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
161
241
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (basePath === '.') {
|
|
165
|
-
basePath = undefined;
|
|
166
|
-
}
|
|
167
|
-
const name = path.join(dir, filePath);
|
|
168
|
-
zip.file(name, { name: filePath, mode: 0o755 });
|
|
169
|
-
});
|
|
242
|
+
return packageJson?.workspaces != null;
|
|
243
|
+
};
|
|
170
244
|
|
|
171
|
-
|
|
172
|
-
|
|
245
|
+
// Traverses up the directory tree to find the workspace root. The workspace
|
|
246
|
+
// root directory either:
|
|
247
|
+
// - contains pnpm-workspace.yaml
|
|
248
|
+
// - contains a package.json file with a "workspaces" field
|
|
249
|
+
// - contains lerna.json
|
|
250
|
+
// Returns the absolute path to the workspace root directory, or null if not
|
|
251
|
+
// found.
|
|
252
|
+
const findWorkspaceRoot = async (workingDir) => {
|
|
253
|
+
let dir = workingDir;
|
|
254
|
+
for (let i = 0; i < 500; i++) {
|
|
255
|
+
if (await looksLikeWorkspaceRoot(dir)) {
|
|
256
|
+
return dir;
|
|
257
|
+
}
|
|
258
|
+
if (dir === '/' || dir.match(/^[a-z]:\\$/i)) {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
dir = path.dirname(dir);
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
173
264
|
};
|
|
174
265
|
|
|
175
|
-
const
|
|
176
|
-
|
|
266
|
+
const getNearestNodeModulesDir = (workingDir, relPath) => {
|
|
267
|
+
if (path.basename(relPath) === 'package.json') {
|
|
268
|
+
const nmDir = path.resolve(
|
|
269
|
+
workingDir,
|
|
270
|
+
path.dirname(relPath),
|
|
271
|
+
'node_modules',
|
|
272
|
+
);
|
|
273
|
+
return fs.existsSync(nmDir) ? path.relative(workingDir, nmDir) : null;
|
|
274
|
+
} else {
|
|
275
|
+
let dir = path.dirname(relPath);
|
|
276
|
+
for (let i = 0; i < 100; i++) {
|
|
277
|
+
if (dir.endsWith(`${path.sep}node_modules`)) {
|
|
278
|
+
return dir;
|
|
279
|
+
}
|
|
280
|
+
const nextDir = path.dirname(dir);
|
|
281
|
+
if (nextDir === dir) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
dir = nextDir;
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
177
289
|
|
|
178
|
-
|
|
290
|
+
const writeBuildZipDumbly = async (workingDir, zip) => {
|
|
291
|
+
for (const entry of walkDirWithPresetBlocklist(workingDir)) {
|
|
292
|
+
const absPath = path.resolve(entry.parentPath, entry.name);
|
|
293
|
+
const relPath = path.relative(workingDir, absPath);
|
|
294
|
+
if (entry.isFile()) {
|
|
295
|
+
zip.file(absPath, { name: relPath });
|
|
296
|
+
} else if (entry.isSymbolicLink()) {
|
|
297
|
+
const target = path.relative(entry.parentPath, fs.realpathSync(absPath));
|
|
298
|
+
zip.symlink(relPath, target, 0o644);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const writeBuildZipSmartly = async (workingDir, zip) => {
|
|
304
|
+
const entryPoints = [path.resolve(workingDir, 'zapierwrapper.js')];
|
|
305
|
+
const indexPath = path.resolve(workingDir, 'index.js');
|
|
179
306
|
if (fs.existsSync(indexPath)) {
|
|
180
307
|
// Necessary for CommonJS integrations. The zapierwrapper they use require()
|
|
181
308
|
// the index.js file using a variable. esbuild can't detect it, so we need
|
|
@@ -183,39 +310,143 @@ const makeZip = async (dir, zipPath, disableDependencyDetection) => {
|
|
|
183
310
|
entryPoints.push(indexPath);
|
|
184
311
|
}
|
|
185
312
|
|
|
186
|
-
|
|
313
|
+
const appConfig = await getLinkedAppConfig(workingDir, false);
|
|
314
|
+
const relPaths = Array.from(
|
|
315
|
+
new Set([
|
|
316
|
+
// Files found by esbuild and their package.json files
|
|
317
|
+
...expandRequiredFiles(
|
|
318
|
+
workingDir,
|
|
319
|
+
await findRequiredFiles(workingDir, entryPoints),
|
|
320
|
+
),
|
|
321
|
+
// Files matching includeInBuild and other preset patterns
|
|
322
|
+
...itermap(
|
|
323
|
+
(entry) =>
|
|
324
|
+
path.relative(workingDir, path.join(entry.parentPath, entry.name)),
|
|
325
|
+
walkDirWithPatterns(workingDir, appConfig?.includeInBuild),
|
|
326
|
+
),
|
|
327
|
+
]),
|
|
328
|
+
).sort();
|
|
329
|
+
|
|
330
|
+
const workspaceRoot = (await findWorkspaceRoot(workingDir)) || workingDir;
|
|
331
|
+
|
|
332
|
+
if (workspaceRoot !== workingDir) {
|
|
333
|
+
const appDirRelPath = path.relative(workspaceRoot, workingDir);
|
|
334
|
+
// zapierwrapper.js and index.js are entry points.
|
|
335
|
+
// 'config' is the default directory that the 'config' npm package expects
|
|
336
|
+
// to find config files at the root directory.
|
|
337
|
+
const linkNames = ['zapierwrapper.js', 'index.js', 'config'];
|
|
338
|
+
for (const name of linkNames) {
|
|
339
|
+
if (fs.existsSync(path.join(workingDir, name))) {
|
|
340
|
+
zip.symlink(name, path.join(appDirRelPath, name), 0o644);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
187
343
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
344
|
+
const filenames = ['package.json', 'definition.json'];
|
|
345
|
+
for (const name of filenames) {
|
|
346
|
+
const absPath = path.resolve(workingDir, name);
|
|
347
|
+
zip.file(absPath, { name, mode: 0o644 });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Write required files to the zip
|
|
352
|
+
for (const relPath of relPaths) {
|
|
353
|
+
const absPath = path.resolve(workingDir, relPath);
|
|
354
|
+
const nameInZip = path.relative(workspaceRoot, absPath);
|
|
355
|
+
if (nameInZip === 'package.json' && workspaceRoot !== workingDir) {
|
|
356
|
+
// Ignore workspace root's package.json
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
zip.file(absPath, { name: nameInZip, mode: 0o644 });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Next, find all symlinks that are either: (1) immediate children of any
|
|
363
|
+
// node_modules directory, or (2) located one directory level below a
|
|
364
|
+
// node_modules directory. (1) is for the case of node_modules/package_name.
|
|
365
|
+
// (2) is for the case of node_modules/@scope/package_name.
|
|
366
|
+
const nodeModulesDirs = new Set();
|
|
367
|
+
for (const relPath of relPaths) {
|
|
368
|
+
const nmDir = getNearestNodeModulesDir(workingDir, relPath);
|
|
369
|
+
if (nmDir) {
|
|
370
|
+
nodeModulesDirs.add(nmDir);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
for (const relNmDir of nodeModulesDirs) {
|
|
375
|
+
const absNmDir = path.resolve(workingDir, relNmDir);
|
|
376
|
+
const symlinks = iterfilter(
|
|
377
|
+
(entry) => {
|
|
378
|
+
// Only include symlinks that are not in node_modules/.bin directories
|
|
379
|
+
return (
|
|
380
|
+
entry.isSymbolicLink() &&
|
|
381
|
+
!entry.parentPath.endsWith(`${path.sep}node_modules${path.sep}.bin`)
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
walkDirLimitedLevels(absNmDir, 2),
|
|
385
|
+
);
|
|
386
|
+
for (const symlink of symlinks) {
|
|
387
|
+
const absPath = path.resolve(
|
|
388
|
+
workingDir,
|
|
389
|
+
symlink.parentPath,
|
|
390
|
+
symlink.name,
|
|
391
|
+
);
|
|
392
|
+
const nameInZip = path.relative(workspaceRoot, absPath);
|
|
393
|
+
const targetInZip = path.relative(
|
|
394
|
+
symlink.parentPath,
|
|
395
|
+
fs.realpathSync(absPath),
|
|
396
|
+
);
|
|
397
|
+
zip.symlink(nameInZip, targetInZip, 0o644);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// Creates the build.zip file.
|
|
403
|
+
const makeBuildZip = async (
|
|
404
|
+
workingDir,
|
|
405
|
+
zipPath,
|
|
406
|
+
disableDependencyDetection,
|
|
407
|
+
) => {
|
|
408
|
+
const zip = openZip(zipPath);
|
|
193
409
|
|
|
194
410
|
if (disableDependencyDetection) {
|
|
195
|
-
|
|
411
|
+
// Ideally, if dependency detection works really well, we don't need to
|
|
412
|
+
// support --disable-dependency-detection at all. We might want to phase out
|
|
413
|
+
// this code path over time. Also, this doesn't handle workspaces.
|
|
414
|
+
await writeBuildZipDumbly(workingDir, zip);
|
|
196
415
|
} else {
|
|
197
|
-
|
|
198
|
-
dumbPaths.filter(forceIncludeDumbPath.bind(null, appConfig)),
|
|
199
|
-
);
|
|
200
|
-
finalPaths = _.uniq(finalPaths);
|
|
201
|
-
finalPaths.sort();
|
|
202
|
-
debug('\nZip files:');
|
|
203
|
-
finalPaths.forEach((filePath) => debug(` ${filePath}`));
|
|
204
|
-
debug('');
|
|
205
|
-
paths = finalPaths;
|
|
416
|
+
await writeBuildZipSmartly(workingDir, zip);
|
|
206
417
|
}
|
|
207
418
|
|
|
208
|
-
await
|
|
419
|
+
await zip.finish();
|
|
209
420
|
};
|
|
210
421
|
|
|
211
|
-
const makeSourceZip = async (
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
422
|
+
const makeSourceZip = async (workingDir, zipPath) => {
|
|
423
|
+
const relPaths = Array.from(
|
|
424
|
+
itermap(
|
|
425
|
+
(entry) =>
|
|
426
|
+
path.relative(workingDir, path.join(entry.parentPath, entry.name)),
|
|
427
|
+
walkDirWithPresetBlocklist(workingDir),
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
const finalRelPaths = respectGitIgnore(workingDir, relPaths).sort();
|
|
431
|
+
|
|
432
|
+
const zip = openZip(zipPath);
|
|
433
|
+
|
|
434
|
+
debug('\nSource files:');
|
|
435
|
+
for (const relPath of finalRelPaths) {
|
|
436
|
+
if (relPath === 'definition.json' || relPath === 'zapierwrapper.js') {
|
|
437
|
+
// These two files are generated at build time;
|
|
438
|
+
// they're not part of the source code.
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const absPath = path.resolve(workingDir, relPath);
|
|
443
|
+
debug(` ${absPath}`);
|
|
444
|
+
|
|
445
|
+
zip.file(absPath, { name: relPath, mode: 0o644 });
|
|
446
|
+
}
|
|
217
447
|
debug();
|
|
218
|
-
|
|
448
|
+
|
|
449
|
+
await zip.finish();
|
|
219
450
|
};
|
|
220
451
|
|
|
221
452
|
const maybeNotifyAboutOutdated = () => {
|
|
@@ -223,19 +454,19 @@ const maybeNotifyAboutOutdated = () => {
|
|
|
223
454
|
// `build` won't run if package.json isn't there, so if we get to here we're good
|
|
224
455
|
const requiredVersion = _.get(
|
|
225
456
|
require(path.resolve('./package.json')),
|
|
226
|
-
`dependencies.${
|
|
457
|
+
`dependencies.${PLATFORM_PACKAGE}`,
|
|
227
458
|
);
|
|
228
459
|
|
|
229
460
|
if (requiredVersion) {
|
|
230
461
|
const notifier = updateNotifier({
|
|
231
|
-
pkg: { name:
|
|
232
|
-
updateCheckInterval:
|
|
462
|
+
pkg: { name: PLATFORM_PACKAGE, version: requiredVersion },
|
|
463
|
+
updateCheckInterval: UPDATE_NOTIFICATION_INTERVAL,
|
|
233
464
|
});
|
|
234
465
|
|
|
235
466
|
if (notifier.update && notifier.update.latest !== requiredVersion) {
|
|
236
467
|
notifier.notify({
|
|
237
468
|
message: `There's a newer version of ${colors.cyan(
|
|
238
|
-
|
|
469
|
+
PLATFORM_PACKAGE,
|
|
239
470
|
)} available.\nConsider updating the dependency in your\n${colors.cyan(
|
|
240
471
|
'package.json',
|
|
241
472
|
)} (${colors.grey(notifier.update.current)} → ${colors.green(
|
|
@@ -256,31 +487,87 @@ const maybeRunBuildScript = async (options = {}) => {
|
|
|
256
487
|
);
|
|
257
488
|
|
|
258
489
|
if (_.get(pJson, ['scripts', ZAPIER_BUILD_KEY])) {
|
|
259
|
-
|
|
490
|
+
if (options.printProgress) {
|
|
491
|
+
startSpinner(`Running ${ZAPIER_BUILD_KEY} script`);
|
|
492
|
+
}
|
|
493
|
+
|
|
260
494
|
await runCommand('npm', ['run', ZAPIER_BUILD_KEY], options);
|
|
261
|
-
|
|
495
|
+
|
|
496
|
+
if (options.printProgress) {
|
|
497
|
+
endSpinner();
|
|
498
|
+
}
|
|
262
499
|
}
|
|
263
500
|
}
|
|
264
501
|
};
|
|
265
502
|
|
|
266
|
-
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
503
|
+
const extractMissingModulePath = (testDir, error) => {
|
|
504
|
+
// Extract relative path to print a more user-friendly error message
|
|
505
|
+
if (error.message && error.message.includes('MODULE_NOT_FOUND')) {
|
|
506
|
+
const searchString = `Cannot find module '${testDir}/`;
|
|
507
|
+
const idx = error.message.indexOf(searchString);
|
|
508
|
+
if (idx >= 0) {
|
|
509
|
+
const pathStart = idx + searchString.length;
|
|
510
|
+
const pathEnd = error.message.indexOf("'", pathStart);
|
|
511
|
+
if (pathEnd >= 0) {
|
|
512
|
+
const relPath = error.message.substring(pathStart, pathEnd);
|
|
513
|
+
return relPath;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
272
516
|
}
|
|
517
|
+
return null;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const testBuildZip = async (zipPath) => {
|
|
521
|
+
const osTmpDir = await fse.realpath(os.tmpdir());
|
|
522
|
+
const testDir = path.join(
|
|
523
|
+
osTmpDir,
|
|
524
|
+
'zapier-' + crypto.randomBytes(4).toString('hex'),
|
|
525
|
+
);
|
|
273
526
|
|
|
274
|
-
let packageJson;
|
|
275
527
|
try {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return [];
|
|
279
|
-
}
|
|
528
|
+
await fse.ensureDir(testDir);
|
|
529
|
+
await decompress(zipPath, testDir);
|
|
280
530
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
531
|
+
const wrapperPath = path.join(testDir, 'zapierwrapper.js');
|
|
532
|
+
if (!fs.existsSync(wrapperPath)) {
|
|
533
|
+
throw new Error('zapierwrapper.js not found in build.zip.');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const indexPath = path.join(testDir, 'index.js');
|
|
537
|
+
const indexExists = fs.existsSync(indexPath);
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
await runCommand(process.execPath, ['zapierwrapper.js'], {
|
|
541
|
+
cwd: testDir,
|
|
542
|
+
timeout: 5000,
|
|
543
|
+
});
|
|
544
|
+
if (indexExists) {
|
|
545
|
+
await runCommand(process.execPath, ['index.js'], {
|
|
546
|
+
cwd: testDir,
|
|
547
|
+
timeout: 5000,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
} catch (error) {
|
|
551
|
+
// Extract relative path to print a more user-friendly error message
|
|
552
|
+
const relPath = extractMissingModulePath(testDir, error);
|
|
553
|
+
if (relPath) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Detected a missing file in build.zip: '${relPath}'\n` +
|
|
556
|
+
`You may have to add it to ${colors.bold.underline('includeInBuild')} ` +
|
|
557
|
+
`in your ${colors.bold.underline('.zapierapprc')} file.`,
|
|
558
|
+
);
|
|
559
|
+
} else if (error.message) {
|
|
560
|
+
// Hide the unzipped temporary directory
|
|
561
|
+
error.message = error.message
|
|
562
|
+
.replaceAll(`file://${testDir}/`, '')
|
|
563
|
+
.replaceAll(`${testDir}/`, '');
|
|
564
|
+
}
|
|
565
|
+
throw error;
|
|
566
|
+
}
|
|
567
|
+
} finally {
|
|
568
|
+
// Clean up test directory
|
|
569
|
+
await fse.remove(testDir);
|
|
570
|
+
}
|
|
284
571
|
};
|
|
285
572
|
|
|
286
573
|
const _buildFunc = async ({
|
|
@@ -290,129 +577,83 @@ const _buildFunc = async ({
|
|
|
290
577
|
printProgress = true,
|
|
291
578
|
checkOutdated = true,
|
|
292
579
|
} = {}) => {
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const wdir = process.cwd();
|
|
296
|
-
|
|
297
|
-
const osTmpDir = await fse.realpath(os.tmpdir());
|
|
298
|
-
const tmpDir = path.join(
|
|
299
|
-
osTmpDir,
|
|
300
|
-
'zapier-' + crypto.randomBytes(4).toString('hex'),
|
|
301
|
-
);
|
|
302
|
-
debug('Using temp directory: ', tmpDir);
|
|
580
|
+
const maybeStartSpinner = printProgress ? startSpinner : () => {};
|
|
581
|
+
const maybeEndSpinner = printProgress ? endSpinner : () => {};
|
|
303
582
|
|
|
304
583
|
if (checkOutdated) {
|
|
305
584
|
maybeNotifyAboutOutdated();
|
|
306
585
|
}
|
|
586
|
+
const appDir = process.cwd();
|
|
307
587
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
588
|
+
let workingDir;
|
|
589
|
+
if (skipNpmInstall) {
|
|
590
|
+
workingDir = appDir;
|
|
591
|
+
debug('Building in app directory: ', workingDir);
|
|
592
|
+
} else {
|
|
593
|
+
const osTmpDir = await fse.realpath(os.tmpdir());
|
|
594
|
+
workingDir = path.join(
|
|
595
|
+
osTmpDir,
|
|
596
|
+
'zapier-' + crypto.randomBytes(4).toString('hex'),
|
|
597
|
+
);
|
|
598
|
+
debug('Building in temp directory: ', workingDir);
|
|
316
599
|
}
|
|
317
600
|
|
|
318
|
-
|
|
319
|
-
? (src) => !src.endsWith('.zip')
|
|
320
|
-
: undefined;
|
|
321
|
-
|
|
322
|
-
await copyDir(wdir, tmpDir, { filter: copyFilter });
|
|
601
|
+
await maybeRunBuildScript({ printProgress });
|
|
323
602
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (wdir !== workspaceRoot) {
|
|
329
|
-
// If we're in here, it means the user is using npm/yarn workspaces
|
|
330
|
-
const workspaces = listWorkspaces(workspaceRoot);
|
|
331
|
-
|
|
332
|
-
await copyDir(nodeModulesDir, path.join(tmpDir, 'node_modules'), {
|
|
333
|
-
filter: (src) => {
|
|
334
|
-
if (src.endsWith('.zip')) {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
const stat = fse.lstatSync(src);
|
|
338
|
-
if (stat.isSymbolicLink()) {
|
|
339
|
-
const realPath = path.resolve(
|
|
340
|
-
path.dirname(src),
|
|
341
|
-
fse.readlinkSync(src),
|
|
342
|
-
);
|
|
343
|
-
for (const workspace of workspaces) {
|
|
344
|
-
// Use minimatch to do glob pattern match. If match, it means the
|
|
345
|
-
// symlink points to a workspace package, so we don't copy it.
|
|
346
|
-
if (minimatch(realPath, workspace)) {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return true;
|
|
352
|
-
},
|
|
353
|
-
onDirExists: (dir) => {
|
|
354
|
-
// Don't overwrite existing sub-directories in node_modules
|
|
355
|
-
return false;
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
603
|
+
// make sure our directories are there
|
|
604
|
+
await fse.ensureDir(workingDir);
|
|
605
|
+
const buildDir = path.join(appDir, BUILD_DIR);
|
|
606
|
+
await fse.ensureDir(buildDir);
|
|
360
607
|
|
|
361
|
-
let output = {};
|
|
362
608
|
if (!skipNpmInstall) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
609
|
+
maybeStartSpinner('Copying project to temp directory');
|
|
610
|
+
const copyFilter = (src) => !src.endsWith('.zip');
|
|
611
|
+
await copyDir(appDir, workingDir, { filter: copyFilter });
|
|
612
|
+
|
|
613
|
+
maybeEndSpinner();
|
|
614
|
+
maybeStartSpinner('Installing project dependencies');
|
|
615
|
+
const output = await runCommand('npm', ['install', '--production'], {
|
|
616
|
+
cwd: workingDir,
|
|
369
617
|
});
|
|
370
|
-
}
|
|
371
618
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
'Could not install dependencies properly. Error log:\n' + output.stderr,
|
|
381
|
-
);
|
|
619
|
+
// `npm install` may fail silently without returning a non-zero exit code,
|
|
620
|
+
// need to check further here
|
|
621
|
+
const corePath = path.join(workingDir, 'node_modules', PLATFORM_PACKAGE);
|
|
622
|
+
if (!fs.existsSync(corePath)) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
'Could not install dependencies properly. Error log:\n' + output.stderr,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
382
627
|
}
|
|
383
628
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
startSpinner('Applying entry point files');
|
|
387
|
-
}
|
|
629
|
+
maybeEndSpinner();
|
|
630
|
+
maybeStartSpinner('Applying entry point files');
|
|
388
631
|
|
|
389
|
-
|
|
632
|
+
const corePath = findCorePackageDir(workingDir);
|
|
633
|
+
await copyZapierWrapper(corePath, workingDir);
|
|
390
634
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
startSpinner('Building app definition.json');
|
|
394
|
-
}
|
|
635
|
+
maybeEndSpinner();
|
|
636
|
+
maybeStartSpinner('Building app definition.json');
|
|
395
637
|
|
|
396
638
|
const rawDefinition = await localAppCommand(
|
|
397
639
|
{ command: 'definition' },
|
|
398
|
-
|
|
640
|
+
workingDir,
|
|
399
641
|
false,
|
|
400
642
|
);
|
|
401
|
-
const fileWriteError = await writeFile(
|
|
402
|
-
path.join(tmpDir, 'definition.json'),
|
|
403
|
-
prettyJSONstringify(rawDefinition),
|
|
404
|
-
);
|
|
405
643
|
|
|
406
|
-
|
|
407
|
-
|
|
644
|
+
try {
|
|
645
|
+
fs.writeFileSync(
|
|
646
|
+
path.join(workingDir, 'definition.json'),
|
|
647
|
+
prettyJSONstringify(rawDefinition),
|
|
648
|
+
);
|
|
649
|
+
} catch (err) {
|
|
650
|
+
debug('\nFile Write Error:\n', err, '\n');
|
|
408
651
|
throw new Error(
|
|
409
|
-
`Unable to write ${
|
|
652
|
+
`Unable to write ${workingDir}/definition.json, please check file permissions!`,
|
|
410
653
|
);
|
|
411
654
|
}
|
|
412
655
|
|
|
413
|
-
|
|
414
|
-
endSpinner();
|
|
415
|
-
}
|
|
656
|
+
maybeEndSpinner();
|
|
416
657
|
|
|
417
658
|
if (!skipValidation) {
|
|
418
659
|
/**
|
|
@@ -421,12 +662,10 @@ const _buildFunc = async ({
|
|
|
421
662
|
* (Remote - `validateApp`) Both the Schema, AppVersion, and Auths are validated
|
|
422
663
|
*/
|
|
423
664
|
|
|
424
|
-
|
|
425
|
-
startSpinner('Validating project schema and style');
|
|
426
|
-
}
|
|
665
|
+
maybeStartSpinner('Validating project schema and style');
|
|
427
666
|
const validationErrors = await localAppCommand(
|
|
428
667
|
{ command: 'validate' },
|
|
429
|
-
|
|
668
|
+
workingDir,
|
|
430
669
|
false,
|
|
431
670
|
);
|
|
432
671
|
if (validationErrors.length) {
|
|
@@ -450,9 +689,8 @@ const _buildFunc = async ({
|
|
|
450
689
|
'We hit some style validation errors, try running `zapier validate` to see them!',
|
|
451
690
|
);
|
|
452
691
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
692
|
+
|
|
693
|
+
maybeEndSpinner();
|
|
456
694
|
|
|
457
695
|
if (_.get(styleChecksResponse, ['warnings', 'total_failures'])) {
|
|
458
696
|
console.log(colors.yellow('WARNINGS:'));
|
|
@@ -468,43 +706,35 @@ const _buildFunc = async ({
|
|
|
468
706
|
debug('\nWarning: Skipping Validation');
|
|
469
707
|
}
|
|
470
708
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
await
|
|
709
|
+
maybeStartSpinner('Zipping project and dependencies');
|
|
710
|
+
|
|
711
|
+
const zipPath = path.join(appDir, BUILD_PATH);
|
|
712
|
+
await makeBuildZip(workingDir, zipPath, disableDependencyDetection);
|
|
475
713
|
await makeSourceZip(
|
|
476
|
-
|
|
477
|
-
path.join(
|
|
714
|
+
workingDir,
|
|
715
|
+
path.join(appDir, SOURCE_PATH),
|
|
478
716
|
disableDependencyDetection,
|
|
479
717
|
);
|
|
480
718
|
|
|
481
|
-
|
|
482
|
-
endSpinner();
|
|
483
|
-
startSpinner('Testing build');
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (!isWindows()) {
|
|
487
|
-
// TODO err, what should we do on windows?
|
|
488
|
-
|
|
489
|
-
// tries to do a reproducible build at least
|
|
490
|
-
// https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files
|
|
491
|
-
// https://reproducible-builds.org/tools/ or strip-nondeterminism
|
|
492
|
-
await runCommand(
|
|
493
|
-
'find',
|
|
494
|
-
['.', '-exec', 'touch', '-t', '201601010000', '{}', '+'],
|
|
495
|
-
{ cwd: tmpDir },
|
|
496
|
-
);
|
|
497
|
-
}
|
|
719
|
+
maybeEndSpinner();
|
|
498
720
|
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
721
|
+
if (skipNpmInstall) {
|
|
722
|
+
maybeStartSpinner('Cleaning up temp files');
|
|
723
|
+
await deleteZapierWrapper(workingDir);
|
|
724
|
+
fs.rmSync(path.join(workingDir, 'definition.json'));
|
|
725
|
+
maybeEndSpinner();
|
|
726
|
+
} else {
|
|
727
|
+
maybeStartSpinner('Cleaning up temp directory');
|
|
728
|
+
await fse.remove(workingDir);
|
|
729
|
+
maybeEndSpinner();
|
|
502
730
|
}
|
|
503
731
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
732
|
+
if (!isWindows()) {
|
|
733
|
+
// "Testing build" doesn't work on Windows because of some permission issue
|
|
734
|
+
// with symlinks
|
|
735
|
+
maybeStartSpinner('Testing build');
|
|
736
|
+
await testBuildZip(zipPath);
|
|
737
|
+
maybeEndSpinner();
|
|
508
738
|
}
|
|
509
739
|
|
|
510
740
|
return zipPath;
|
|
@@ -535,9 +765,9 @@ const buildAndOrUpload = async (
|
|
|
535
765
|
|
|
536
766
|
module.exports = {
|
|
537
767
|
buildAndOrUpload,
|
|
538
|
-
|
|
768
|
+
findRequiredFiles,
|
|
769
|
+
makeBuildZip,
|
|
539
770
|
makeSourceZip,
|
|
540
|
-
listFiles,
|
|
541
|
-
requiredFiles,
|
|
542
771
|
maybeRunBuildScript,
|
|
772
|
+
walkDirWithPresetBlocklist,
|
|
543
773
|
};
|