@speedkit/cli 4.23.3 → 4.24.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.
Files changed (28) hide show
  1. package/.env.sample +7 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +1 -1
  4. package/dist/helpers/origin-proxy.d.ts +89 -0
  5. package/dist/helpers/origin-proxy.js +207 -0
  6. package/dist/helpers/origin-proxy.spec.d.ts +1 -0
  7. package/dist/helpers/origin-proxy.spec.js +167 -0
  8. package/dist/helpers/scrape.js +2 -2
  9. package/dist/helpers/site-analyzer.js +2 -2
  10. package/dist/services/bundler/bundle-service-factory.d.ts +2 -1
  11. package/dist/services/bundler/bundle-service-factory.js +4 -2
  12. package/dist/services/bundler/bundle-service.d.ts +30 -2
  13. package/dist/services/bundler/bundle-service.js +81 -6
  14. package/dist/services/bundler/bundle-service.spec.js +124 -0
  15. package/dist/services/document-handler-runtime/document-handler-server.d.ts +23 -3
  16. package/dist/services/document-handler-runtime/document-handler-server.js +66 -43
  17. package/dist/services/document-handler-runtime/factory/document-handler-runtime-service-factory.js +7 -2
  18. package/dist/services/onboarding/dashboard/request-diff-service.js +5 -11
  19. package/dist/services/onboarding/onboarding-service-factory.d.ts +0 -2
  20. package/dist/services/onboarding/onboarding-service-factory.js +5 -43
  21. package/dist/services/onboarding/virtual-orestes-app/crawler.d.ts +1 -3
  22. package/dist/services/onboarding/virtual-orestes-app/crawler.js +6 -10
  23. package/dist/services/onboarding/virtual-orestes-app/crawler.spec.d.ts +1 -0
  24. package/dist/services/onboarding/virtual-orestes-app/crawler.spec.js +49 -0
  25. package/dist/services/origin-request/origin-request-service-factory.js +5 -1
  26. package/dist/services/origin-request/origin-request-service.js +2 -1
  27. package/oclif.manifest.json +1 -1
  28. package/package.json +2 -1
@@ -5,13 +5,23 @@ import { BundleResolveError } from "./error/bundle-resolve-error.js";
5
5
  import { BundleFetchError } from "./error/bundle-fetch-error.js";
6
6
  import { resolve } from "node:path";
7
7
  import { createRequire } from "node:module";
8
+ const FALLBACK_NAMESPACE = "bundle-resolve-fallback";
9
+ const RELATIVE_SPECIFIER = /^\.{1,2}\//;
8
10
  const importCache = {};
9
11
  export class BundleService {
10
12
  moduleDiscoveryDir;
11
- constructor(moduleDiscoveryDir) {
13
+ additionalModulePaths;
14
+ /**
15
+ * @param moduleDiscoveryDir the directory bundled code resolves its own imports from
16
+ * @param additionalModulePaths extra `node_modules` directories to search, like `NODE_PATH`.
17
+ * `sk` installs the packages a customer config declares under `dependencies` into its cache
18
+ * rather than into the customer folder, so esbuild would otherwise never find them.
19
+ */
20
+ constructor(moduleDiscoveryDir, additionalModulePaths = []) {
12
21
  this.moduleDiscoveryDir = moduleDiscoveryDir;
22
+ this.additionalModulePaths = additionalModulePaths;
13
23
  }
14
- async bundle({ entryPoints, basePaths, define, target = "es5", minify = true, logLevel = "silent", alias = {}, platform, }) {
24
+ async bundle({ entryPoints, basePaths, define, target = "es5", minify = true, logLevel = "silent", alias = {}, platform, resolveFallback, }) {
15
25
  const plugins = [];
16
26
  if (basePaths && Object.keys(basePaths).length > 0) {
17
27
  plugins.push(this.baqendPlugin(basePaths));
@@ -19,6 +29,9 @@ export class BundleService {
19
29
  if (entryPoints) {
20
30
  plugins.push(this.entryPointsPlugin(entryPoints));
21
31
  }
32
+ if (resolveFallback) {
33
+ plugins.push(this.resolveFallbackPlugin(resolveFallback));
34
+ }
22
35
  const result = await build({
23
36
  plugins: plugins,
24
37
  bundle: true,
@@ -30,6 +43,7 @@ export class BundleService {
30
43
  entryPoints: Object.keys(entryPoints),
31
44
  platform: platform || "neutral",
32
45
  logLevel,
46
+ nodePaths: this.additionalModulePaths,
33
47
  alias: this.resolveAliases(alias),
34
48
  });
35
49
  return result.outputFiles
@@ -54,14 +68,32 @@ export class BundleService {
54
68
  }
55
69
  // The filename never has to exist; only its directory decides where the lookup starts.
56
70
  const requireFrom = createRequire(resolve(this.moduleDiscoveryDir, "noop.js"));
57
- return Object.fromEntries(Object.entries(alias).map(([from, to]) => {
71
+ return Object.fromEntries(Object.entries(alias).map(([from, to]) => [
72
+ from,
73
+ this.resolveAliasTarget(requireFrom, to) ?? to,
74
+ ]));
75
+ }
76
+ /**
77
+ * Looks the target up the way the bundled code would: the customer folder first, then the
78
+ * additional module paths. Returns `null` when it is installed nowhere, so the bare name reaches
79
+ * esbuild and is reported as before.
80
+ */
81
+ resolveAliasTarget(requireFrom, target) {
82
+ try {
83
+ return requireFrom.resolve(target);
84
+ }
85
+ catch {
86
+ // not in the customer folder — fall through to the additional module paths
87
+ }
88
+ for (const modulePath of this.additionalModulePaths) {
58
89
  try {
59
- return [from, requireFrom.resolve(to)];
90
+ return createRequire(resolve(modulePath, "noop.js")).resolve(target);
60
91
  }
61
92
  catch {
62
- return [from, to];
93
+ // keep looking
63
94
  }
64
- }));
95
+ }
96
+ return null;
65
97
  }
66
98
  entryPointsPlugin(entryPoints) {
67
99
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -126,4 +158,47 @@ export class BundleService {
126
158
  },
127
159
  };
128
160
  }
161
+ /**
162
+ * Resolves relative imports that exist nowhere in the customer folder through `resolveFallback`.
163
+ *
164
+ * A document handler shares its source with the Baqend app it is deployed to, so it may
165
+ * `require("./someModule")` a module that only lives in the app. Disk always wins: the fallback
166
+ * is only consulted after esbuild's own resolution has failed, and only for relative specifiers —
167
+ * a bare name is an npm package, and a missing one stays esbuild's error to report.
168
+ */
169
+ resolveFallbackPlugin(resolveFallback) {
170
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
171
+ const me = this;
172
+ return {
173
+ name: "resolveFallback",
174
+ setup(build) {
175
+ build.onResolve({ filter: RELATIVE_SPECIFIER }, async (arguments_) => {
176
+ // build.resolve() re-enters this hook; the flag makes the second pass a no-op.
177
+ if (arguments_.pluginData?.fallbackChecked) {
178
+ return;
179
+ }
180
+ const onDisk = await build.resolve(arguments_.path, {
181
+ importer: arguments_.importer,
182
+ kind: arguments_.kind,
183
+ resolveDir: arguments_.resolveDir,
184
+ pluginData: { fallbackChecked: true },
185
+ });
186
+ if (onDisk.errors.length === 0) {
187
+ return onDisk;
188
+ }
189
+ const contents = await resolveFallback(arguments_.path);
190
+ if (contents === null) {
191
+ return;
192
+ }
193
+ return { path: arguments_.path, namespace: FALLBACK_NAMESPACE };
194
+ });
195
+ build.onLoad({ filter: /.*/, namespace: FALLBACK_NAMESPACE }, async (arguments_) => ({
196
+ contents: await resolveFallback(arguments_.path),
197
+ loader: "js",
198
+ // Lets a fallback module require npm packages the customer folder installed.
199
+ resolveDir: resolve(me.moduleDiscoveryDir),
200
+ }));
201
+ },
202
+ };
203
+ }
129
204
  }
@@ -52,3 +52,127 @@ describe("BundleService alias resolution", () => {
52
52
  expect(String(error)).to.contain("not-installed-anywhere");
53
53
  });
54
54
  });
55
+ /**
56
+ * A document handler is shared with the Baqend app it is deployed to, so it can require a module
57
+ * that only exists there. The bundler must reach for that copy only after the customer folder has
58
+ * had its chance, and only for the relative specifiers Baqend modules are required by.
59
+ */
60
+ describe("BundleService resolve fallback", () => {
61
+ const bundleWithFallback = (source, fallback, moduleDiscoveryDir = process.cwd()) => new BundleService(moduleDiscoveryDir).bundle({
62
+ entryPoints: { "handler.js": source },
63
+ minify: false,
64
+ platform: "node",
65
+ logLevel: "silent",
66
+ resolveFallback: fallback,
67
+ });
68
+ it("bundles a relative module the customer folder does not have", async () => {
69
+ const bundle = await bundleWithFallback("module.exports = require('./checkS3Assets');", async () => 'module.exports = "from-the-app";');
70
+ expect(bundle).to.contain("from-the-app");
71
+ });
72
+ it("prefers a file on disk over the fallback", async () => {
73
+ const customerDirectory = mkdtempSync(join(tmpdir(), "sk-fallback-"));
74
+ writeFileSync(join(customerDirectory, "checkS3Assets.js"), 'module.exports = "from-the-customer-folder";');
75
+ let fallbackCalls = 0;
76
+ try {
77
+ const bundle = await bundleWithFallback("module.exports = require('./checkS3Assets');", async () => {
78
+ fallbackCalls++;
79
+ return 'module.exports = "from-the-app";';
80
+ }, customerDirectory);
81
+ expect(bundle).to.contain("from-the-customer-folder");
82
+ expect(bundle).to.not.contain("from-the-app");
83
+ expect(fallbackCalls).to.equal(0);
84
+ }
85
+ finally {
86
+ rmSync(customerDirectory, { recursive: true, force: true });
87
+ }
88
+ });
89
+ it("resolves a relative import made by a fallback module", async () => {
90
+ const modules = {
91
+ "./checkS3Assets": "module.exports = require('./s3Client');",
92
+ "./s3Client": 'module.exports = "nested-app-module";',
93
+ };
94
+ const bundle = await bundleWithFallback("module.exports = require('./checkS3Assets');", async (specifier) => modules[specifier] ?? null);
95
+ expect(bundle).to.contain("nested-app-module");
96
+ });
97
+ it("leaves a bare specifier to esbuild", async () => {
98
+ let fallbackCalls = 0;
99
+ const bundling = bundleWithFallback("module.exports = require('not-installed-anywhere');", async () => {
100
+ fallbackCalls++;
101
+ return 'module.exports = "from-the-app";';
102
+ });
103
+ const error = await bundling.then(() => undefined, (reason) => reason);
104
+ expect(String(error)).to.contain("not-installed-anywhere");
105
+ expect(fallbackCalls).to.equal(0);
106
+ });
107
+ it("reports esbuild's own error when the fallback has nothing", async () => {
108
+ const bundling = bundleWithFallback("module.exports = require('./checkS3Assets');", async () => null);
109
+ const error = await bundling.then(() => undefined, (reason) => reason);
110
+ expect(String(error)).to.contain("./checkS3Assets");
111
+ });
112
+ });
113
+ /**
114
+ * `sk` installs the packages a customer config declares under `dependencies` into its own cache,
115
+ * not into the customer folder, so the bundler needs that directory on its search path as well.
116
+ */
117
+ describe("BundleService additional module paths", () => {
118
+ const MARKER = "fixture-additional-dependency";
119
+ let cacheDirectory;
120
+ let customerDirectory;
121
+ let additionalModulePath;
122
+ before(() => {
123
+ cacheDirectory = mkdtempSync(join(tmpdir(), "sk-cache-"));
124
+ customerDirectory = mkdtempSync(join(tmpdir(), "sk-customer-"));
125
+ additionalModulePath = join(cacheDirectory, "node_modules");
126
+ const packageDirectory = join(additionalModulePath, "additional-fixture");
127
+ mkdirSync(packageDirectory, { recursive: true });
128
+ writeFileSync(join(packageDirectory, "package.json"), JSON.stringify({
129
+ name: "additional-fixture",
130
+ version: "1.0.0",
131
+ main: "index.js",
132
+ }));
133
+ writeFileSync(join(packageDirectory, "index.js"), `module.exports = "${MARKER}";`);
134
+ });
135
+ after(() => {
136
+ rmSync(cacheDirectory, { recursive: true, force: true });
137
+ rmSync(customerDirectory, { recursive: true, force: true });
138
+ });
139
+ it("resolves a package installed outside the customer folder", async () => {
140
+ const bundle = await new BundleService(customerDirectory, [
141
+ additionalModulePath,
142
+ ]).bundle({
143
+ entryPoints: {
144
+ "handler.js": "module.exports = require('additional-fixture');",
145
+ },
146
+ minify: false,
147
+ platform: "node",
148
+ logLevel: "silent",
149
+ });
150
+ expect(bundle).to.contain(MARKER);
151
+ });
152
+ it("fails without the additional path, as it did before", async () => {
153
+ const bundling = new BundleService(customerDirectory).bundle({
154
+ entryPoints: {
155
+ "handler.js": "module.exports = require('additional-fixture');",
156
+ },
157
+ minify: false,
158
+ platform: "node",
159
+ logLevel: "silent",
160
+ });
161
+ const error = await bundling.then(() => undefined, (reason) => reason);
162
+ expect(String(error)).to.contain("additional-fixture");
163
+ });
164
+ it("resolves an alias target from an additional module path", async () => {
165
+ const bundle = await new BundleService(customerDirectory, [
166
+ additionalModulePath,
167
+ ]).bundle({
168
+ entryPoints: {
169
+ "handler.js": "module.exports = require('node-fetch');",
170
+ },
171
+ minify: false,
172
+ platform: "node",
173
+ logLevel: "silent",
174
+ alias: { "node-fetch": "additional-fixture" },
175
+ });
176
+ expect(bundle).to.contain(MARKER);
177
+ });
178
+ });
@@ -8,13 +8,14 @@ export declare class DocumentHandlerServer {
8
8
  private files;
9
9
  private bundler;
10
10
  private cli;
11
- private nodeModulesPath;
12
11
  private verboseLevel;
13
12
  private entityManager?;
14
13
  private documentHandlerCode;
15
14
  private documentHandlerConfigCode;
16
15
  private database;
17
- constructor(customerConfig: CustomerConfig, files: FileListInterface, bundler: BundleService, cli: CliService, nodeModulesPath: string, verboseLevel?: boolean, entityManager?: EntityManager);
16
+ /** Sources of app modules already looked up; `null` marks a lookup that came back empty. */
17
+ private readonly appModules;
18
+ constructor(customerConfig: CustomerConfig, files: FileListInterface, bundler: BundleService, cli: CliService, verboseLevel?: boolean, entityManager?: EntityManager);
18
19
  transform(contentType: string, html: string, variation: string, url: string, headers: Record<string, string>): Promise<DocumentHandlerResponse>;
19
20
  private getTransformFunctionWrapper;
20
21
  buildDocumentHandler(): Promise<void>;
@@ -27,7 +28,12 @@ export declare class DocumentHandlerServer {
27
28
  private getContextVars;
28
29
  private createDataBaseMock;
29
30
  private createNodeVmContext;
30
- private createRequireMock;
31
+ /**
32
+ * The `fetch` the customer's document handler calls.
33
+ *
34
+ * It stays on a direct connection, with no proxy and no fixed exit IP, because that is what the
35
+ * document handler gets in production.
36
+ */
31
37
  private createFetchMock;
32
38
  private createProcessPartial;
33
39
  /**
@@ -37,4 +43,18 @@ export declare class DocumentHandlerServer {
37
43
  */
38
44
  bundleDynamicFetcher(basePaths: Record<string, string>, dynamicFetcher: string): Promise<string>;
39
45
  bundleDocumentHandlerCode(name: string, code: string): Promise<string>;
46
+ /**
47
+ * Loads a module the customer folder does not contain from the app the config is deployed to.
48
+ *
49
+ * A document handler is shared with its Baqend app, so `require("./checkS3Assets")` may point at
50
+ * a module that only ever lived in the app. The bundler calls this after its own resolution
51
+ * failed, so a file on disk always wins.
52
+ */
53
+ private loadAppModule;
54
+ /**
55
+ * esbuild reports an unresolvable relative import without saying that the module may simply live
56
+ * in the app rather than in the customer folder — which is all the reader needs to know when the
57
+ * app lookup was skipped because there is no session.
58
+ */
59
+ private explainUnresolvedModules;
40
60
  }
@@ -2,7 +2,6 @@ import { LATEST_DYNAMIC_FETCHER_URL, LOCAL_DOCUMENT_HANDLER, LOCAL_DOCUMENT_HAND
2
2
  import RequiredFileNotFoundError from "./error/required-file-not-found-error.js";
3
3
  import DatabaseMock from "./templates/database-mock.js";
4
4
  import * as vm from "node:vm";
5
- import path, { resolve } from "node:path";
6
5
  import { INTEGRATION_FILES } from "../../models/files.js";
7
6
  // documentHandlerDependencies
8
7
  import fetch from "node-fetch";
@@ -13,23 +12,24 @@ import { safe } from "../../helpers/safe.js";
13
12
  import { VmEmptyResponseError } from "../onboarding/error/vm-empty-response-error.js";
14
13
  import { DocumentHandlerTransformError } from "../onboarding/error/document-handler-transform-error.js";
15
14
  import { createRequire } from "node:module";
15
+ import ApplicationError from "../error-handling/error/application-error.js";
16
16
  export class DocumentHandlerServer {
17
17
  customerConfig;
18
18
  files;
19
19
  bundler;
20
20
  cli;
21
- nodeModulesPath;
22
21
  verboseLevel;
23
22
  entityManager;
24
23
  documentHandlerCode;
25
24
  documentHandlerConfigCode;
26
25
  database;
27
- constructor(customerConfig, files, bundler, cli, nodeModulesPath, verboseLevel = false, entityManager) {
26
+ /** Sources of app modules already looked up; `null` marks a lookup that came back empty. */
27
+ appModules = new Map();
28
+ constructor(customerConfig, files, bundler, cli, verboseLevel = false, entityManager) {
28
29
  this.customerConfig = customerConfig;
29
30
  this.files = files;
30
31
  this.bundler = bundler;
31
32
  this.cli = cli;
32
- this.nodeModulesPath = nodeModulesPath;
33
33
  this.verboseLevel = verboseLevel;
34
34
  this.entityManager = entityManager;
35
35
  }
@@ -192,43 +192,12 @@ export class DocumentHandlerServer {
192
192
  }
193
193
  return context;
194
194
  }
195
- createRequireMock() {
196
- return (module) => {
197
- if (module === "baqend") {
198
- return { baqend: { message: { RevalidateAssets: () => true } } };
199
- }
200
- if (module.toLowerCase().includes("fetch")) {
201
- return this.createFetchMock();
202
- }
203
- const fileExtension = path.extname(module);
204
- const moduleFileName = path.basename(module, fileExtension);
205
- if (this.files.hasFile(moduleFileName)) {
206
- const vmContext = {
207
- module: { exports: {} },
208
- exports: {},
209
- ...this.createNodeVmContext(),
210
- };
211
- vm.runInNewContext(this.files
212
- .getByName(moduleFileName)
213
- .getContent(), vmContext, { displayErrors: true });
214
- return { ...vmContext.module.exports, ...vmContext.exports };
215
- }
216
- try {
217
- return import(module);
218
- }
219
- catch {
220
- // do nothing
221
- }
222
- try {
223
- const additional = resolve(this.nodeModulesPath, "node_modules", module);
224
- return import(additional);
225
- }
226
- catch {
227
- // do nothing
228
- }
229
- this.cli.writeError(`[documentHandler] could not load module: ${module}`);
230
- };
231
- }
195
+ /**
196
+ * The `fetch` the customer's document handler calls.
197
+ *
198
+ * It stays on a direct connection, with no proxy and no fixed exit IP, because that is what the
199
+ * document handler gets in production.
200
+ */
232
201
  createFetchMock() {
233
202
  return async (url, options) => {
234
203
  if (this.verboseLevel) {
@@ -270,7 +239,7 @@ export class DocumentHandlerServer {
270
239
  });
271
240
  }
272
241
  async bundleDocumentHandlerCode(name, code) {
273
- return this.bundler.bundle({
242
+ const bundling = await safe(this.bundler.bundle({
274
243
  entryPoints: {
275
244
  [name]: code,
276
245
  },
@@ -279,6 +248,60 @@ export class DocumentHandlerServer {
279
248
  platform: "node",
280
249
  logLevel: "silent",
281
250
  alias: { "node-fetch": "node-fetch-native" },
282
- });
251
+ resolveFallback: this.loadAppModule,
252
+ }));
253
+ if (bundling.success === true) {
254
+ return bundling.data;
255
+ }
256
+ throw this.explainUnresolvedModules(bundling.errorObj ?? new Error(bundling.error));
257
+ }
258
+ /**
259
+ * Loads a module the customer folder does not contain from the app the config is deployed to.
260
+ *
261
+ * A document handler is shared with its Baqend app, so `require("./checkS3Assets")` may point at
262
+ * a module that only ever lived in the app. The bundler calls this after its own resolution
263
+ * failed, so a file on disk always wins.
264
+ */
265
+ loadAppModule = async (specifier) => {
266
+ if (!this.entityManager) {
267
+ return null;
268
+ }
269
+ const moduleName = specifier
270
+ .replace(/^(?:\.{1,2}\/)+/, "")
271
+ .replace(/\.js$/, "");
272
+ if (!this.appModules.has(moduleName)) {
273
+ // esbuild resolves imports concurrently, so the action is keyed per module.
274
+ const action = `ONBOARDING:DOCUMENT_HANDLER:MODULE:${moduleName}`;
275
+ this.cli.startAction(action, this.cli.style.yellow(`[documentHandler.module]: load "${moduleName}" from app ${this.customerConfig.app}`));
276
+ const module = await safe(this.entityManager.code.loadCode(moduleName, "module"));
277
+ this.appModules.set(moduleName, module.success === true ? module.data : null);
278
+ if (module.success === true) {
279
+ this.cli.successAction(action);
280
+ }
281
+ else {
282
+ this.cli.failAction(action);
283
+ }
284
+ }
285
+ return this.appModules.get(moduleName);
286
+ };
287
+ /**
288
+ * esbuild reports an unresolvable relative import without saying that the module may simply live
289
+ * in the app rather than in the customer folder — which is all the reader needs to know when the
290
+ * app lookup was skipped because there is no session.
291
+ */
292
+ explainUnresolvedModules(error) {
293
+ const unresolved = [
294
+ ...error.message.matchAll(/Could not resolve "(\.{1,2}\/[^"]+)"/g),
295
+ ].map((match) => match[1]);
296
+ if (unresolved.length === 0) {
297
+ return error;
298
+ }
299
+ const suggestions = [
300
+ `${unresolved.join(", ")} was found neither in the customer folder nor in app "${this.customerConfig.app}".`,
301
+ ];
302
+ suggestions.push(this.entityManager
303
+ ? `Deploy the module to the app, or add it to the customer folder.`
304
+ : `You are not logged in to app "${this.customerConfig.app}", so modules that only exist in the app cannot be loaded. Run \`sk login ${this.customerConfig.app}\` and try again.`);
305
+ return new ApplicationError(error.message, suggestions);
283
306
  }
284
307
  }
@@ -8,6 +8,7 @@ import { BundleServiceFactory } from "../../bundler/index.js";
8
8
  import { EntityManagerFactory } from "../../config-api/entity-manager-factory.js";
9
9
  import { safe } from "../../../helpers/safe.js";
10
10
  import { existsSync, mkdirSync } from "node:fs";
11
+ import { resolve } from "node:path";
11
12
  export default class DocumentHandlerRuntimeServiceFactory {
12
13
  context;
13
14
  cliConfig;
@@ -33,11 +34,15 @@ export default class DocumentHandlerRuntimeServiceFactory {
33
34
  .buildService()
34
35
  .run());
35
36
  const customerConfig = this.getCustomerConfig(files);
36
- const bundler = new BundleServiceFactory(this.context.customerPath).buildService();
37
37
  const cliService = new CliServiceFactory().getService();
38
38
  await this.installAdditionalDependencies(customerConfig, cliService);
39
+ // installAdditionalDependencies() installs into the cache, not into the customer folder, so
40
+ // the bundler has to search there too or a declared dependency resolves nowhere.
41
+ const bundler = new BundleServiceFactory(this.context.customerPath, [
42
+ resolve(this.cliConfig.nodeModulesPath, "node_modules"),
43
+ ]).buildService();
39
44
  const entityManagerResult = await safe(new EntityManagerFactory().getEntityManager(customerConfig.app));
40
- return new DocumentHandlerServer(customerConfig, files, bundler, cliService, this.cliConfig.nodeModulesPath, this.context.verboseLevel, entityManagerResult.success ? entityManagerResult.data : null);
45
+ return new DocumentHandlerServer(customerConfig, files, bundler, cliService, this.context.verboseLevel, entityManagerResult.success ? entityManagerResult.data : null);
41
46
  }
42
47
  prepareIntegrationApi() {
43
48
  const integrationApiContext = new IntegrationApiContext(this.context.customerPath, this.context.configName, this.context.fileDependencies, [".git", ".idea", "node_modules"]);
@@ -1,8 +1,6 @@
1
1
  import { BaqendResponse } from "../browser/baqend-response.js";
2
2
  import { safe } from "../../../helpers/safe.js";
3
- import { Agent } from "node:https";
4
- import crypto from "node:crypto";
5
- import fetch from "node-fetch";
3
+ import { originFetch } from "../../../helpers/origin-proxy.js";
6
4
  import { formatHtml } from "../../../helpers/html-format-helper.js";
7
5
  export class RequestDiffService {
8
6
  diffService;
@@ -61,17 +59,13 @@ export class RequestDiffService {
61
59
  return await this.diffService.executeDiff(parametersFilePath, pureFilePath);
62
60
  }
63
61
  async fetchHtml(url) {
64
- const agent = new Agent({
65
- rejectUnauthorized: false,
66
- keepAlive: true,
67
- secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
68
- });
69
- const response = await safe(fetch(url, {
70
- agent: agent,
62
+ const response = await safe(originFetch(url, {
71
63
  headers: {
72
64
  "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36 SpeedKitCrawler/1.0",
73
65
  },
74
- }));
66
+ },
67
+ // The dashboard diff has always accepted a broken origin handshake here.
68
+ { tolerateLegacyTls: true }));
75
69
  if (response.success !== true) {
76
70
  console.log(response.errorObj);
77
71
  throw response.errorObj;
@@ -16,6 +16,4 @@ export declare class OnboardingServiceFactory {
16
16
  private getIntegrationFiles;
17
17
  private getUrlForTest;
18
18
  private ensureHttps;
19
- private getCrawlerAgent;
20
- private getCrawlerProxyConfig;
21
19
  }
@@ -39,8 +39,7 @@ import { StaticRecipe } from "../integration-api/virtual/static-recipe.js";
39
39
  import { DiffAgainstCurrentPage } from "./dashboard/diff-against-current-page.js";
40
40
  import { ExtensionDownloader } from "./browser/extension/extension-downloader.js";
41
41
  import { ExecutableValidator } from "./browser/executable/executable-validator.js";
42
- import { socksDispatcher } from "fetch-socks";
43
- import crypto from "node:crypto";
42
+ import { applyOriginProxy, withOriginProxyFlag, } from "../../helpers/origin-proxy.js";
44
43
  import { QueryBuilderFactory } from "../query-builder/query-builder-factory.js";
45
44
  import { QueryTpe } from "../query-builder/query-builder-model.js";
46
45
  export class OnboardingServiceFactory {
@@ -54,6 +53,8 @@ export class OnboardingServiceFactory {
54
53
  const files = await this.getIntegrationFiles();
55
54
  const cli = new CliServiceFactory().getService();
56
55
  const customerConfig = files.getCustomerConfig().config;
56
+ // Before any origin request, so Chrome and the CLI agree on the route out.
57
+ applyOriginProxy(customerConfig.chromeFlags, cli);
57
58
  const domainToStart = await this.getUrlForTest(customerConfig, cli);
58
59
  const cache = new Cache(cli);
59
60
  const documentHandlerContext = {
@@ -73,7 +74,7 @@ export class OnboardingServiceFactory {
73
74
  this.cliConfig.chromeExtensionPaths = extensionPaths.join(",");
74
75
  const browserValidator = new ExecutableValidator(this.cliConfig, cli);
75
76
  const browserVersionString = await browserValidator.validateOrInstall();
76
- const browserContext = new BrowserContext(domainToStart, browserVersionString, customerConfig.chromeFlags || [], this.cliConfig, undefined, this.context.debuggingPort, this.context.headless, this.context.debugPort);
77
+ const browserContext = new BrowserContext(domainToStart, browserVersionString, withOriginProxyFlag(customerConfig.chromeFlags), this.cliConfig, undefined, this.context.debuggingPort, this.context.headless, this.context.debugPort);
77
78
  if (this.context.debuggingPort || this.context.headless) {
78
79
  cli.writeWarning(`Chrome remote debugging enabled on port ${this.context.debugPort}.`);
79
80
  }
@@ -149,8 +150,7 @@ export class OnboardingServiceFactory {
149
150
  async getFetchEventHandler(files, customerConfig, documentHandler, cache, cli, browserContext, athenaClient, messageHandler, fileWatcher, speedKitServiceWorkerJs) {
150
151
  const configApi = this.getConfigApi(customerConfig.app);
151
152
  const serverConfig = await this.getSpeedKitServerConfig(configApi, cli);
152
- const agent = this.getCrawlerAgent(customerConfig, cli);
153
- const crawler = new Crawler(cli, agent, serverConfig);
153
+ const crawler = new Crawler(cli, serverConfig);
154
154
  const parameterQueryBuilder = await new QueryBuilderFactory({
155
155
  configName: this.context.configName,
156
156
  customerPath: this.context.customerPath,
@@ -251,42 +251,4 @@ export class OnboardingServiceFactory {
251
251
  ensureHttps(url = "") {
252
252
  return url.startsWith("https://") ? url : `https://${url}`;
253
253
  }
254
- getCrawlerAgent(customerConfig, cli) {
255
- const proxy = [];
256
- const connect = {
257
- keepAlive: true,
258
- };
259
- const agentOptions = {
260
- rejectUnauthorized: false,
261
- secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
262
- };
263
- const crawlerProxyConfig = this.getCrawlerProxyConfig(customerConfig);
264
- if (crawlerProxyConfig) {
265
- cli.writeWarning(`use proxy from chromeFlags for local documenthandler`);
266
- proxy.push(crawlerProxyConfig);
267
- }
268
- return socksDispatcher(proxy, { ...agentOptions, connect });
269
- }
270
- getCrawlerProxyConfig(customerConfig) {
271
- if (!customerConfig.chromeFlags ||
272
- customerConfig.chromeFlags.length === 0) {
273
- return;
274
- }
275
- //"--proxy-server=socks5://ap-northeast-1.proxy.baqend.com:1080"
276
- const selector = /--proxy-server=socks(4|5):(\/\/[^:]*):(\d+)/is;
277
- for (const flag of customerConfig.chromeFlags) {
278
- if (!flag.includes("--proxy-server=socks")) {
279
- continue;
280
- }
281
- const [, type, host, port] = flag.match(selector) || [];
282
- if (!type || !host || !port) {
283
- continue;
284
- }
285
- return {
286
- type: Number(type),
287
- host,
288
- port: Number(port),
289
- };
290
- }
291
- }
292
254
  }
@@ -1,13 +1,11 @@
1
1
  import { SpeedKitServerConfig } from "../onboarding-model.js";
2
2
  import { CliService } from "../../cli/index.js";
3
- import { Agent } from "undici";
4
3
  export declare class Crawler {
5
4
  private cli;
6
- private agentConfig;
7
5
  private speedKitServerConfig?;
8
6
  /** Warnings already shown, so a per-fetch condition is reported once per session. */
9
7
  private readonly warned;
10
- constructor(cli: CliService, agentConfig: Agent, speedKitServerConfig?: SpeedKitServerConfig);
8
+ constructor(cli: CliService, speedKitServerConfig?: SpeedKitServerConfig);
11
9
  private warnOnce;
12
10
  fetchRemote(originUrl: string, variation: string): Promise<{
13
11
  response: Response;