@devvit/build-pack 0.12.0-next-2025-04-30-5d2b49b36.0 → 0.12.0-next-2025-08-12-20-06-14-50f19bb3e.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 (36) hide show
  1. package/esbuild/BuildInfoUtil.d.ts +6 -8
  2. package/esbuild/BuildInfoUtil.d.ts.map +1 -1
  3. package/esbuild/BuildInfoUtil.js +40 -26
  4. package/esbuild/BundleModule.d.ts.map +1 -1
  5. package/esbuild/BundleModule.js +6 -13
  6. package/esbuild/ESBuildPack.d.ts +38 -14
  7. package/esbuild/ESBuildPack.d.ts.map +1 -1
  8. package/esbuild/ESBuildPack.js +136 -98
  9. package/esbuild/dependency-spec-util.d.ts +10 -0
  10. package/esbuild/dependency-spec-util.d.ts.map +1 -0
  11. package/esbuild/dependency-spec-util.js +135 -0
  12. package/esbuild/dependency-spec-util.test.d.ts.map +1 -0
  13. package/esbuild/templatizer/blocks.template.d.ts +10 -0
  14. package/esbuild/templatizer/blocks.template.d.ts.map +1 -0
  15. package/esbuild/templatizer/blocks.template.js +343 -0
  16. package/esbuild/templatizer/blocks.template.test.d.ts.map +1 -0
  17. package/esbuild/templatizer/templatizer.d.ts +4 -0
  18. package/esbuild/templatizer/templatizer.d.ts.map +1 -0
  19. package/esbuild/templatizer/templatizer.js +12 -0
  20. package/esbuild/templatizer/templatizer.test.d.ts.map +1 -0
  21. package/esbuild/utils.d.ts +0 -4
  22. package/esbuild/utils.d.ts.map +1 -1
  23. package/esbuild/utils.js +0 -21
  24. package/index.d.ts +0 -1
  25. package/index.d.ts.map +1 -1
  26. package/index.js +0 -1
  27. package/lib/BuildPack.d.ts +6 -16
  28. package/lib/BuildPack.d.ts.map +1 -1
  29. package/lib/BuildPack.js +10 -6
  30. package/package.json +26 -15
  31. package/esbuild/type-checkers/TscTypeChecker.d.ts +0 -13
  32. package/esbuild/type-checkers/TscTypeChecker.d.ts.map +0 -1
  33. package/esbuild/type-checkers/TscTypeChecker.js +0 -93
  34. package/esbuild/type-checkers/types.d.ts +0 -10
  35. package/esbuild/type-checkers/types.d.ts.map +0 -1
  36. package/esbuild/type-checkers/types.js +0 -1
@@ -9,24 +9,26 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _Watcher_instances, _Watcher_disableExternDevvitProtos, _Watcher_typeChecker, _Watcher_targetRuntimes, _Watcher_root, _Watcher_observable, _Watcher_disposedNotifier, _Watcher_disposeWatcher, _Watcher_namespace, _Watcher_buildTargets, _Watcher_ready, _Watcher_init, _Watcher_makeEsbuildContext, _Watcher_onBuildEnd, _ESBuildPack_bundleWatchers, _ESBuildPack_namespace, _ESBuildPack_disableExternDevvitProtos, _ESBuildPack_typeChecker, _ESBuildPack_disableTypeCheckerWarning;
13
- import crypto from 'node:crypto';
14
- import fs from 'node:fs';
15
- import { Bundle, CompileLog, CompileResponse, Minify, } from '@devvit/protos/types/devvit/plugin/buildpack/buildpack_common.js';
12
+ var _Watcher_instances, _Watcher_disableExternDevvitProtos, _Watcher_config, _Watcher_root, _Watcher_observable, _Watcher_disposedNotifier, _Watcher_disposeWatcher, _Watcher_namespace, _Watcher_buildTargets, _Watcher_ready, _Watcher_init, _Watcher_makeEntry, _Watcher_makeEsbuildContext, _Watcher_onBuildEnd, _ESBuildPack_bundleWatchers, _ESBuildPack_namespace, _ESBuildPack_disableExternDevvitProtos;
13
+ import fs, { readFileSync } from 'node:fs';
14
+ import path from 'node:path';
15
+ import { Bundle } from '@devvit/protos/types/devvit/plugin/buildpack/buildpack_common.js';
16
16
  import { TargetRuntime, } from '@devvit/protos/types/devvit/runtime/bundle.js';
17
17
  import { ConfigImpl as Config } from '@devvit/shared-types/ConfigImpl.js';
18
- import { assertNonNull, NonNull } from '@devvit/shared-types/NonNull.js';
18
+ import { NonNull } from '@devvit/shared-types/NonNull.js';
19
19
  import { StringUtil } from '@devvit/shared-types/StringUtil.js';
20
20
  import esbuild, {} from 'esbuild';
21
21
  import { BehaviorSubject, Observable, Subject } from 'rxjs';
22
+ import { of } from 'rxjs';
22
23
  import { takeUntil } from 'rxjs/operators';
23
- import glob from 'tiny-glob';
24
- import { getModuleEntrypoint } from '../lib/BuildPack.js';
24
+ import { getClassicModuleEntrypoint, getModuleEntrypoint } from '../lib/BuildPack.js';
25
25
  import { newBuildInfoDependencies } from './BuildInfoUtil.js';
26
26
  import { dangerouslyGetBundleActor } from './BundleModule.js';
27
+ import { createDependencySpec } from './dependency-spec-util.js';
27
28
  import { externalizeDevvitProtos } from './plugins/externalizeDevvitProtos.js';
28
29
  import { postProcessServerStubCode, serverClientCodeSplit, } from './plugins/serverClientCodeSplit.js';
29
- import { mergeCompileRes, prefixBuildResultLogs } from './utils.js';
30
+ import { templatize } from './templatizer/templatizer.js';
31
+ import { prefixBuildResultLogs } from './utils.js';
30
32
  const FAKE_FACTORY = {
31
33
  // TODO: remove use of any below
32
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -38,11 +40,10 @@ export class Watcher {
38
40
  * The default behavior is to externalize @devvit/protos. Set
39
41
  * disableExternDevvitProtos to bundle this large dependency.
40
42
  */
41
- constructor(namespace, root, actorSpec, options) {
43
+ constructor(config, namespace, root, actorSpec, options) {
42
44
  _Watcher_instances.add(this);
43
45
  _Watcher_disableExternDevvitProtos.set(this, void 0);
44
- _Watcher_typeChecker.set(this, void 0);
45
- _Watcher_targetRuntimes.set(this, void 0);
46
+ _Watcher_config.set(this, void 0);
46
47
  _Watcher_root.set(this, void 0);
47
48
  /**
48
49
  * the actual emitter of updates
@@ -59,11 +60,10 @@ export class Watcher {
59
60
  _Watcher_namespace.set(this, void 0);
60
61
  _Watcher_buildTargets.set(this, new Map());
61
62
  _Watcher_ready.set(this, void 0);
63
+ __classPrivateFieldSet(this, _Watcher_config, config, "f");
62
64
  __classPrivateFieldSet(this, _Watcher_namespace, namespace, "f");
63
65
  __classPrivateFieldSet(this, _Watcher_root, root, "f");
64
66
  __classPrivateFieldSet(this, _Watcher_disableExternDevvitProtos, options?.disableExternDevvitProtos ?? false, "f");
65
- __classPrivateFieldSet(this, _Watcher_typeChecker, options?.typeChecker, "f");
66
- __classPrivateFieldSet(this, _Watcher_targetRuntimes, options?.targetRuntimes ?? BUILD_TARGETS, "f");
67
67
  // observers of this will be subscribed until this.disposedNotifier emits a value
68
68
  const sourceObservable = new Observable().pipe(takeUntil(__classPrivateFieldGet(this, _Watcher_disposedNotifier, "f")));
69
69
  // our observable is a BehaviorSubject used to multicast the source observer
@@ -88,11 +88,12 @@ export class Watcher {
88
88
  __classPrivateFieldGet(this, _Watcher_disposedNotifier, "f").complete();
89
89
  }
90
90
  }
91
- _Watcher_disableExternDevvitProtos = new WeakMap(), _Watcher_typeChecker = new WeakMap(), _Watcher_targetRuntimes = new WeakMap(), _Watcher_root = new WeakMap(), _Watcher_observable = new WeakMap(), _Watcher_disposedNotifier = new WeakMap(), _Watcher_disposeWatcher = new WeakMap(), _Watcher_namespace = new WeakMap(), _Watcher_buildTargets = new WeakMap(), _Watcher_ready = new WeakMap(), _Watcher_instances = new WeakSet(), _Watcher_init = async function _Watcher_init(actorSpec) {
91
+ _Watcher_disableExternDevvitProtos = new WeakMap(), _Watcher_config = new WeakMap(), _Watcher_root = new WeakMap(), _Watcher_observable = new WeakMap(), _Watcher_disposedNotifier = new WeakMap(), _Watcher_disposeWatcher = new WeakMap(), _Watcher_namespace = new WeakMap(), _Watcher_buildTargets = new WeakMap(), _Watcher_ready = new WeakMap(), _Watcher_instances = new WeakSet(), _Watcher_init = async function _Watcher_init(actorSpec) {
92
+ const entry = __classPrivateFieldGet(this, _Watcher_instances, "m", _Watcher_makeEntry).call(this);
92
93
  // Building for Universal and Client targets. May include Server in the future.
93
- for (const target of __classPrivateFieldGet(this, _Watcher_targetRuntimes, "f")) {
94
+ for (const target of BUILD_TARGETS) {
94
95
  __classPrivateFieldGet(this, _Watcher_buildTargets, "f").set(target, {
95
- ctx: await __classPrivateFieldGet(this, _Watcher_instances, "m", _Watcher_makeEsbuildContext).call(this, actorSpec, target),
96
+ ctx: await __classPrivateFieldGet(this, _Watcher_instances, "m", _Watcher_makeEsbuildContext).call(this, actorSpec, target, entry),
96
97
  });
97
98
  }
98
99
  __classPrivateFieldSet(this, _Watcher_disposeWatcher, async () => {
@@ -110,12 +111,19 @@ _Watcher_disableExternDevvitProtos = new WeakMap(), _Watcher_typeChecker = new W
110
111
  watchers.push(target.ctx.watch());
111
112
  }
112
113
  await Promise.all(watchers);
113
- }, _Watcher_makeEsbuildContext = async function _Watcher_makeEsbuildContext(actorSpec, targetRuntime) {
114
- const entryPoint = getModuleEntrypoint(__classPrivateFieldGet(this, _Watcher_root, "f"));
115
- const cfg = esbuildConfig(__classPrivateFieldGet(this, _Watcher_disableExternDevvitProtos, "f"), targetRuntime, true);
114
+ }, _Watcher_makeEntry = function _Watcher_makeEntry() {
115
+ // The watched file doesn't change but its dependencies do.
116
+ const entry = getModuleEntrypoint(__classPrivateFieldGet(this, _Watcher_config, "f"), __classPrivateFieldGet(this, _Watcher_root, "f"));
117
+ if (__classPrivateFieldGet(this, _Watcher_config, "f")) {
118
+ const template = templatize(__classPrivateFieldGet(this, _Watcher_root, "f"), __classPrivateFieldGet(this, _Watcher_config, "f").blocks?.entry);
119
+ fs.writeFileSync(entry, template);
120
+ }
121
+ return entry;
122
+ }, _Watcher_makeEsbuildContext = async function _Watcher_makeEsbuildContext(actorSpec, targetRuntime, entry) {
123
+ const cfg = esbuildConfig(__classPrivateFieldGet(this, _Watcher_config, "f"), __classPrivateFieldGet(this, _Watcher_disableExternDevvitProtos, "f"), targetRuntime, true);
116
124
  return await esbuild.context({
117
125
  ...cfg,
118
- entryPoints: [entryPoint],
126
+ entryPoints: [entry],
119
127
  minify: false,
120
128
  sourcemap: 'external',
121
129
  outfile: 'main.js',
@@ -125,7 +133,7 @@ _Watcher_disableExternDevvitProtos = new WeakMap(), _Watcher_typeChecker = new W
125
133
  name: 'optionally-type-check-and-publish-compile-result-on-build-end',
126
134
  setup: (build) => {
127
135
  build.onEnd(async (result) => {
128
- await __classPrivateFieldGet(this, _Watcher_instances, "m", _Watcher_onBuildEnd).call(this, actorSpec, result, entryPoint, targetRuntime);
136
+ await __classPrivateFieldGet(this, _Watcher_instances, "m", _Watcher_onBuildEnd).call(this, actorSpec, result, targetRuntime);
129
137
  });
130
138
  },
131
139
  },
@@ -137,7 +145,7 @@ _Watcher_disableExternDevvitProtos = new WeakMap(), _Watcher_typeChecker = new W
137
145
  /**
138
146
  * @description The callback function for esbuild's onEnd hook. This is called after esbuild has finished building.
139
147
  */
140
- async function _Watcher_onBuildEnd(actorSpec, esbuildResult, entryPoint, targetRuntime) {
148
+ async function _Watcher_onBuildEnd(actorSpec, esbuildResult, targetRuntime) {
141
149
  await __classPrivateFieldGet(this, _Watcher_ready, "f");
142
150
  __classPrivateFieldGet(this, _Watcher_buildTargets, "f").get(targetRuntime).result = esbuildResult;
143
151
  const results = new Map();
@@ -147,22 +155,12 @@ async function _Watcher_onBuildEnd(actorSpec, esbuildResult, entryPoint, targetR
147
155
  return;
148
156
  results.set(targetRuntime, target.result);
149
157
  }
150
- const esbuildCompileRes = await newCompileResponse(__classPrivateFieldGet(this, _Watcher_namespace, "f"), actorSpec, results, entryPoint);
158
+ const esbuildCompileRes = await newCompileResponse(__classPrivateFieldGet(this, _Watcher_config, "f"), __classPrivateFieldGet(this, _Watcher_namespace, "f"), actorSpec, results, __classPrivateFieldGet(this, _Watcher_root, "f"));
151
159
  // clear saved values so we don't double-fire the next time we get results
152
160
  for (const [_, target] of __classPrivateFieldGet(this, _Watcher_buildTargets, "f")) {
153
161
  target.result = undefined;
154
162
  }
155
- // to-do: why is type-checking disabled for projects with tsx files (nearly
156
- // all projects).
157
- if (!__classPrivateFieldGet(this, _Watcher_typeChecker, "f") || (await hasTsxFiles(__classPrivateFieldGet(this, _Watcher_root, "f")))) {
158
- console.warn('Type checking is disabled.');
159
- __classPrivateFieldGet(this, _Watcher_observable, "f").next(esbuildCompileRes);
160
- return;
161
- }
162
- const typeCheckCompileRes = await __classPrivateFieldGet(this, _Watcher_typeChecker, "f").check({
163
- filename: __classPrivateFieldGet(this, _Watcher_root, "f"),
164
- });
165
- __classPrivateFieldGet(this, _Watcher_observable, "f").next(mergeCompileRes(esbuildCompileRes, typeCheckCompileRes));
163
+ __classPrivateFieldGet(this, _Watcher_observable, "f").next(esbuildCompileRes);
166
164
  };
167
165
  export class ESBuildPack {
168
166
  /**
@@ -178,35 +176,32 @@ export class ESBuildPack {
178
176
  _ESBuildPack_namespace.set(this, void 0);
179
177
  // EsbuildPack options
180
178
  _ESBuildPack_disableExternDevvitProtos.set(this, void 0);
181
- // type chcecking will be disabled if typeChecker is undefined
182
- _ESBuildPack_typeChecker.set(this, void 0);
183
- // don't print out warnings if this is set
184
- _ESBuildPack_disableTypeCheckerWarning.set(this, void 0);
185
179
  __classPrivateFieldSet(this, _ESBuildPack_namespace, namespace, "f");
186
180
  __classPrivateFieldSet(this, _ESBuildPack_disableExternDevvitProtos, options?.disableExternDevvitProtos ?? false, "f");
187
- __classPrivateFieldSet(this, _ESBuildPack_typeChecker, options?.typeChecker, "f");
188
- __classPrivateFieldSet(this, _ESBuildPack_disableTypeCheckerWarning, options?.disableTypeCheckerWarning ?? false, "f");
189
181
  }
190
- async Compile({ filename, minify, info, includeMetafile }, _metadata) {
191
- // TODO: add flag to conditionally compile stripped bundle
192
- if (filename == null) {
193
- // TODO: implement virtualFileSystem
194
- throw new Error('Bundling virtual file system not implemented yet');
182
+ async compile({ config, minify, info, includeMetafile, root, }) {
183
+ const entry = getModuleEntrypoint(config, root);
184
+ if (config) {
185
+ const template = templatize(root, config.blocks?.entry);
186
+ try {
187
+ fs.writeFileSync(entry, template);
188
+ }
189
+ catch (err) {
190
+ return unknownToErrorCompileResponse('Compilation', err);
191
+ }
195
192
  }
196
- assertNonNull(info, "Expected 'info' of CompileParams to be non-null");
197
- const entrypoint = getModuleEntrypoint(filename);
198
193
  const buildResults = new Map();
199
194
  for (const targetRuntime of BUILD_TARGETS) {
200
- const cfg = esbuildConfig(__classPrivateFieldGet(this, _ESBuildPack_disableExternDevvitProtos, "f"), targetRuntime);
195
+ const cfg = esbuildConfig(config, __classPrivateFieldGet(this, _ESBuildPack_disableExternDevvitProtos, "f"), targetRuntime);
201
196
  try {
202
197
  const buildResult = await esbuild.build({
203
198
  ...cfg,
204
- entryPoints: [entrypoint],
199
+ entryPoints: [entry],
205
200
  metafile: true,
206
201
  sourcemap: 'external',
207
202
  plugins: [excludeNodeModulesFromSourceMapsPlugin(), ...(cfg.plugins || [])],
208
203
  sourcesContent: false,
209
- minify: minify === Minify.ALL,
204
+ minify: minify === 'All',
210
205
  outfile: 'main.js',
211
206
  treeShaking: true,
212
207
  });
@@ -214,46 +209,40 @@ export class ESBuildPack {
214
209
  }
215
210
  catch (err) {
216
211
  return isEsbuildResult(err)
217
- ? await newCompileResponse(__classPrivateFieldGet(this, _ESBuildPack_namespace, "f"), info, new Map([[targetRuntime, err]]), entrypoint, includeMetafile)
212
+ ? await newCompileResponse(config, __classPrivateFieldGet(this, _ESBuildPack_namespace, "f"), info, new Map([[targetRuntime, err]]), root, includeMetafile)
218
213
  : unknownToErrorCompileResponse('Compilation', err);
219
214
  }
220
215
  }
221
- const esbuildCompileResult = await newCompileResponse(__classPrivateFieldGet(this, _ESBuildPack_namespace, "f"), info, buildResults, entrypoint, includeMetafile);
216
+ const esbuildCompileResult = await newCompileResponse(config, __classPrivateFieldGet(this, _ESBuildPack_namespace, "f"), info, buildResults, root, includeMetafile);
222
217
  if (esbuildCompileResult.bundles.length === 0) {
223
218
  throw new Error(esbuildCompileResult.errors?.[0]?.text ?? 'Missing bundle');
224
219
  }
225
- // to-do: why is type-checking disabled for projects with tsx files (nearly
226
- // all projects).
227
- if (!__classPrivateFieldGet(this, _ESBuildPack_typeChecker, "f") || (await hasTsxFiles(filename))) {
228
- if (!__classPrivateFieldGet(this, _ESBuildPack_disableTypeCheckerWarning, "f"))
229
- console.warn('Type checking is disabled.');
230
- return esbuildCompileResult;
231
- }
232
- const typeCheckingResult = await __classPrivateFieldGet(this, _ESBuildPack_typeChecker, "f").check({ filename });
233
- return mergeCompileRes(esbuildCompileResult, typeCheckingResult);
220
+ return esbuildCompileResult;
234
221
  }
235
222
  async dispose() {
236
223
  await Promise.all(Array.from(__classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").values()).map((watcher) => watcher.dispose()));
237
224
  }
238
- Watch({ filename, info }, _metadata) {
239
- assertNonNull(filename, "Expected 'filename' of CompileParams to be non-null");
240
- assertNonNull(info, "Expected 'info' of CompileParams to be non-null");
241
- if (!__classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").has(filename)) {
242
- const watcher = new Watcher(__classPrivateFieldGet(this, _ESBuildPack_namespace, "f"), filename, info, {
243
- disableExternDevvitProtos: __classPrivateFieldGet(this, _ESBuildPack_disableExternDevvitProtos, "f"),
244
- typeChecker: __classPrivateFieldGet(this, _ESBuildPack_typeChecker, "f"),
245
- });
246
- __classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").set(filename, watcher);
225
+ watch({ config, root, info }) {
226
+ // Use a stable entrypoint, not a template.
227
+ const blocksEntry = config?.blocks?.entry ?? getClassicModuleEntrypoint(root);
228
+ if (!__classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").has(blocksEntry)) {
229
+ let watcher;
230
+ try {
231
+ watcher = new Watcher(config, __classPrivateFieldGet(this, _ESBuildPack_namespace, "f"), root, info, {
232
+ disableExternDevvitProtos: __classPrivateFieldGet(this, _ESBuildPack_disableExternDevvitProtos, "f"),
233
+ });
234
+ }
235
+ catch (err) {
236
+ return of(unknownToErrorCompileResponse('Compilation', err));
237
+ }
238
+ __classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").set(root, watcher);
247
239
  return watcher.getObservable();
248
240
  }
249
- return NonNull(__classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").get(filename)).getObservable();
241
+ return NonNull(__classPrivateFieldGet(this, _ESBuildPack_bundleWatchers, "f").get(blocksEntry)).getObservable();
250
242
  }
251
243
  }
252
- _ESBuildPack_bundleWatchers = new WeakMap(), _ESBuildPack_namespace = new WeakMap(), _ESBuildPack_disableExternDevvitProtos = new WeakMap(), _ESBuildPack_typeChecker = new WeakMap(), _ESBuildPack_disableTypeCheckerWarning = new WeakMap();
253
- // TODO: Complete way to determine dependencies from module's config.ts or config.yaml. See DX-61
254
- // TODO: Owner should come from project root's config file
255
- async function newCompileResponse(namespace, actorSpec, buildResults, entryPoint, includeMetafile = false) {
256
- assertNonNull(actorSpec, 'Expected actorSpec to be non-null');
244
+ _ESBuildPack_bundleWatchers = new WeakMap(), _ESBuildPack_namespace = new WeakMap(), _ESBuildPack_disableExternDevvitProtos = new WeakMap();
245
+ async function newCompileResponse(config, namespace, actorSpec, buildResults, root, includeMetafile = false) {
257
246
  const errors = [];
258
247
  const warnings = [];
259
248
  const bundles = [];
@@ -289,8 +278,7 @@ async function newCompileResponse(namespace, actorSpec, buildResults, entryPoint
289
278
  }
290
279
  let dependencies;
291
280
  try {
292
- const ActorClass = await dangerouslyGetBundleActor(code);
293
- dependencies = await makeDependencySpec(namespace, actorSpec, ActorClass);
281
+ dependencies = await getDependencySpec(actorSpec, code, config, namespace);
294
282
  }
295
283
  catch (err) {
296
284
  return unknownToErrorCompileResponse('Evaluation', err);
@@ -306,31 +294,25 @@ async function newCompileResponse(namespace, actorSpec, buildResults, entryPoint
306
294
  dependencies,
307
295
  buildInfo: {
308
296
  created: new Date(),
309
- dependencies: newBuildInfoDependencies(entryPoint),
297
+ dependencies: newBuildInfoDependencies(root),
310
298
  targetRuntime,
311
299
  },
312
300
  webviewAssetIds: {},
313
301
  metafile,
314
302
  });
315
303
  }
316
- // remove duplicate bundles
317
- const filteredBundles = [];
318
- const bundleHashes = new Set();
319
- bundles.forEach((bundle) => {
320
- const algo = crypto.createHash('sha256');
321
- algo.update(bundle.code);
322
- const hash = algo.digest('hex');
323
- if (!bundleHashes.has(hash)) {
324
- bundleHashes.add(hash);
325
- filteredBundles.push(bundle);
326
- }
327
- });
328
- if (filteredBundles.length === 1) {
329
- filteredBundles[0].buildInfo.targetRuntime = TargetRuntime.UNIVERSAL;
304
+ try {
305
+ updateBundleServer(bundles, root, config?.server);
330
306
  }
331
- return { bundles: filteredBundles, errors, warnings };
307
+ catch {
308
+ const serverEntry = config?.server
309
+ ? path.join(config.server.dir, config.server.entry)
310
+ : undefined;
311
+ return unknownToErrorCompileResponse('Evaluation', `Cannot read \`config.server.entry\` file (${serverEntry}).`);
312
+ }
313
+ return { bundles, errors, warnings };
332
314
  }
333
- async function makeDependencySpec(namespace, actorSpec, ActorClass) {
315
+ async function dangerouslyMakeDependencySpec(namespace, actorSpec, ActorClass) {
334
316
  const config = new Config(FAKE_FACTORY, actorSpec, {}, {});
335
317
  new ActorClass(config);
336
318
  const spec = config.export(namespace);
@@ -368,10 +350,22 @@ function esbuildMessageToBuildLog(msg) {
368
350
  function isEsbuildResult(err) {
369
351
  return err instanceof Object && 'errors' in err && 'warnings' in err;
370
352
  }
371
- function esbuildConfig(disableExternDevvitProtos, targetRuntime, watchMode = false) {
353
+ /** Call with defined config for v1 behavior. */
354
+ function esbuildConfig(config, disableExternDevvitProtos, targetRuntime, watchMode = false) {
372
355
  return {
356
+ // When Blocks migration is used, the Devvit singleton used in the template
357
+ // must be the same instance as that used elsewhere. Aliases are resolved
358
+ // from the current working directory, not the compiled file's directory, so
359
+ // this effectively maps the template's @devvit/public-api to the app's
360
+ // version. The same could be done for @devvit/server but apps don't
361
+ // necessarily have a copy.
362
+ alias: config?.blocks ? { '@devvit/public-api': '@devvit/public-api' } : {},
373
363
  // Recursively inline any imported dependencies.
374
364
  bundle: true,
365
+ // Tightly coupled to blocks.template.tsx.
366
+ define: {
367
+ 'globalThis.__devvit__': config ? JSON.stringify({ config }, undefined, 2) : 'undefined',
368
+ },
375
369
  external: [
376
370
  'node:*',
377
371
  // All .js files from https://github.com/nodejs/node/tree/b2405e9/lib that
@@ -474,6 +468,50 @@ function excludeNodeModulesFromSourceMapsPlugin() {
474
468
  },
475
469
  };
476
470
  }
477
- async function hasTsxFiles(dir) {
478
- return await glob('**/*.tsx', { cwd: dir }).then((files) => files.length > 0);
471
+ async function getDependencySpec(actorSpec, code, config, namespace) {
472
+ if (config)
473
+ return createDependencySpec(actorSpec, config, namespace);
474
+ const ActorClass = await dangerouslyGetBundleActor(code);
475
+ return await dangerouslyMakeDependencySpec(namespace, actorSpec, ActorClass);
476
+ }
477
+ /** Throws when unable to read config.server.entry. */
478
+ function newServerBundle(root, server) {
479
+ const serverEntry = path.join(server.dir, server.entry);
480
+ let code;
481
+ try {
482
+ code = readFileSync(path.resolve(root, serverEntry), 'utf8');
483
+ }
484
+ catch (err) {
485
+ throw Error(`Cannot read \`config.server.entry\` file (${serverEntry}).`, {
486
+ cause: err,
487
+ });
488
+ }
489
+ const sourceMapFilename = findSourceMapURL(code) ?? `${serverEntry}.map`;
490
+ let sourceMap = '';
491
+ try {
492
+ sourceMap = readFileSync(path.resolve(root, sourceMapFilename), 'utf8');
493
+ }
494
+ catch {
495
+ // source map files aren't required, safe to swallow this error
496
+ }
497
+ return { code, sourceMap };
498
+ }
499
+ /** @internal */
500
+ export function findSourceMapURL(src) {
501
+ return /[@#]\s*sourceMappingURL=([^\s*]+)(?![\s\S]*sourceMappingURL)/.exec(src)?.[1];
502
+ }
503
+ /** Throws when unable to read config.server.entry. */
504
+ export function updateBundleServer(bundles, root, server) {
505
+ if (!server)
506
+ return;
507
+ for (const bundle of bundles)
508
+ if (bundle.buildInfo?.targetRuntime === TargetRuntime.UNIVERSAL)
509
+ bundle.server = newServerBundle(root, server);
510
+ }
511
+ export function updateBundleVersion(bundles, version) {
512
+ for (const bundle of bundles) {
513
+ bundle.dependencies ??= { hostname: '', provides: [], uses: [], permissions: [] };
514
+ bundle.dependencies.actor ??= { name: '', owner: '', version: '' };
515
+ bundle.dependencies.actor.version = version.toString();
516
+ }
479
517
  }
@@ -0,0 +1,10 @@
1
+ import type { ActorSpec, DependencySpec } from '@devvit/protos/community.js';
2
+ import type { Namespace } from '@devvit/shared-types/Namespace.js';
3
+ import type { AppConfig } from '@devvit/shared-types/schemas/config-file.v1.js';
4
+ /**
5
+ * Convert a static `AppConfig` to a `DependencySpec`. Similar to the classic
6
+ * `Devvit` singleton, `ConfigImpl`, `addPaymentHandler()`, and
7
+ * `paymentsPlugin`.
8
+ */
9
+ export declare function createDependencySpec(actorSpec: Readonly<ActorSpec>, config: Readonly<Omit<AppConfig, 'json'>>, namespace: Readonly<Namespace>): DependencySpec;
10
+ //# sourceMappingURL=dependency-spec-util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-spec-util.d.ts","sourceRoot":"","sources":["../../src/esbuild/dependency-spec-util.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAK7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gDAAgD,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC9B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,EACzC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,GAC7B,cAAc,CAgHhB"}
@@ -0,0 +1,135 @@
1
+ import { AppSettingsDefinition, ContextActionDefinition, CustomPostDefinition, Definition, FlairDefinition, GraphQLDefinition, HTTPDefinition, InstallationSettingsDefinition, LinksAndCommentsDefinition, ListingsDefinition, MediaServiceDefinition, ModerationDefinition, ModlogDefinition, ModNoteDefinition, NewModmailDefinition, OnAppInstallDefinition, OnAppUpgradeDefinition, OnAutomoderatorFilterCommentDefinition, OnAutomoderatorFilterPostDefinition, OnCommentCreateDefinition, OnCommentDeleteDefinition, OnCommentReportDefinition, OnCommentSubmitDefinition, OnCommentUpdateDefinition, OnModActionDefinition, OnModMailDefinition, OnPostCreateDefinition, OnPostDeleteDefinition, OnPostFlairUpdateDefinition, OnPostNsfwUpdateDefinition, OnPostReportDefinition, OnPostSpoilerUpdateDefinition, OnPostSubmitDefinition, OnPostUpdateDefinition, PrivateMessagesDefinition, RealtimeDefinition, RedisAPIDefinition, SchedulerHandlerDefinition, SettingsDefinition, SubredditsDefinition, UIEventHandlerDefinition, UserActionsDefinition, UsersDefinition, WidgetsDefinition, WikiDefinition, } from '@devvit/protos';
2
+ import { PaymentProcessorDefinition, PaymentsServiceDefinition } from '@devvit/protos/payments.js';
3
+ import { WebbitServerDefinition } from '@devvit/protos/types/devvit/actor/webbit/webbit.js';
4
+ import { normalizeDomains } from '@devvit/shared-types/fetch-domains.js';
5
+ import { PLUGIN_NAME, resolveActorHostname } from '@devvit/shared-types/HostnameUtil.js';
6
+ /**
7
+ * Convert a static `AppConfig` to a `DependencySpec`. Similar to the classic
8
+ * `Devvit` singleton, `ConfigImpl`, `addPaymentHandler()`, and
9
+ * `paymentsPlugin`.
10
+ */
11
+ export function createDependencySpec(actorSpec, config, namespace) {
12
+ const spec = {
13
+ actor: actorSpec,
14
+ hostname: resolveActorHostname(actorSpec.name, namespace),
15
+ permissions: [],
16
+ provides: [],
17
+ uses: [],
18
+ };
19
+ const permissions = {
20
+ requestedFetchDomains: [],
21
+ asUserScopes: [],
22
+ };
23
+ if (config.permissions.http.enable) {
24
+ use(spec, HTTPDefinition);
25
+ permissions.requestedFetchDomains.push(...normalizeDomains(config.permissions.http.domains));
26
+ }
27
+ if (config.permissions.media)
28
+ use(spec, MediaServiceDefinition);
29
+ if (config.permissions.payments) {
30
+ use(spec, PaymentsServiceDefinition);
31
+ provide(spec, PaymentProcessorDefinition);
32
+ }
33
+ if (config.permissions.realtime)
34
+ use(spec, RealtimeDefinition);
35
+ if (config.permissions.reddit.enable) {
36
+ use(spec, FlairDefinition, GraphQLDefinition, LinksAndCommentsDefinition, ListingsDefinition, ModerationDefinition, ModNoteDefinition, NewModmailDefinition, PrivateMessagesDefinition, SubredditsDefinition, UsersDefinition, WidgetsDefinition, WikiDefinition);
37
+ if (config.permissions.reddit.scope === 'moderator')
38
+ use(spec, ModlogDefinition);
39
+ if (config.permissions.reddit.asUser.length > 0) {
40
+ use(spec, UserActionsDefinition);
41
+ if (config.permissions.reddit.asUser.length > 0) {
42
+ permissions.asUserScopes.push(...config.permissions.reddit.asUser);
43
+ }
44
+ }
45
+ }
46
+ if (config.permissions.redis)
47
+ use(spec, RedisAPIDefinition);
48
+ if (permissions.requestedFetchDomains.length > 0 || permissions.asUserScopes.length > 0) {
49
+ spec.permissions.push(permissions);
50
+ }
51
+ if (config.post) {
52
+ provide(spec, CustomPostDefinition, UIEventHandlerDefinition);
53
+ }
54
+ if (config.server)
55
+ provide(spec, WebbitServerDefinition);
56
+ if (config.permissions.menu)
57
+ provide(spec, ContextActionDefinition);
58
+ if (config.permissions.settings) {
59
+ use(spec, SettingsDefinition);
60
+ provide(spec, AppSettingsDefinition, InstallationSettingsDefinition);
61
+ }
62
+ if (config.forms)
63
+ provide(spec, UIEventHandlerDefinition);
64
+ if (config.scheduler) {
65
+ provide(spec, SchedulerHandlerDefinition);
66
+ if (Object.values(config.scheduler.tasks).some((task) => task.cron)) {
67
+ provide(spec, OnAppInstallDefinition);
68
+ provide(spec, OnAppUpgradeDefinition);
69
+ }
70
+ }
71
+ if (config.triggers) {
72
+ if (config.triggers.onAppInstall)
73
+ provide(spec, OnAppInstallDefinition);
74
+ if (config.triggers.onAppUpgrade)
75
+ provide(spec, OnAppUpgradeDefinition);
76
+ if (config.triggers.onAutomoderatorFilterComment)
77
+ provide(spec, OnAutomoderatorFilterCommentDefinition);
78
+ if (config.triggers.onAutomoderatorFilterPost)
79
+ provide(spec, OnAutomoderatorFilterPostDefinition);
80
+ if (config.triggers.onCommentCreate)
81
+ provide(spec, OnCommentCreateDefinition);
82
+ if (config.triggers.onCommentDelete)
83
+ provide(spec, OnCommentDeleteDefinition);
84
+ if (config.triggers.onCommentReport)
85
+ provide(spec, OnCommentReportDefinition);
86
+ if (config.triggers.onCommentSubmit)
87
+ provide(spec, OnCommentSubmitDefinition);
88
+ if (config.triggers.onCommentUpdate)
89
+ provide(spec, OnCommentUpdateDefinition);
90
+ if (config.triggers.onModAction)
91
+ provide(spec, OnModActionDefinition);
92
+ if (config.triggers.onModMail)
93
+ provide(spec, OnModMailDefinition);
94
+ if (config.triggers.onPostCreate)
95
+ provide(spec, OnPostCreateDefinition);
96
+ if (config.triggers.onPostDelete)
97
+ provide(spec, OnPostDeleteDefinition);
98
+ if (config.triggers.onPostFlairUpdate)
99
+ provide(spec, OnPostFlairUpdateDefinition);
100
+ if (config.triggers.onPostNsfwUpdate)
101
+ provide(spec, OnPostNsfwUpdateDefinition);
102
+ if (config.triggers.onPostReport)
103
+ provide(spec, OnPostReportDefinition);
104
+ if (config.triggers.onPostSpoilerUpdate)
105
+ provide(spec, OnPostSpoilerUpdateDefinition);
106
+ if (config.triggers.onPostSubmit)
107
+ provide(spec, OnPostSubmitDefinition);
108
+ if (config.triggers.onPostUpdate)
109
+ provide(spec, OnPostUpdateDefinition);
110
+ }
111
+ if (config.settings) {
112
+ use(spec, SettingsDefinition);
113
+ if (config.settings.global) {
114
+ provide(spec, AppSettingsDefinition);
115
+ }
116
+ if (config.settings.subreddit) {
117
+ provide(spec, InstallationSettingsDefinition);
118
+ }
119
+ }
120
+ return spec;
121
+ }
122
+ function provide(spec, ...definitions) {
123
+ spec.provides.push(...definitions
124
+ .filter((def) => !spec.provides.some((provide) => provide.definition?.fullName === def.fullName))
125
+ .map((def) => ({
126
+ actor: structuredClone(spec.actor),
127
+ definition: Definition.toSerializable(def),
128
+ partitionsBy: [],
129
+ })));
130
+ }
131
+ function use(spec, ...definitions) {
132
+ spec.uses.push(...definitions
133
+ .filter((def) => !spec.uses.some((use) => use.typeName === def.fullName))
134
+ .map((def) => ({ name: PLUGIN_NAME, typeName: def.fullName })));
135
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-spec-util.test.d.ts","sourceRoot":"","sources":["../../src/esbuild/dependency-spec-util.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { Devvit, type FormKey } from '@devvit/public-api';
2
+ import type { UiResponse } from '@devvit/shared';
3
+ /** @internal [state] Map of devvit.json form keys to Devvit-singleton form keys. */
4
+ export declare const formKeyMap: {
5
+ [formKey: string]: FormKey;
6
+ };
7
+ /** @internal */
8
+ export declare function validateUiResponse(uiResponse: UiResponse): void;
9
+ export default Devvit;
10
+ //# sourceMappingURL=blocks.template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.template.d.ts","sourceRoot":"","sources":["../../../src/esbuild/templatizer/blocks.template.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,MAAM,EACN,KAAK,OAAO,EAOb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAA8C,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAwB7F,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE;IAAE,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAAO,CAAC;AAkH7D,gBAAgB;AAChB,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CA4D/D;AA4OD,eAAe,MAAM,CAAC"}