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